Skip to content

Commit

Permalink
now works with statless components
Browse files Browse the repository at this point in the history
  • Loading branch information
madushan1000 committed Oct 18, 2016
1 parent 0708dd8 commit d383385
Show file tree
Hide file tree
Showing 8 changed files with 344 additions and 26 deletions.
57 changes: 37 additions & 20 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,68 @@
import * as _ from 'lodash';
import * as reactDocs from 'react-docgen';
import isReactComponentClass from './isReactComponentClass';
import isStatelessComponent from './isStatelessComponent';
import * as p from 'path';

export default function({types: t}) {
export default function ({types: t}) {
return {
visitor: {
Class(path, state) {
const {
node,
scope,
} = path;

if(isReactComponentClass(path)){
injectReactDocgenInfo(path, this.file.code, t);
injectDocgenGlobal(path, state, t);
if(isReactComponentClass(path)) {
const className = path.node.id.name;
injectReactDocgenInfo(className, path, state, this.file.code, t);
}
},
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression'(path) {
'FunctionDeclaration|FunctionExpression|ArrowFunctionExpression'(path, state) {
if(isStatelessComponent(path)) {
const className = path.parentPath.node.id.name;
injectReactDocgenInfo(className, path, state, this.file.code, t);
}
},
}
};
}

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(path, code, t) {
if(!t.isProgram(path.parentPath.node)) {
function injectReactDocgenInfo(className, path, state, code, t) {
const program = path.scope.getProgramParent().path;

if(alreadyVisited(program, t)) {
return;
}

const docObj = reactDocs.parse(code);
const docNode = buildObjectExpression(docObj, t);
const docgenInfo = t.expressionStatement(
t.assignmentExpression(
"=",
t.memberExpression(t.identifier(path.node.id.name), t.identifier('__docgenInfo')),
t.memberExpression(t.identifier(className), t.identifier('__docgenInfo')),
docNode
));
path.parentPath.pushContainer('body', docgenInfo);

program.pushContainer('body', docgenInfo);
injectDocgenGlobal(className, path, state, t);
}

function injectDocgenGlobal(path, state, t) {
if(!state.opts.DOC_GEN_GLOBAL || !t.isProgram(path.parentPath.node)) {
function injectDocgenGlobal(className, path, state, t) {
const program = path.scope.getProgramParent().path;

if(!state.opts.DOC_GEN_GLOBAL) {
return;
}

const globalName = state.opts.DOC_GEN_GLOBAL;
const className = path.node.id.name;
const filePath = p.relative('./', p.resolve('./', path.hub.file.opts.filename));
const globalNode = t.ifStatement(
t.binaryExpression(
Expand Down Expand Up @@ -86,7 +103,7 @@ function injectDocgenGlobal(path, state, t) {
)
])
);
path.parentPath.pushContainer('body', globalNode);
program.pushContainer('body', globalNode);
}

function buildObjectExpression(obj, t){
Expand All @@ -109,7 +126,7 @@ function buildObjectExpression(obj, t){
return t.numericLiteral(obj);
} else if (_.isArray(obj)) {
const children = [];
obj.forEach(function(val) {
obj.forEach(function (val) {
children.push(buildObjectExpression(val, t));
});
return t.ArrayExpression(children);
Expand Down
91 changes: 91 additions & 0 deletions src/isStatelessComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
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;

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);

if (!binding) {
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;
}
27 changes: 27 additions & 0 deletions test/fixtures/case3/actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';

const Button = ({ children, onClick, style = {} }) => (
<button
style={{ }}
onClick={onClick}
>
{children}
</button>
);

Button.propTypes = {
children: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func,
style: React.PropTypes.object,
};

export default Button;

let A;
A = [1,2,2,2];

function abc() {
let c = function cef() {
A = 'str';
};
}
79 changes: 79 additions & 0 deletions test/fixtures/case3/expected.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

Object.defineProperty(exports, "__esModule", {
value: true
});

var _react = require('react');

var _react2 = _interopRequireDefault(_react);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

var Button = function Button(_ref) {
var children = _ref.children;
var onClick = _ref.onClick;
var _ref$style = _ref.style;
var style = _ref$style === undefined ? {} : _ref$style;
return _react2.default.createElement(
'button',
{
style: {},
onClick: onClick
},
children
);
};

Button.propTypes = {
children: _react2.default.PropTypes.string.isRequired,
onClick: _react2.default.PropTypes.func,
style: _react2.default.PropTypes.object
};

exports.default = Button;


var A = void 0;
A = [1, 2, 2, 2];

function abc() {
var c = function cef() {
A = 'str';
};
}
Button.__docgenInfo = {
description: '',
methods: [],
props: {
children: {
type: {
name: 'string'
},
required: true,
description: ''
},
onClick: {
type: {
name: 'func'
},
required: false,
description: ''
},
style: {
type: {
name: 'object'
},
required: false,
description: ''
}
}
};

if (typeof STORYBOOK_REACT_CLASSES !== 'undefined') {
STORYBOOK_REACT_CLASSES['test/fixtures/case3/actual.js'] = {
name: 'Button',
docgenInfo: Button.__docgenInfo,
path: 'test/fixtures/case3/actual.js'
};
}
29 changes: 29 additions & 0 deletions test/fixtures/case4/actual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';

const Button = ({ children, onClick, style = {} }) => {
return (
<button
style={{ }}
onClick={onClick}
>
{children}
</button>
);
};

Button.propTypes = {
children: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func,
style: React.PropTypes.object,
};

export default Button;

let A;
A = [1,2,2,2];

function abc() {
let c = function cef() {
A = 'str';
};
}
Loading

0 comments on commit d383385

Please sign in to comment.