From f244cb158fbf28672aa658eca52855e89d872eaa Mon Sep 17 00:00:00 2001 From: Charles Lehnert Date: Fri, 1 Jun 2018 19:18:59 -0400 Subject: [PATCH 1/7] Remove redundancy with react-docgen. Output docs under actual component name--not a different displayName. Output component docs based on react-docgen resolvers (multiple components supported). --- package.json | 8 +- src/actualNameHandler.js | 62 ++++++++++++ src/index.js | 182 ++++------------------------------- src/isReactComponentClass.js | 35 ------- src/isStatelessComponent.js | 102 -------------------- test/index.js | 49 +++++----- 6 files changed, 110 insertions(+), 328 deletions(-) create mode 100644 src/actualNameHandler.js delete mode 100644 src/isReactComponentClass.js delete mode 100644 src/isStatelessComponent.js diff --git a/package.json b/package.json index 8d29ffc..5287c1b 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,9 @@ "description": "Babel plugin to add react-docgen info into your code", "repository": "https://github.com/storybooks/babel-plugin-react-docgen", "author": "Madushan Nishantha ", + "contributors": [ + "Charles Lehnert" + ], "main": "lib/index.js", "devDependencies": { "babel-cli": "^6.26.0", @@ -11,12 +14,13 @@ "babel-preset-flow": "^6.23.0", "babel-preset-react": "^6.24.1", "babel-preset-stage-0": "^6.24.1", - "mocha": "^4.1.0" + "mocha": "^4.1.0", + "recast": "^0.14.7" }, "scripts": { "clean": "rm -rf lib", "build": "babel src -d lib", - "test": "mocha --compilers js:babel-register", + "test": "mocha --require babel-register", "test:watch": "npm run test -- --watch", "prepare": "npm run test && npm run clean && npm run build" }, diff --git a/src/actualNameHandler.js b/src/actualNameHandler.js new file mode 100644 index 0000000..ab1c891 --- /dev/null +++ b/src/actualNameHandler.js @@ -0,0 +1,62 @@ +/* + * 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. + * + * + * 2018 - Charles Lehnert + * This is heavily based on the react-docgen `displayNameHandler` but instead defines an `actualName` property on the + * generated docs that is taken first from the component's actual name. This addresses an issue where the name that + * the generated docs are stored under is incorrectly named with the `displayName` and not the component's actual name. + * + */ + +import { utils } from 'react-docgen'; +import recast from 'recast'; + +const { getMemberValuePath, getNameOrValue, resolveFunctionDefinitionToReturnValue, resolveToValue } = utils; +const {types: {namedTypes: types}} = recast; + +export default function actualNameHandler(documentation, path) { + // Function and class declarations need special treatment. The name of the + // function / class is the displayName + if ( + types.ClassDeclaration.check(path.node) || + types.FunctionDeclaration.check(path.node) + ) { + documentation.set('actualName', getNameOrValue(path.get('id'))); + } else if ( + types.ArrowFunctionExpression.check(path.node) || + types.FunctionExpression.check(path.node) + ) { + if (types.VariableDeclarator.check(path.parentPath.node)) { + documentation.set('actualName', getNameOrValue(path.parentPath.get('id'))); + } else if (types.AssignmentExpression.check(path.parentPath.node)) { + documentation.set('actualName', getNameOrValue(path.parentPath.get('left'))); + } + } else if ( + // React.createClass() or createReactClass() + types.CallExpression.check(path.parentPath.node) && + types.VariableDeclarator.check(path.parentPath.parentPath.parentPath.node) + ) { + documentation.set('actualName', getNameOrValue(path.parentPath.parentPath.parentPath.get('id'))); + } else { + // Could not find an actual name + documentation.set('actualName', ''); + } + return; + + // If display name is defined as a getter we get a function expression as + // value. In that case we try to determine the value from the return + // statement. + if (types.FunctionExpression.check(displayNamePath.node)) { + displayNamePath = resolveFunctionDefinitionToReturnValue(displayNamePath); + } + if (!displayNamePath || !types.Literal.check(displayNamePath.node)) { + return; + } + documentation.set('actualName', displayNamePath.node.value); +} diff --git a/src/index.js b/src/index.js index f6b0894..892ec67 100644 --- a/src/index.js +++ b/src/index.js @@ -1,170 +1,34 @@ +import * as Path from 'path'; import * as _ from 'lodash'; import * as ReactDocgen from 'react-docgen'; -import isReactComponentClass from './isReactComponentClass'; -import isStatelessComponent from './isStatelessComponent'; -import * as Path from 'path'; +import * as reactDocgenHandlers from 'react-docgen/dist/handlers'; +import actualNameHandler from './actualNameHandler'; -export default function ({types: t}) { +const defaultHandlers = Object.values(reactDocgenHandlers).map(handler => handler); +const handlers = [...defaultHandlers, actualNameHandler] + +export default function({ types: t }) { return { visitor: { - Class(path, state) { - if(!isReactComponentClass(path)) { - return; - } - if(!path.node.id){ - return; + Program: { + exit(path, state) { + injectReactDocgenInfo(path, state, this.file.code, t); } - const className = path.node.id.name; - - if(!isExported(path, className, t)){ - return; - } - injectReactDocgenInfo(className, path, state, this.file.code, t); - }, - 'CallExpression'(path, state) { - const callee = path.node.callee; - - const objectName = _.get(callee, 'object.name') ? callee.object.name.toLowerCase() : null; - const propertyName = _.get(callee, 'property.name') ? callee.property.name.toLowerCase() : null; - const calleeName = _.get(callee, 'name') ? callee.name.toLowerCase() : null; - - // Detect `React.createClass()` - const hasReactCreateClass = (objectName === 'react' && propertyName === 'createclass'); - - // Detect `createReactClass()` - const hasCreateReactClass = (calleeName === 'createreactclass'); - - // Get React class name from variable declaration - const className = _.get(path, 'parentPath.parent.declarations[0].id.name'); - - // Detect `React.createElement()` - const hasReactCreateElement = (objectName === 'react' && propertyName === 'createelement'); - - if (className && (hasReactCreateClass || hasCreateReactClass)) { - injectReactDocgenInfo(className, path, state, this.file.code, t); - } - - if (hasReactCreateElement) { - const variableDeclaration = path.findParent((path) => path.isVariableDeclaration()); - - if (variableDeclaration) { - const elementClassName = variableDeclaration.node.declarations[0].id.name; - if (!isExported(path, elementClassName, t)) { - return; - } - - injectReactDocgenInfo(elementClassName, path, state, this.file.code, t); - } - } - }, - 'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression'(path, state) { - if (!isStatelessComponent(path)) { - return; - } - - const node = (path.node.type === 'FunctionDeclaration') ? path.node : path.parentPath.node; - - if (!node.id) { - return; - } - const className = node.id.name; - - if (!isExported(path, className, t)) { - return; - } - injectReactDocgenInfo(className, path, state, this.file.code, t); - }, - } - }; -} - -function isExported(path, className, t){ - const types = [ - 'ExportDefaultDeclaration', - 'ExportNamedDeclaration' - ]; - - function findMostRightHandArgument(args = []) { - const arg = args[0] - if (t.isIdentifier(arg)) { - return arg.name - } else if(t.isCallExpression(arg)) { - return findMostRightHandArgument(arg.arguments) - } - } - - if(path.parentPath.node && - types.some(type => {return path.parentPath.node.type === type;})) { - return true; - } - - const program = path.scope.getProgramParent().path; - return program.get('body').some(path => { - if(path.node.type === 'ExportNamedDeclaration') { - if (path.node.specifiers && path.node.specifiers.length) { - return className === path.node.specifiers[0].exported.name; - } else if (path.node.declaration.declarations && path.node.declaration.declarations.length) { - return className === path.node.declaration.declarations[0].id.name; - } - } else if(path.node.type === 'ExportDefaultDeclaration') { - const decl = path.node.declaration - if (t.isCallExpression(decl)) { - return className === findMostRightHandArgument(decl.arguments); - } else { - return className === decl.name; } - // Detect module.exports = className; - } else if(path.node.type === 'ExpressionStatement') { - const expr = path.node.expression - - if (t.isAssignmentExpression(expr)) { - const left = expr.left; - const right = expr.right; - - const leftIsModuleExports = t.isMemberExpression(left) && - t.isIdentifier(left.object) && - t.isIdentifier(left.property) && - left.object.name === 'module' && - left.property.name === 'exports'; - - const rightIsIdentifierClass = t.isIdentifier(right) && right.name === className; - - return leftIsModuleExports && rightIsIdentifierClass; - } - } - return false; - }); -} - -function alreadyVisited(program, t) { - return program.node.body.some(node => { - if(t.isExpressionStatement(node) && - t.isAssignmentExpression(node.expression) && - t.isMemberExpression(node.expression.left) - ) { - return node.expression.left.property.name === '__docgenInfo'; - } - return false; - }); + }, + }; } - -function injectReactDocgenInfo(className, path, state, code, t) { +function injectReactDocgenInfo(path, state, code, t) { const program = path.scope.getProgramParent().path; - if(alreadyVisited(program, t)) { - return; - } - let docgenResults = []; - try { // all exported component definitions includes named exports + try { let resolver = ReactDocgen.resolver.findAllExportedComponentDefinitions; - if (state.opts.resolver) { resolver = ReactDocgen.resolver[state.opts.resolver]; } - - docgenResults = ReactDocgen.parse(code, resolver); + docgenResults = ReactDocgen.parse(code, resolver, handlers); if (state.opts.removeMethods) { docgenResults.forEach(function(docgenResult) { @@ -177,21 +41,8 @@ function injectReactDocgenInfo(className, path, state, code, t) { return; } - // docgen sometimes doesn't include 'displayName' which is the react function/class name - // the first time it's not available, we try to match it to the export name - let isDefaultClassNameUsed = false; - docgenResults.forEach(function(docgenResult, index) { - if (isDefaultClassNameUsed && !docgenResult.displayName) { - return; - } - - let exportName = docgenResult.displayName; - if (!exportName) { - exportName = className; - isDefaultClassNameUsed = true; - } - + let exportName = docgenResult.actualName; const docNode = buildObjectExpression(docgenResult, t); const docgenInfo = t.expressionStatement( t.assignmentExpression( @@ -260,6 +111,7 @@ function buildObjectExpression(obj, t){ if(_.isPlainObject(obj)) { const children = []; for (let key in obj) { + if (key === 'actualName') continue; if(!obj.hasOwnProperty(key) || _.isUndefined(obj[key])) continue; children.push( t.objectProperty( diff --git a/src/isReactComponentClass.js b/src/isReactComponentClass.js deleted file mode 100644 index 05424d9..0000000 --- a/src/isReactComponentClass.js +++ /dev/null @@ -1,35 +0,0 @@ -import * as BabelTypes from 'babel-types'; - -function isRenderMethod(node) { - return BabelTypes.isClassMethod(node) && - !node.computed && - !node.static && - (node.kind === '' || node.kind === 'method') && - node.key.name === 'render'; -} - -export default function isReactComponentClass(path) { - var node = path.node; - if (!BabelTypes.isClassDeclaration(node) && - !BabelTypes.isClassExpression(node)) { - return false; - } - - // render method - if (node.body.body.some(isRenderMethod)) { - return true; - } - - // extends ReactComponent? - if (!node.superClass) { - return false; - } - var superClass = path.get('superClass'); - if ( - (BabelTypes.isMemberExpression(superClass.node) && superClass.get('property').node.name != 'Component') - || superClass.node.name != 'Component' - ) { - return false; - } - return true; -} diff --git a/src/isStatelessComponent.js b/src/isStatelessComponent.js deleted file mode 100644 index 5dc553b..0000000 --- a/src/isStatelessComponent.js +++ /dev/null @@ -1,102 +0,0 @@ -// This is taken from the oliviertassinari/babel-plugin-transform-react-remove-prop-types -// See: https://git.io/vPHoh - -function isJSXElementOrReactCreateElement(node) { - const { - type, - callee, - } = node; - - if (type === 'JSXElement') { - return true; - } - - if (callee && callee.object && callee.object.name === 'React' && - callee.property.name === 'createElement') { - return true; - } - - return false; -} - -function isReturningJSXElement(path) { - /** - * Early exit for ArrowFunctionExpressions, there is no ReturnStatement node. - */ - if (path.node.init && path.node.init.body && isJSXElementOrReactCreateElement(path.node.init.body)) { - return true; - } - - let visited = false; - - /** - Looking for functions returning a JSX. - */ - path.traverse({ - ReturnStatement(path2) { - // We have already found what we are looking for. - if (visited) { - return; - } - - const argument = path2.get('argument'); - - // Nothing is returned - if (!argument.node) { - return; - } - - if (isJSXElementOrReactCreateElement(argument.node)) { - visited = true; - return; - } - - if (argument.node.type === 'CallExpression') { - const name = argument.get('callee').node.name; - const binding = path.scope.getBinding(name); - - // Here we do not check for any function calles except `_possibleConstructorReturn`. - // That's because we only need to check if the function returning JSX elements right away. - // We don't need to check any other function calls which generate JSX. - // TODO: between babel versions `_possibleConstructorReturn` name could get changed. - // So, we need to look at it a bit. - if (!binding || name != '_possibleConstructorReturn') { - return; - } - - if (isReturningJSXElement(binding.path)) { - visited = true; - } - } - }, - }); - - return visited; -} - -const validPossibleStatelessComponentTypes = [ - 'Property', - 'VariableDeclarator', - 'FunctionDeclaration', - 'ArrowFunctionExpression', -]; - -/** - * Returns `true` if the path represents a function which returns a JSXElement - */ -export default function isStatelessComponent(path) { - const node = path.node; - - if (validPossibleStatelessComponentTypes.indexOf(node.type) === -1) { - return false; - } - - if(path.get('body').get('type').node == 'JSXElement') { - return true; - } - if (isReturningJSXElement(path)) { - return true; - } - - return false; -} diff --git a/test/index.js b/test/index.js index 6bf0412..f4aa0fb 100644 --- a/test/index.js +++ b/test/index.js @@ -11,30 +11,31 @@ function trim(str) { describe('Add propType doc to react classes', () => { const fixturesDir = path.join(__dirname, 'fixtures'); fs.readdirSync(fixturesDir).map((caseName) => { - it(`should ${caseName.split('-').join(' ')}`, () => { - const fixtureDir = path.join(fixturesDir, caseName); - const actualPath = path.join(fixtureDir, 'actual.js'); - const options = { - presets: [ - "react", - "env", - "flow", - "stage-0" - ], - plugins: [ - [plugin, { - "DOC_GEN_COLLECTION_NAME": "STORYBOOK_REACT_CLASSES" - }] - ], - babelrc: false - }; + // Ignore macOS directory files + if (caseName.indexOf('.DS_Store') < 0) { + it(`should ${caseName.split('-').join(' ')}`, () => { + const fixtureDir = path.join(fixturesDir, caseName); + const actualPath = path.join(fixtureDir, 'source.js'); + const options = { + presets: [ + "react", + "env", + "flow", + "stage-0" + ], + plugins: [ + [plugin, { + "DOC_GEN_COLLECTION_NAME": "STORYBOOK_REACT_CLASSES" + }] + ], + babelrc: false + }; - const actual = transformFileSync(actualPath, options).code; - // fs.writeFileSync(path.join(fixtureDir, 'expected.js'), actual); - const expected = fs.readFileSync( - path.join(fixtureDir, 'expected.js') - ).toString(); - assert.equal(trim(actual), trim(expected)); - }); + const actual = transformFileSync(actualPath, options).code; + // fs.writeFileSync(path.join(fixtureDir, 'actual.js'), actual); + const expected = fs.readFileSync(path.join(fixtureDir, 'expected.js')).toString(); + assert.equal(trim(actual), trim(expected)); + }); + } }); }); From c951c114cfa4d2e491375920be3165f6e7ae4468 Mon Sep 17 00:00:00 2001 From: Charles Lehnert Date: Fri, 1 Jun 2018 19:23:04 -0400 Subject: [PATCH 2/7] Update tests' expected doc prop order to accomodate default order of react-docgen handlers. Ignore macos directory files when testing. --- test/fixtures/case1/expected.js | 98 +++++++++--------- test/fixtures/case2/expected.js | 2 +- test/fixtures/case3/expected.js | 22 ++--- test/fixtures/case4/expected.js | 22 ++--- test/fixtures/case8/expected.js | 2 +- test/fixtures/createReactClass/expected.js | 12 +-- test/fixtures/differentName/actual.js | 38 +++++++ test/fixtures/differentName/expected.js | 99 +++++++++++++++++++ .../example-module-exports/expected.js | 4 +- test/fixtures/example/expected.js | 4 +- test/fixtures/flowType/expected.js | 2 +- test/fixtures/functionDeclaration/expected.js | 2 +- test/fixtures/hoc-multiple/expected.js | 2 +- test/fixtures/hoc/expected.js | 2 +- test/fixtures/multiple-exports/expected.js | 4 +- test/fixtures/reactCreateClass/expected.js | 10 +- test/fixtures/reactCreateElement/expected.js | 18 ++-- test/index.js | 12 ++- 18 files changed, 247 insertions(+), 108 deletions(-) create mode 100644 test/fixtures/differentName/actual.js create mode 100644 test/fixtures/differentName/expected.js diff --git a/test/fixtures/case1/expected.js b/test/fixtures/case1/expected.js index 67b755b..0426be5 100644 --- a/test/fixtures/case1/expected.js +++ b/test/fixtures/case1/expected.js @@ -194,7 +194,6 @@ CalendarDay.propTypes = propTypes; CalendarDay.defaultProps = defaultProps; CalendarDay.__docgenInfo = { 'description': '', - 'displayName': 'CalendarDay', 'methods': [{ 'name': 'handleDayClick', 'docblock': 'Some description about how handleDayClick works\n@param {Object} day this is a moment js object\n@param {number|string} modifiers hello world\n@param {number=} e events yo\n@return {*} wut return', @@ -333,20 +332,25 @@ CalendarDay.__docgenInfo = { }], 'returns': null }], + 'displayName': 'CalendarDay', 'props': { 'day': { + 'defaultValue': { + 'value': 'moment()', + 'computed': true + }, 'type': { 'name': 'custom', 'raw': 'momentPropTypes.momentObj' }, 'required': false, - 'description': '', - 'defaultValue': { - 'value': 'moment()', - 'computed': true - } + 'description': '' }, 'modifiers': { + 'defaultValue': { + 'value': '[]', + 'computed': false + }, 'type': { 'name': 'arrayOf', 'value': { @@ -354,110 +358,106 @@ CalendarDay.__docgenInfo = { } }, 'required': false, - 'description': '', - 'defaultValue': { - 'value': '[]', - 'computed': false - } + 'description': '' }, 'onDayClick': { + 'defaultValue': { + 'value': 'function() {}', + 'computed': false + }, 'type': { 'name': 'func' }, 'required': false, - 'description': '', + 'description': '' + }, + 'onDayMouseDown': { 'defaultValue': { 'value': 'function() {}', 'computed': false - } - }, - 'onDayMouseDown': { + }, 'type': { 'name': 'func' }, 'required': false, - 'description': '', + 'description': '' + }, + 'onDayMouseUp': { 'defaultValue': { 'value': 'function() {}', 'computed': false - } - }, - 'onDayMouseUp': { + }, 'type': { 'name': 'func' }, 'required': false, - 'description': '', + 'description': '' + }, + 'onDayMouseEnter': { 'defaultValue': { 'value': 'function() {}', 'computed': false - } - }, - 'onDayMouseEnter': { + }, 'type': { 'name': 'func' }, 'required': false, - 'description': '', + 'description': '' + }, + 'onDayMouseLeave': { 'defaultValue': { 'value': 'function() {}', 'computed': false - } - }, - 'onDayMouseLeave': { + }, 'type': { 'name': 'func' }, 'required': false, - 'description': '', + 'description': '' + }, + 'onDayTouchStart': { 'defaultValue': { 'value': 'function() {}', 'computed': false - } - }, - 'onDayTouchStart': { + }, 'type': { 'name': 'func' }, 'required': false, - 'description': '', + 'description': '' + }, + 'onDayTouchEnd': { 'defaultValue': { 'value': 'function() {}', 'computed': false - } - }, - 'onDayTouchEnd': { + }, 'type': { 'name': 'func' }, 'required': false, - 'description': '', + 'description': '' + }, + 'onDayTouchTap': { 'defaultValue': { 'value': 'function() {}', 'computed': false - } - }, - 'onDayTouchTap': { + }, 'type': { 'name': 'func' }, 'required': false, - 'description': '', - 'defaultValue': { - 'value': 'function() {}', - 'computed': false - } + 'description': '' }, 'hypen-dash': { + 'defaultValue': { + 'value': '\'hello\'', + 'computed': false + }, 'type': { 'name': 'string' }, 'required': false, - 'description': '', - 'defaultValue': { - 'value': '\'hello\'', - 'computed': false - } + 'description': '' } } }; diff --git a/test/fixtures/case2/expected.js b/test/fixtures/case2/expected.js index 517af7c..53cc3a1 100644 --- a/test/fixtures/case2/expected.js +++ b/test/fixtures/case2/expected.js @@ -53,8 +53,8 @@ ErrorBox.propTypes = { exports.default = ErrorBox; ErrorBox.__docgenInfo = { 'description': '', - 'displayName': 'ErrorBox', 'methods': [], + 'displayName': 'ErrorBox', 'props': { 'children': { 'type': { diff --git a/test/fixtures/case3/expected.js b/test/fixtures/case3/expected.js index b343b00..bd6d9f7 100644 --- a/test/fixtures/case3/expected.js +++ b/test/fixtures/case3/expected.js @@ -46,6 +46,17 @@ Button.__docgenInfo = { 'description': '', 'methods': [], 'props': { + 'style': { + 'defaultValue': { + 'value': '{}', + 'computed': false + }, + 'type': { + 'name': 'object' + }, + 'required': false, + 'description': '' + }, 'children': { 'type': { 'name': 'string' @@ -59,17 +70,6 @@ Button.__docgenInfo = { }, 'required': false, 'description': '' - }, - 'style': { - 'type': { - 'name': 'object' - }, - 'required': false, - 'description': '', - 'defaultValue': { - 'value': '{}', - 'computed': false - } } } }; diff --git a/test/fixtures/case4/expected.js b/test/fixtures/case4/expected.js index eaaa92f..afcd942 100644 --- a/test/fixtures/case4/expected.js +++ b/test/fixtures/case4/expected.js @@ -47,6 +47,17 @@ Button.__docgenInfo = { 'description': '', 'methods': [], 'props': { + 'style': { + 'defaultValue': { + 'value': '{}', + 'computed': false + }, + 'type': { + 'name': 'object' + }, + 'required': false, + 'description': '' + }, 'children': { 'type': { 'name': 'string' @@ -60,17 +71,6 @@ Button.__docgenInfo = { }, 'required': false, 'description': '' - }, - 'style': { - 'type': { - 'name': 'object' - }, - 'required': false, - 'description': '', - 'defaultValue': { - 'value': '{}', - 'computed': false - } } } }; diff --git a/test/fixtures/case8/expected.js b/test/fixtures/case8/expected.js index 9bcdde7..d176767 100644 --- a/test/fixtures/case8/expected.js +++ b/test/fixtures/case8/expected.js @@ -61,8 +61,8 @@ Wrapper.propTypes = { }; Wrapper.__docgenInfo = { "description": "", - "displayName": "Wrapper", "methods": [], + "displayName": "Wrapper", "props": { "children": { "type": { diff --git a/test/fixtures/createReactClass/expected.js b/test/fixtures/createReactClass/expected.js index 7efa2dc..8dea3d1 100644 --- a/test/fixtures/createReactClass/expected.js +++ b/test/fixtures/createReactClass/expected.js @@ -42,20 +42,20 @@ var ComponentWrapper = createReactClass({ module.exports = ComponentWrapper; ComponentWrapper.__docgenInfo = { 'description': 'Component for displaying a container that resembles the original CSS environment for different themes', - 'displayName': 'ComponentWrapper', 'methods': [], + 'displayName': 'ComponentWrapper', 'props': { 'theme': { + 'defaultValue': { + 'value': '\'damask\'', + 'computed': false + }, 'type': { 'name': 'custom', 'raw': 'PropTypes.string' }, 'required': false, - 'description': 'Theme to display', - 'defaultValue': { - 'value': '\'damask\'', - 'computed': false - } + 'description': 'Theme to display' } } }; diff --git a/test/fixtures/differentName/actual.js b/test/fixtures/differentName/actual.js new file mode 100644 index 0000000..93bf236 --- /dev/null +++ b/test/fixtures/differentName/actual.js @@ -0,0 +1,38 @@ +import React, { PropTypes } from 'react' + +const stylesheet = {}; + +/** + * Component for displaying a container that resembles the original CSS environment for different themes + */ + +class OriginalName extends Component () { + static displayName = 'ThisIsTheDisplayNameNow' + + static propTypes = { + /** + * Theme to display + */ + theme: PropTypes.string, + /** + * The component children + */ + children: PropTypes.node + } + + getDefaultProps() { + return { + theme: 'damask' + }; + } + + render() { + return ( +
+ { this.props.children } +
+ ); + } +} + +module.exports = OriginalName; diff --git a/test/fixtures/differentName/expected.js b/test/fixtures/differentName/expected.js new file mode 100644 index 0000000..d6b3ef8 --- /dev/null +++ b/test/fixtures/differentName/expected.js @@ -0,0 +1,99 @@ +'use strict'; + +var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + +function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + +var stylesheet = {}; + +/** + * Component for displaying a container that resembles the original CSS environment for different themes + */ + +var OriginalName = function (_Component) { + _inherits(OriginalName, _Component); + + function OriginalName() { + _classCallCheck(this, OriginalName); + + return _possibleConstructorReturn(this, (OriginalName.__proto__ || Object.getPrototypeOf(OriginalName)).apply(this, arguments)); + } + + _createClass(OriginalName, [{ + key: 'getDefaultProps', + value: function getDefaultProps() { + return { + theme: 'damask' + }; + } + }, { + key: 'render', + value: function render() { + return _react2.default.createElement( + 'div', + { className: stylesheet[this.props.theme] }, + this.props.children + ); + } + }]); + + return OriginalName; +}(Component()); + +OriginalName.displayName = 'ThisIsTheDisplayNameNow'; +OriginalName.propTypes = { + /** + * Theme to display + */ + theme: _react.PropTypes.string, + /** + * The component children + */ + children: _react.PropTypes.node +}; + + +module.exports = OriginalName; +OriginalName.__docgenInfo = { + 'description': 'Component for displaying a container that resembles the original CSS environment for different themes', + 'methods': [], + 'displayName': 'ThisIsTheDisplayNameNow', + 'props': { + 'theme': { + 'defaultValue': { + 'value': '\'damask\'', + 'computed': false + }, + 'type': { + 'name': 'string' + }, + 'required': false, + 'description': 'Theme to display' + }, + 'children': { + 'type': { + 'name': 'node' + }, + 'required': false, + 'description': 'The component children' + } + } +}; + +if (typeof STORYBOOK_REACT_CLASSES !== 'undefined') { + STORYBOOK_REACT_CLASSES['test/fixtures/differentName/actual.js'] = { + name: 'OriginalName', + docgenInfo: OriginalName.__docgenInfo, + path: 'test/fixtures/differentName/actual.js' + }; +} \ No newline at end of file diff --git a/test/fixtures/example-module-exports/expected.js b/test/fixtures/example-module-exports/expected.js index 15e2aee..97fd08a 100644 --- a/test/fixtures/example-module-exports/expected.js +++ b/test/fixtures/example-module-exports/expected.js @@ -43,8 +43,8 @@ function App() { module.exports = App; App.__docgenInfo = { 'description': '', - 'displayName': 'App', - 'methods': [] + 'methods': [], + 'displayName': 'App' }; if (typeof STORYBOOK_REACT_CLASSES !== 'undefined') { diff --git a/test/fixtures/example/expected.js b/test/fixtures/example/expected.js index 46662f3..4916cc0 100644 --- a/test/fixtures/example/expected.js +++ b/test/fixtures/example/expected.js @@ -70,8 +70,8 @@ var App = function (_Component) { exports.default = App; App.__docgenInfo = { 'description': '', - 'displayName': 'App', - 'methods': [] + 'methods': [], + 'displayName': 'App' }; if (typeof STORYBOOK_REACT_CLASSES !== 'undefined') { diff --git a/test/fixtures/flowType/expected.js b/test/fixtures/flowType/expected.js index 07cf3d9..b141b4f 100644 --- a/test/fixtures/flowType/expected.js +++ b/test/fixtures/flowType/expected.js @@ -59,7 +59,6 @@ var FlowTypeButton = function (_React$Component) { exports.default = FlowTypeButton; FlowTypeButton.__docgenInfo = { 'description': '', - 'displayName': 'FlowTypeButton', 'methods': [{ 'name': 'handleClick', 'docblock': 'handle click number of times clicked and update parent component via callback\n@return {string} returns nothing but at least this makes it into docgen', @@ -79,6 +78,7 @@ FlowTypeButton.__docgenInfo = { }, 'description': 'handle click number of times clicked and update parent component via callback' }], + 'displayName': 'FlowTypeButton', 'props': { 'label': { 'flowType': { diff --git a/test/fixtures/functionDeclaration/expected.js b/test/fixtures/functionDeclaration/expected.js index 9ffd586..2a52ad7 100644 --- a/test/fixtures/functionDeclaration/expected.js +++ b/test/fixtures/functionDeclaration/expected.js @@ -36,8 +36,8 @@ FuncDeclaration.propTypes = { exports.default = FuncDeclaration; FuncDeclaration.__docgenInfo = { 'description': '', - 'displayName': 'FuncDeclaration', 'methods': [], + 'displayName': 'FuncDeclaration', 'props': { 'children': { 'type': { diff --git a/test/fixtures/hoc-multiple/expected.js b/test/fixtures/hoc-multiple/expected.js index 5a65a31..87f52df 100644 --- a/test/fixtures/hoc-multiple/expected.js +++ b/test/fixtures/hoc-multiple/expected.js @@ -48,8 +48,8 @@ Component.propTypes = { exports.default = withHoc()(deeperHoc(Component)); Component.__docgenInfo = { "description": "Super tiny component", - "displayName": "Component", "methods": [], + "displayName": "Component", "props": { "children": { "type": { diff --git a/test/fixtures/hoc/expected.js b/test/fixtures/hoc/expected.js index c435922..cc20600 100644 --- a/test/fixtures/hoc/expected.js +++ b/test/fixtures/hoc/expected.js @@ -48,8 +48,8 @@ Component.propTypes = { exports.default = withHoc(Component); Component.__docgenInfo = { "description": "Super tiny component", - "displayName": "Component", "methods": [], + "displayName": "Component", "props": { "children": { "type": { diff --git a/test/fixtures/multiple-exports/expected.js b/test/fixtures/multiple-exports/expected.js index f43fc4a..7ccab0d 100644 --- a/test/fixtures/multiple-exports/expected.js +++ b/test/fixtures/multiple-exports/expected.js @@ -86,8 +86,8 @@ ErrorBox2.propTypes = { exports.ErrorBox2 = ErrorBox2; ErrorBox.__docgenInfo = { 'description': '', - 'displayName': 'ErrorBox', 'methods': [], + 'displayName': 'ErrorBox', 'props': { 'children': { 'type': { @@ -109,8 +109,8 @@ if (typeof STORYBOOK_REACT_CLASSES !== 'undefined') { ErrorBox2.__docgenInfo = { 'description': '', - 'displayName': 'ErrorBox2', 'methods': [], + 'displayName': 'ErrorBox2', 'props': { 'children2': { 'type': { diff --git a/test/fixtures/reactCreateClass/expected.js b/test/fixtures/reactCreateClass/expected.js index aae785b..3509b56 100644 --- a/test/fixtures/reactCreateClass/expected.js +++ b/test/fixtures/reactCreateClass/expected.js @@ -44,15 +44,15 @@ ComponentWrapper.__docgenInfo = { 'methods': [], 'props': { 'theme': { + 'defaultValue': { + 'value': '\'damask\'', + 'computed': false + }, 'type': { 'name': 'string' }, 'required': false, - 'description': 'Theme to display', - 'defaultValue': { - 'value': '\'damask\'', - 'computed': false - } + 'description': 'Theme to display' } } }; diff --git a/test/fixtures/reactCreateElement/expected.js b/test/fixtures/reactCreateElement/expected.js index f60158a..c99607d 100644 --- a/test/fixtures/reactCreateElement/expected.js +++ b/test/fixtures/reactCreateElement/expected.js @@ -38,26 +38,26 @@ Kitten.__docgenInfo = { 'methods': [], 'props': { 'isWide': { + 'defaultValue': { + 'value': 'false', + 'computed': false + }, 'type': { 'name': 'bool' }, 'required': false, - 'description': 'Whether the cat is wide', + 'description': 'Whether the cat is wide' + }, + 'isLong': { 'defaultValue': { 'value': 'false', 'computed': false - } - }, - 'isLong': { + }, 'type': { 'name': 'bool' }, 'required': false, - 'description': 'Whether the cat is long', - 'defaultValue': { - 'value': 'false', - 'computed': false - } + 'description': 'Whether the cat is long' } } }; diff --git a/test/index.js b/test/index.js index f4aa0fb..50c0b60 100644 --- a/test/index.js +++ b/test/index.js @@ -15,13 +15,13 @@ describe('Add propType doc to react classes', () => { if (caseName.indexOf('.DS_Store') < 0) { it(`should ${caseName.split('-').join(' ')}`, () => { const fixtureDir = path.join(fixturesDir, caseName); - const actualPath = path.join(fixtureDir, 'source.js'); + const actualPath = path.join(fixtureDir, 'actual.js'); const options = { presets: [ - "react", "env", "flow", - "stage-0" + "stage-0", + "react" ], plugins: [ [plugin, { @@ -32,8 +32,10 @@ describe('Add propType doc to react classes', () => { }; const actual = transformFileSync(actualPath, options).code; - // fs.writeFileSync(path.join(fixtureDir, 'actual.js'), actual); - const expected = fs.readFileSync(path.join(fixtureDir, 'expected.js')).toString(); + // fs.writeFileSync(path.join(fixtureDir, 'expected.js'), actual); + const expected = fs.readFileSync( + path.join(fixtureDir, 'expected.js') + ).toString(); assert.equal(trim(actual), trim(expected)); }); } From e1964bcb10a957de8118d5862a1607906efe3676 Mon Sep 17 00:00:00 2001 From: Alex Kurochkin Date: Fri, 15 Jun 2018 16:07:27 +0300 Subject: [PATCH 3/7] Fix isExported to also detect export declarations --- test/fixtures/exportedDeclaration/expected.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/fixtures/exportedDeclaration/expected.js b/test/fixtures/exportedDeclaration/expected.js index 4bdf674..4e51d45 100644 --- a/test/fixtures/exportedDeclaration/expected.js +++ b/test/fixtures/exportedDeclaration/expected.js @@ -70,4 +70,4 @@ if (typeof STORYBOOK_REACT_CLASSES !== 'undefined') { docgenInfo: Button.__docgenInfo, path: 'test/fixtures/exportedDeclaration/actual.js' }; -} \ No newline at end of file +} From 10bbf9095027523655ebe8fdd5e14a5647ca09b2 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sun, 17 Jun 2018 12:04:48 -0400 Subject: [PATCH 4/7] fix test --- test/fixtures/exportedDeclaration/expected.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/fixtures/exportedDeclaration/expected.js b/test/fixtures/exportedDeclaration/expected.js index 4e51d45..552e6d1 100644 --- a/test/fixtures/exportedDeclaration/expected.js +++ b/test/fixtures/exportedDeclaration/expected.js @@ -36,6 +36,17 @@ Button.__docgenInfo = { 'description': '', 'methods': [], 'props': { + 'style': { + 'defaultValue': { + 'value': '{}', + 'computed': false + }, + 'type': { + 'name': 'object' + }, + 'required': false, + 'description': '' + }, 'children': { 'type': { 'name': 'string' @@ -49,17 +60,6 @@ Button.__docgenInfo = { }, 'required': false, 'description': '' - }, - 'style': { - 'type': { - 'name': 'object' - }, - 'required': false, - 'description': '', - 'defaultValue': { - 'value': '{}', - 'computed': false - } } } }; @@ -70,4 +70,4 @@ if (typeof STORYBOOK_REACT_CLASSES !== 'undefined') { docgenInfo: Button.__docgenInfo, path: 'test/fixtures/exportedDeclaration/actual.js' }; -} +} \ No newline at end of file From 005b61b78956d8d874e1ef14a4b110c1d02d0d8b Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sun, 17 Jun 2018 12:09:55 -0400 Subject: [PATCH 5/7] add test in #48 --- test/fixtures/hoc-function/actual.js | 24 ++++++++ test/fixtures/hoc-function/expected.js | 78 ++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 test/fixtures/hoc-function/actual.js create mode 100644 test/fixtures/hoc-function/expected.js diff --git a/test/fixtures/hoc-function/actual.js b/test/fixtures/hoc-function/actual.js new file mode 100644 index 0000000..05e4526 --- /dev/null +++ b/test/fixtures/hoc-function/actual.js @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { mapStateToProps, mapDispatchToProps } from './testSelector'; + +const TestComponent = (props) => { + const { text, onClick } = props; + + return ( +
+
Text: {text}
+ +
+ ); +}; + +TestComponent.propTypes = { + /** Text to display */ + text: PropTypes.string, + /** Called on click */ + onClick: PropTypes.func +}; + +export default connect(mapStateToProps, mapDispatchToProps)(TestComponent); diff --git a/test/fixtures/hoc-function/expected.js b/test/fixtures/hoc-function/expected.js new file mode 100644 index 0000000..9ec4945 --- /dev/null +++ b/test/fixtures/hoc-function/expected.js @@ -0,0 +1,78 @@ +'use strict'; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = require('react'); + +var _react2 = _interopRequireDefault(_react); + +var _propTypes = require('prop-types'); + +var _propTypes2 = _interopRequireDefault(_propTypes); + +var _reactRedux = require('react-redux'); + +var _testSelector = require('./testSelector'); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var TestComponent = function TestComponent(props) { + var text = props.text, + onClick = props.onClick; + + + return _react2.default.createElement( + 'div', + null, + _react2.default.createElement( + 'div', + null, + 'Text: ', + text + ), + _react2.default.createElement( + 'button', + { onClick: onClick }, + 'Button' + ) + ); +}; + +TestComponent.propTypes = { + /** Text to display */ + text: _propTypes2.default.string, + /** Called on click */ + onClick: _propTypes2.default.func +}; + +exports.default = (0, _reactRedux.connect)(_testSelector.mapStateToProps, _testSelector.mapDispatchToProps)(TestComponent); +TestComponent.__docgenInfo = { + 'description': '', + 'methods': [], + 'props': { + 'text': { + 'type': { + 'name': 'string' + }, + 'required': false, + 'description': 'Text to display' + }, + 'onClick': { + 'type': { + 'name': 'func' + }, + 'required': false, + 'description': 'Called on click' + } + } +}; + +if (typeof STORYBOOK_REACT_CLASSES !== 'undefined') { + STORYBOOK_REACT_CLASSES['test/fixtures/hoc-function/actual.js'] = { + name: 'TestComponent', + docgenInfo: TestComponent.__docgenInfo, + path: 'test/fixtures/hoc-function/actual.js' + }; +} \ No newline at end of file From 0299787d1079a987ce19df933211bad2d38d5ed7 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Sun, 17 Jun 2018 12:24:25 -0400 Subject: [PATCH 6/7] update add test for multiple exports and not working flow --- test/fixtures/flowTypeNotWorking/actual.js | 26 ++ test/fixtures/flowTypeNotWorking/expected.js | 30 ++ test/fixtures/multipleExports2/actual.js | 81 ++++++ test/fixtures/multipleExports2/expected.js | 272 +++++++++++++++++++ 4 files changed, 409 insertions(+) create mode 100644 test/fixtures/flowTypeNotWorking/actual.js create mode 100644 test/fixtures/flowTypeNotWorking/expected.js create mode 100644 test/fixtures/multipleExports2/actual.js create mode 100644 test/fixtures/multipleExports2/expected.js diff --git a/test/fixtures/flowTypeNotWorking/actual.js b/test/fixtures/flowTypeNotWorking/actual.js new file mode 100644 index 0000000..2896b40 --- /dev/null +++ b/test/fixtures/flowTypeNotWorking/actual.js @@ -0,0 +1,26 @@ +// @flow + +import * as React from 'react'; +import styled from 'styled-components'; + +type Button = { + margin?: string, + bgColor?: string, + hoverColor?: string, +} + +export const StyledButton = (styled.button` + margin: ${props => (props.margin ? props.margin : '10px')}; + height: 40px; + max-width: 254px; + text-transform: uppercase; + text-decoration: none; + text-align: center; + cursor: pointer; + border-radius: 2px; + background-color: ${props => (props.bgColor ? props.bgColor : 'rgb(255, 168, 39)')}; + + &:hover { + background: ${props => (props.hoverColor ? props.hoverColor : '#81A2CA')}; + } +`: React.ComponentType