From 472c287cd3a05953020ef73cc7cd88da9982ee84 Mon Sep 17 00:00:00 2001 From: Felix Kling Date: Wed, 18 Feb 2015 16:39:28 -0800 Subject: [PATCH] Update react-docgen and ignore pages with no header --- .../lib/ReactDocumentationParser.js | 211 +++++------------- .../ReactDocumentationParser-test.js | 127 +++-------- .../findAllReactCreateClassCalls-test.js | 106 +++++++++ .../findExportedReactCreateClassCall-test.js | 106 +++++++++ .../findAllReactCreateClassCalls.js | 47 ++++ .../findExportedReactCreateClassCall.js | 78 +++++++ .../isExportsOrModuleAssignment-test.js | 36 +++ .../lib/utils/isExportsOrModuleAssignment.js | 37 +++ .../lib/utils/isReactCreateClassCall.js | 37 +++ website/react-docgen/package.json | 2 +- website/server/convert.js | 6 +- website/server/extractDocs.js | 17 +- 12 files changed, 564 insertions(+), 246 deletions(-) create mode 100644 website/react-docgen/lib/strategies/__tests__/findAllReactCreateClassCalls-test.js create mode 100644 website/react-docgen/lib/strategies/__tests__/findExportedReactCreateClassCall-test.js create mode 100644 website/react-docgen/lib/strategies/findAllReactCreateClassCalls.js create mode 100644 website/react-docgen/lib/strategies/findExportedReactCreateClassCall.js create mode 100644 website/react-docgen/lib/utils/__tests__/isExportsOrModuleAssignment-test.js create mode 100644 website/react-docgen/lib/utils/isExportsOrModuleAssignment.js create mode 100644 website/react-docgen/lib/utils/isReactCreateClassCall.js diff --git a/website/react-docgen/lib/ReactDocumentationParser.js b/website/react-docgen/lib/ReactDocumentationParser.js index 8e73d47d7..49914d60f 100644 --- a/website/react-docgen/lib/ReactDocumentationParser.js +++ b/website/react-docgen/lib/ReactDocumentationParser.js @@ -13,201 +13,112 @@ */ "use strict"; -/** - * How this parser works: - * - * 1. For each given file path do: - * - * a. Find component definition - * -. Find the rvalue module.exports assignment. - * Otherwise inspect assignments to exports. If there are multiple - * components that are exported, we don't continue with parsing the file. - * -. If the previous step results in a variable name, resolve it. - * -. Extract the object literal from the React.createClass call. - * - * b. Execute definition handlers (handlers working with the object - * expression). - * - * c. For each property of the definition object, execute the registered - * callbacks, if they are eligible for this property. - * - * 2. Return the aggregated results - */ - type Handler = (documentation: Documentation, path: NodePath) => void; var Documentation = require('./Documentation'); -var expressionTo = require('./utils/expressionTo'); +var findExportedReactCreateClass = + require('./strategies/findExportedReactCreateClassCall'); var getPropertyName = require('./utils/getPropertyName'); -var isReactModuleName = require('./utils/isReactModuleName'); -var match = require('./utils/match'); -var resolveToValue = require('./utils/resolveToValue'); -var resolveToModule = require('./utils/resolveToModule'); var recast = require('recast'); +var resolveToValue = require('./utils/resolveToValue'); var n = recast.types.namedTypes; -function ignore() { - return false; -} - -/** - * Returns true if the statement is of form `foo = bar;`. - * - * @param {object} node - * @return {bool} - */ -function isAssignmentStatement(node) { - return match(node, {expression: {operator: '='}}); -} - -/** - * Returns true if the expression is of form `exports.foo = bar;` or - * `modules.exports = foo;`. - * - * @param {object} node - * @return {bool} - */ -function isExportsOrModuleExpression(path) { - if (!n.AssignmentExpression.check(path.node) || - !n.MemberExpression.check(path.node.left)) { - return false; - } - var exprArr = expressionTo.Array(path.get('left')); - return (exprArr[0] === 'module' && exprArr[1] === 'exports') || - exprArr[0] == 'exports'; -} - -/** - * Returns true if the expression is a function call of the form - * `React.createClass(...)`. - * - * @param {object} node - * @param {array} scopeChain - * @return {bool} - */ -function isReactCreateClassCall(path) { - if (!match(path.node, {callee: {property: {name: 'createClass'}}})) { - return false; - } - var module = resolveToModule(path.get('callee', 'object')); - return module && isReactModuleName(module); -} - -/** - * Given an AST, this function tries to find the object expression that is - * passed to `React.createClass`, by resolving all references properly. - * - * @param {object} ast - * @return {?object} - */ -function findComponentDefinition(ast) { - var definition; - - recast.visit(ast, { - visitFunctionDeclaration: ignore, - visitFunctionExpression: ignore, - visitIfStatement: ignore, - visitWithStatement: ignore, - visitSwitchStatement: ignore, - visitTryStatement: ignore, - visitWhileStatement: ignore, - visitDoWhileStatement: ignore, - visitForStatement: ignore, - visitForInStatement: ignore, - visitAssignmentExpression: function(path) { - // Ignore anything that is not `exports.X = ...;` or - // `module.exports = ...;` - if (!isExportsOrModuleExpression(path)) { - return false; - } - // Resolve the value of the right hand side. It should resolve to a call - // expression, something like React.createClass - path = resolveToValue(path.get('right')); - if (!isReactCreateClassCall(path)) { - return false; - } - if (definition) { // If a file exports multiple components, ... complain! - throw new Error(ReactDocumentationParser.ERROR_MULTIPLE_DEFINITIONS); - } - // We found React.createClass. Lets get cracking! - definition = resolveToValue(path.get('arguments', 0)); - return false; - } - }); - - return definition; -} - - class ReactDocumentationParser { _componentHandlers: Array; - _propertyHandlers: Object; + _apiHandlers: Object; constructor() { this._componentHandlers = []; - this._propertyHandlers = Object.create(null); + this._apiHandlers = Object.create(null); } /** - * Handlers extract information from the component definition. + * Handlers to extract information from the component definition. * * If "property" is not provided, the handler is passed the whole component * definition. + * + * NOTE: The component definition is currently expected to be represented as + * an ObjectExpression (an object literal). This will likely change in the + * future. */ addHandler(handler: Handler, property?: string): void { if (!property) { this._componentHandlers.push(handler); } else { - if (!this._propertyHandlers[property]) { - this._propertyHandlers[property] = []; + if (!this._apiHandlers[property]) { + this._apiHandlers[property] = []; } - this._propertyHandlers[property].push(handler); + this._apiHandlers[property].push(handler); } } /** * Takes JavaScript source code and returns an object with the information * extract from it. + * + * The second argument is strategy to find the AST node(s) of the component + * definition(s) inside `source`. + * It is a function that gets passed the program AST node of + * the source as first argument, and a reference to recast as second argument. + * + * This allows you define your own strategy for finding component definitions. + * By default it will look for the exported component created by + * React.createClass. An error is thrown if multiple components are exported. + * + * NOTE: The component definition is currently expected to be represented as + * an ObjectExpression (an object literal), no matter which strategy is + * chosen. This will likely change in the future. */ - parseSource(source: string): Object { - var documentation = new Documentation(); + parseSource( + source: string, + componentDefinitionStrategy?: + (program: ASTNode, recast: Object) => (Array|NodePath) + ): (Array|Object) { + if (!componentDefinitionStrategy) { + componentDefinitionStrategy = findExportedReactCreateClass; + } var ast = recast.parse(source); - // Find the component definition first. The return value should be + // Find the component definitions first. The return value should be // an ObjectExpression. - var componentDefinition = findComponentDefinition(ast.program); - if (!componentDefinition) { + var componentDefinition = componentDefinitionStrategy(ast.program, recast); + var isArray = Array.isArray(componentDefinition); + if (!componentDefinition || (isArray && componentDefinition.length === 0)) { throw new Error(ReactDocumentationParser.ERROR_MISSING_DEFINITION); } - // Execute all the handlers to extract the information - this._executeHandlers(documentation, componentDefinition); - - return documentation.toObject(); + return isArray ? + this._executeHandlers(componentDefinition).map( + documentation => documentation.toObject() + ) : + this._executeHandlers([componentDefinition])[0].toObject(); } - _executeHandlers(documentation, componentDefinition: NodePath) { - componentDefinition.get('properties').each(propertyPath => { - var name = getPropertyName(propertyPath); - if (!this._propertyHandlers[name]) { - return; - } - var propertyValuePath = propertyPath.get('value'); - this._propertyHandlers[name].forEach( - handler => handler(documentation, propertyValuePath) + _executeHandlers(componentDefinitions: Array): Array { + return componentDefinitions.map(componentDefinition => { + var documentation = new Documentation(); + componentDefinition.get('properties').each(propertyPath => { + var name = getPropertyName(propertyPath); + if (!this._apiHandlers[name]) { + return; + } + var propertyValuePath = propertyPath.get('value'); + this._apiHandlers[name].forEach( + handler => handler(documentation, propertyValuePath) + ); + }); + + this._componentHandlers.forEach( + handler => handler(documentation, componentDefinition) ); + return documentation; }); - - this._componentHandlers.forEach( - handler => handler(documentation, componentDefinition) - ); } + } ReactDocumentationParser.ERROR_MISSING_DEFINITION = 'No suitable component definition found.'; -ReactDocumentationParser.ERROR_MULTIPLE_DEFINITIONS = - 'Multiple exported component definitions found.'; - module.exports = ReactDocumentationParser; diff --git a/website/react-docgen/lib/__tests__/ReactDocumentationParser-test.js b/website/react-docgen/lib/__tests__/ReactDocumentationParser-test.js index 52ede4145..f0a865d1c 100644 --- a/website/react-docgen/lib/__tests__/ReactDocumentationParser-test.js +++ b/website/react-docgen/lib/__tests__/ReactDocumentationParser-test.js @@ -10,109 +10,52 @@ "use strict"; -require('mock-modules').autoMockOff(); +jest.autoMockOff(); describe('React documentation parser', function() { var ReactDocumentationParser; var parser; + var recast; beforeEach(function() { + recast = require('recast'); ReactDocumentationParser = require('../ReactDocumentationParser'); parser = new ReactDocumentationParser(); }); - it('errors if component definition is not found', function() { - var source = 'var React = require("React");'; + function pathFromSource(source) { + return new recast.types.NodePath( + recast.parse(source).program.body[0].expression + ); + } + + describe('parseSource', function() { + + it('allows custom component definition resolvers', function() { + var path = pathFromSource('({foo: "bar"})'); + var resolver = jest.genMockFunction().mockReturnValue(path); + var handler = jest.genMockFunction(); + parser.addHandler(handler); + parser.parseSource('', resolver); + + expect(resolver).toBeCalled(); + expect(handler.mock.calls[0][1]).toBe(path); + }); + + it('errors if component definition is not found', function() { + var handler = jest.genMockFunction(); + expect(function() { + parser.parseSource('', handler); + }).toThrow(ReactDocumentationParser.ERROR_MISSING_DEFINITION); + expect(handler).toBeCalled(); + + handler = jest.genMockFunction().mockReturnValue([]); + expect(function() { + parser.parseSource('', handler); + }).toThrow(ReactDocumentationParser.ERROR_MISSING_DEFINITION); + expect(handler).toBeCalled(); + }); - expect(function() { - parser.parseSource(source); - }).toThrow(ReactDocumentationParser.ERROR_MISSING_DEFINITION); }); - it('finds React.createClass', function() { - var source = [ - 'var React = require("React");', - 'var Component = React.createClass({});', - 'module.exports = Component;' - ].join('\n'); - - expect(function() { - parser.parseSource(source); - }).not.toThrow(); - }); - - it('finds React.createClass, independent of the var name', function() { - var source = [ - 'var R = require("React");', - 'var Component = R.createClass({});', - 'module.exports = Component;' - ].join('\n'); - - expect(function() { - parser.parseSource(source); - }).not.toThrow(); - }); - - it('does not process X.createClass of other modules', function() { - var source = [ - 'var R = require("NoReact");', - 'var Component = R.createClass({});', - 'module.exports = Component;' - ].join('\n'); - - expect(function() { - parser.parseSource(source); - }).toThrow(ReactDocumentationParser.ERROR_MISSING_DEFINITION); - }); - - it('finds assignments to exports', function() { - var source = [ - 'var R = require("React");', - 'var Component = R.createClass({});', - 'exports.foo = 42;', - 'exports.Component = Component;' - ].join('\n'); - - expect(function() { - parser.parseSource(source); - }).not.toThrow(); - }); - - it('errors if multiple components are exported', function() { - var source = [ - 'var R = require("React");', - 'var ComponentA = R.createClass({});', - 'var ComponentB = R.createClass({});', - 'exports.ComponentA = ComponentA;', - 'exports.ComponentB = ComponentB;' - ].join('\n'); - - expect(function() { - parser.parseSource(source); - }).toThrow(ReactDocumentationParser.ERROR_MULTIPLE_DEFINITIONS); - }); - - it('accepts multiple definitions if only one is exported', function() { - var source = [ - 'var R = require("React");', - 'var ComponentA = R.createClass({});', - 'var ComponentB = R.createClass({});', - 'exports.ComponentB = ComponentB;' - ].join('\n'); - - expect(function() { - parser.parseSource(source); - }).not.toThrow(); - - source = [ - 'var R = require("React");', - 'var ComponentA = R.createClass({});', - 'var ComponentB = R.createClass({});', - 'module.exports = ComponentB;' - ].join('\n'); - - expect(function() { - parser.parseSource(source); - }).not.toThrow(); - }); }); diff --git a/website/react-docgen/lib/strategies/__tests__/findAllReactCreateClassCalls-test.js b/website/react-docgen/lib/strategies/__tests__/findAllReactCreateClassCalls-test.js new file mode 100644 index 000000000..a78e6a77d --- /dev/null +++ b/website/react-docgen/lib/strategies/__tests__/findAllReactCreateClassCalls-test.js @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +"use strict"; + +jest.autoMockOff(); + +describe('React documentation parser', function() { + var findAllReactCreateClassCalls; + var recast; + + function parse(source) { + return findAllReactCreateClassCalls( + recast.parse(source).program, + recast + ); + } + + beforeEach(function() { + findAllReactCreateClassCalls = require('../findAllReactCreateClassCalls'); + recast = require('recast'); + }); + + + it('finds React.createClass', function() { + var source = [ + 'var React = require("React");', + 'var Component = React.createClass({});', + 'module.exports = Component;' + ].join('\n'); + + var result = parse(source); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBe(1); + expect(result[0] instanceof recast.types.NodePath).toBe(true); + expect(result[0].node.type).toBe('ObjectExpression'); + }); + + it('finds React.createClass, independent of the var name', function() { + var source = [ + 'var R = require("React");', + 'var Component = R.createClass({});', + 'module.exports = Component;' + ].join('\n'); + + var result = parse(source); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBe(1); + }); + + it('does not process X.createClass of other modules', function() { + var source = [ + 'var R = require("NoReact");', + 'var Component = R.createClass({});', + 'module.exports = Component;' + ].join('\n'); + + var result = parse(source); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBe(0); + }); + + it('finds assignments to exports', function() { + var source = [ + 'var R = require("React");', + 'var Component = R.createClass({});', + 'exports.foo = 42;', + 'exports.Component = Component;' + ].join('\n'); + + var result = parse(source); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBe(1); + }); + + it('accepts multiple definitions', function() { + var source = [ + 'var R = require("React");', + 'var ComponentA = R.createClass({});', + 'var ComponentB = R.createClass({});', + 'exports.ComponentB = ComponentB;' + ].join('\n'); + + var result = parse(source); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBe(2); + + source = [ + 'var R = require("React");', + 'var ComponentA = R.createClass({});', + 'var ComponentB = R.createClass({});', + 'module.exports = ComponentB;' + ].join('\n'); + + result = parse(source); + expect(Array.isArray(result)).toBe(true); + expect(result.length).toBe(2); + }); +}); diff --git a/website/react-docgen/lib/strategies/__tests__/findExportedReactCreateClassCall-test.js b/website/react-docgen/lib/strategies/__tests__/findExportedReactCreateClassCall-test.js new file mode 100644 index 000000000..b0e4cba92 --- /dev/null +++ b/website/react-docgen/lib/strategies/__tests__/findExportedReactCreateClassCall-test.js @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +"use strict"; + +jest.autoMockOff(); + +describe('React documentation parser', function() { + var findExportedReactCreateClass; + var recast; + + function parse(source) { + return findExportedReactCreateClass( + recast.parse(source).program, + recast + ); + } + + beforeEach(function() { + findExportedReactCreateClass = + require('../findExportedReactCreateClassCall'); + recast = require('recast'); + }); + + it('finds React.createClass', function() { + var source = [ + 'var React = require("React");', + 'var Component = React.createClass({});', + 'module.exports = Component;' + ].join('\n'); + + expect(parse(source)).toBeDefined(); + }); + + it('finds React.createClass, independent of the var name', function() { + var source = [ + 'var R = require("React");', + 'var Component = R.createClass({});', + 'module.exports = Component;' + ].join('\n'); + + expect(parse(source)).toBeDefined(); + }); + + it('does not process X.createClass of other modules', function() { + var source = [ + 'var R = require("NoReact");', + 'var Component = R.createClass({});', + 'module.exports = Component;' + ].join('\n'); + + expect(parse(source)).toBeUndefined(); + }); + + it('finds assignments to exports', function() { + var source = [ + 'var R = require("React");', + 'var Component = R.createClass({});', + 'exports.foo = 42;', + 'exports.Component = Component;' + ].join('\n'); + + expect(parse(source)).toBeDefined(); + }); + + it('errors if multiple components are exported', function() { + var source = [ + 'var R = require("React");', + 'var ComponentA = R.createClass({});', + 'var ComponentB = R.createClass({});', + 'exports.ComponentA = ComponentA;', + 'exports.ComponentB = ComponentB;' + ].join('\n'); + + expect(function() { + parse(source) + }).toThrow(); + }); + + it('accepts multiple definitions if only one is exported', function() { + var source = [ + 'var R = require("React");', + 'var ComponentA = R.createClass({});', + 'var ComponentB = R.createClass({});', + 'exports.ComponentB = ComponentB;' + ].join('\n'); + + expect(parse(source)).toBeDefined(); + + source = [ + 'var R = require("React");', + 'var ComponentA = R.createClass({});', + 'var ComponentB = R.createClass({});', + 'module.exports = ComponentB;' + ].join('\n'); + + expect(parse(source)).toBeDefined(); + }); +}); diff --git a/website/react-docgen/lib/strategies/findAllReactCreateClassCalls.js b/website/react-docgen/lib/strategies/findAllReactCreateClassCalls.js new file mode 100644 index 000000000..7971a1d26 --- /dev/null +++ b/website/react-docgen/lib/strategies/findAllReactCreateClassCalls.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +/** + * @flow + */ +"use strict"; + +var isReactCreateClassCall = require('../utils/isReactCreateClassCall'); +var resolveToValue = require('../utils/resolveToValue'); + +/** + * Given an AST, this function tries to find all object expressions that are + * passed to `React.createClass` calls, by resolving all references properly. + */ +function findAllReactCreateClassCalls( + ast: ASTNode, + recast: Object +): Array { + var types = recast.types.namedTypes; + var definitions = []; + + recast.visit(ast, { + visitCallExpression: function(path) { + if (!isReactCreateClassCall(path)) { + return false; + } + // We found React.createClass. Lets get cracking! + var resolvedPath = resolveToValue(path.get('arguments', 0)); + if (types.ObjectExpression.check(resolvedPath.node)) { + definitions.push(resolvedPath); + } + return false; + } + }); + + return definitions; +} + +module.exports = findAllReactCreateClassCalls; diff --git a/website/react-docgen/lib/strategies/findExportedReactCreateClassCall.js b/website/react-docgen/lib/strategies/findExportedReactCreateClassCall.js new file mode 100644 index 000000000..ccb8a3907 --- /dev/null +++ b/website/react-docgen/lib/strategies/findExportedReactCreateClassCall.js @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +/** + * @flow + */ +"use strict"; + +var isExportsOrModuleAssignment = + require('../utils/isExportsOrModuleAssignment'); +var isReactCreateClassCall = require('../utils/isReactCreateClassCall'); +var resolveToValue = require('../utils/resolveToValue'); + +var ERROR_MULTIPLE_DEFINITIONS = + 'Multiple exported component definitions found.'; + +function ignore() { + return false; +} + +/** + * Given an AST, this function tries to find the object expression that is + * passed to `React.createClass`, by resolving all references properly. + */ +function findExportedReactCreateClass( + ast: ASTNode, + recast: Object +): ?NodePath { + var types = recast.types.namedTypes; + var definition; + + recast.visit(ast, { + visitFunctionDeclaration: ignore, + visitFunctionExpression: ignore, + visitIfStatement: ignore, + visitWithStatement: ignore, + visitSwitchStatement: ignore, + visitCatchCause: ignore, + visitWhileStatement: ignore, + visitDoWhileStatement: ignore, + visitForStatement: ignore, + visitForInStatement: ignore, + visitAssignmentExpression: function(path) { + // Ignore anything that is not `exports.X = ...;` or + // `module.exports = ...;` + if (!isExportsOrModuleAssignment(path)) { + return false; + } + // Resolve the value of the right hand side. It should resolve to a call + // expression, something like React.createClass + path = resolveToValue(path.get('right')); + if (!isReactCreateClassCall(path)) { + return false; + } + if (definition) { + // If a file exports multiple components, ... complain! + throw new Error(ERROR_MULTIPLE_DEFINITIONS); + } + // We found React.createClass. Lets get cracking! + var resolvedPath = resolveToValue(path.get('arguments', 0)); + if (types.ObjectExpression.check(resolvedPath.node)) { + definition = resolvedPath; + } + return false; + } + }); + + return definition; +} + +module.exports = findExportedReactCreateClass; diff --git a/website/react-docgen/lib/utils/__tests__/isExportsOrModuleAssignment-test.js b/website/react-docgen/lib/utils/__tests__/isExportsOrModuleAssignment-test.js new file mode 100644 index 000000000..11c049c38 --- /dev/null +++ b/website/react-docgen/lib/utils/__tests__/isExportsOrModuleAssignment-test.js @@ -0,0 +1,36 @@ +"use strict"; + +jest.autoMockOff(); + +describe('isExportsOrModuleAssignment', function() { + var recast; + var isExportsOrModuleAssignment; + + function parse(src) { + return new recast.types.NodePath( + recast.parse(src).program.body[0] + ); + } + + beforeEach(function() { + isExportsOrModuleAssignment = require('../isExportsOrModuleAssignment'); + recast = require('recast'); + }); + + it('detects "module.exports = ...;"', function() { + expect(isExportsOrModuleAssignment(parse('module.exports = foo;'))) + .toBe(true); + }); + + it('detects "exports.foo = ..."', function() { + expect(isExportsOrModuleAssignment(parse('exports.foo = foo;'))) + .toBe(true); + }); + + it('does not accept "exports = foo;"', function() { + // That doesn't actually export anything + expect(isExportsOrModuleAssignment(parse('exports = foo;'))) + .toBe(false); + }); + +}); diff --git a/website/react-docgen/lib/utils/isExportsOrModuleAssignment.js b/website/react-docgen/lib/utils/isExportsOrModuleAssignment.js new file mode 100644 index 000000000..f6d7f1a9c --- /dev/null +++ b/website/react-docgen/lib/utils/isExportsOrModuleAssignment.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +/** + * @flow + */ +"use strict"; + +var expressionTo = require('./expressionTo'); +var types = require('recast').types.namedTypes; + +/** + * Returns true if the expression is of form `exports.foo = ...;` or + * `modules.exports = ...;`. + */ +function isExportsOrModuleAssignment(path: NodePath): boolean { + if (types.ExpressionStatement.check(path.node)) { + path = path.get('expression'); + } + if (!types.AssignmentExpression.check(path.node) || + !types.MemberExpression.check(path.node.left)) { + return false; + } + + var exprArr = expressionTo.Array(path.get('left')); + return (exprArr[0] === 'module' && exprArr[1] === 'exports') || + exprArr[0] == 'exports'; +} + +module.exports = isExportsOrModuleAssignment; diff --git a/website/react-docgen/lib/utils/isReactCreateClassCall.js b/website/react-docgen/lib/utils/isReactCreateClassCall.js new file mode 100644 index 000000000..5231f3da8 --- /dev/null +++ b/website/react-docgen/lib/utils/isReactCreateClassCall.js @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2015, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + */ + +/** + * @flow + */ +"use strict"; + +var isReactModuleName = require('./isReactModuleName'); +var match = require('./match'); +var resolveToModule = require('./resolveToModule'); +var types = require('recast').types.namedTypes; + +/** + * Returns true if the expression is a function call of the form + * `React.createClass(...)`. + */ +function isReactCreateClassCall(path: NodePath): boolean { + if (types.ExpressionStatement.check(path.node)) { + path = path.get('expression'); + } + + if (!match(path.node, {callee: {property: {name: 'createClass'}}})) { + return false; + } + var module = resolveToModule(path.get('callee', 'object')); + return module && isReactModuleName(module); +} + +module.exports = isReactCreateClassCall; diff --git a/website/react-docgen/package.json b/website/react-docgen/package.json index 9974df250..c0f85e0f7 100644 --- a/website/react-docgen/package.json +++ b/website/react-docgen/package.json @@ -25,7 +25,7 @@ "recast": "^0.9.17" }, "devDependencies": { - "jest-cli": "^0.2.2", + "jest-cli": "^0.3.0", "react-tools": "^0.12.2" }, "jest": { diff --git a/website/server/convert.js b/website/server/convert.js index de04eb948..1a02efca7 100644 --- a/website/server/convert.js +++ b/website/server/convert.js @@ -14,7 +14,8 @@ function splitHeader(content) { } } return { - header: lines.slice(1, i + 1).join('\n'), + header: i < lines.length - 1 ? + lines.slice(1, i + 1).join('\n') : null, content: lines.slice(i + 1).join('\n') }; } @@ -47,6 +48,9 @@ function execute() { // Extract markdown metadata header var both = splitHeader(content); + if (!both.header) { + return; + } var lines = both.header.split('\n'); for (var i = 0; i < lines.length - 1; ++i) { var keyvalue = lines[i].split(':'); diff --git a/website/server/extractDocs.js b/website/server/extractDocs.js index 9b7c0609a..d07395a39 100644 --- a/website/server/extractDocs.js +++ b/website/server/extractDocs.js @@ -1,4 +1,10 @@ var docs = require('../react-docgen'); +var findExportedReactCreateClassCall = require( + '../react-docgen/dist/strategies/findExportedReactCreateClassCall' +); +var findAllReactCreateClassCalls = require( + '../react-docgen/dist/strategies/findAllReactCreateClassCalls' +); var fs = require('fs'); var path = require('path'); var slugify = require('../core/slugify'); @@ -12,7 +18,14 @@ function getNameFromPath(filepath) { } function docsToMarkdown(filepath, i) { - var json = docs.parseSource(fs.readFileSync(filepath)); + var json = docs.parseSource( + fs.readFileSync(filepath), + function(node, recast) { + return findExportedReactCreateClassCall(node, recast) || + findAllReactCreateClassCalls(node, recast)[0]; + } + ) + var componentName = getNameFromPath(filepath); var res = [ @@ -45,7 +58,7 @@ var components = [ '../Libraries/Components/TextInput/TextInput.ios.js', '../Libraries/Components/Touchable/TouchableHighlight.js', '../Libraries/Components/Touchable/TouchableWithoutFeedback.js', -// '../Libraries/Components/View/View.js', + '../Libraries/Components/View/View.js', ]; module.exports = function() {