Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

infer displayName when not defined #59

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 151 additions & 7 deletions src/handlers/__tests__/displayNameHandler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
jest.autoMockOff();
jest.mock('../../Documentation');

describe('defaultPropsHandler', () => {
describe('displayNameHandler', () => {
var documentation;
var displayNameHandler;
var expression, statement;
Expand Down Expand Up @@ -70,17 +70,161 @@ describe('defaultPropsHandler', () => {
expect(documentation.displayName).not.toBeDefined();
});

it('ignores non-literal names', () => {
var definition = expression('({displayName: foo.bar})');
expect(() => displayNameHandler(documentation, definition)).not.toThrow();
expect(documentation.displayName).not.toBeDefined();
it('infers the displayName with es6 class', () => {
var definition = statement(`
class Foo {}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Foo');
});

definition = statement(`
class Foo {
it('infers the displayName with extended es6 class', () => {
var definition = statement(`
class Foo extends Component {
render(){
return null
}
}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Foo');
});

it('infers the displayName with stateless functional component', () => {
var definition = statement(`
var Foo = () => {
return <div>JSX</div>
}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Foo');
});

it('infers the displayName with function expression', () => {
var definition = statement(`
(function FooBar() {})
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('FooBar');
});

});

describe('defaultPropsHandler with ES6 Exports', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this should be "dispayNameHandler".

var documentation;
var displayNameHandler;
var expression, statement;

beforeEach(() => {
({expression, statement} = require('../../../tests/utils'));
documentation = new (require('../../Documentation'));
displayNameHandler = require('../displayNameHandler');
});

it('extracts the displayName', () => {
var definition = statement(`
export class Foo {
static displayName = "BarFoo";
}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('BarFoo');
});

it('resolves identifiers', () => {
var definition = statement(`
export class Foo {
static displayName = name;
}
var name = 'xyz';
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('xyz');
});

it('ignores non-literal names', () => {
var definition = statement(`
export class Foo {
static displayName = foo.bar;
}
`);
expect(() => displayNameHandler(documentation, definition)).not.toThrow();
expect(documentation.displayName).not.toBeDefined();
});

it('infers the displayName with es6 class', () => {
var definition = statement(`
export class Baz {}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Baz');
});

it('infers the displayName with extended es6 class', () => {
var definition = statement(`
export class Foo extends Component {
render(){
return null
}
}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Foo');
});

it('infers the displayName with arrow function declaration', () => {
var definition = statement(`
export var Bar = () => {}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Bar');
});

it('infers the displayName with function expression', () => {
var definition = statement(`
export function Qux() {}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Qux');
});

it('infers the displayName with function declaration', () => {
var definition = statement(`
export const Foo = function() {}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Foo');
});

});

describe('defaultPropsHandler with commonJS Exports', () => {
var documentation;
var displayNameHandler;
var expression, statement;

beforeEach(() => {
({expression, statement} = require('../../../tests/utils'));
documentation = new (require('../../Documentation'));
displayNameHandler = require('../displayNameHandler');
});

it('infers the displayName with stateless functional component', () => {
var definition = statement(`
exports.Baz = () => {
return <div>JSX</div>
}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('Baz');
});

it('infers the displayName with function declaration', () => {
var definition = statement(`
exports.BazBar = function () {}
`);
displayNameHandler(documentation, definition);
expect(documentation.displayName).toBe('BazBar');
});
});

59 changes: 51 additions & 8 deletions src/handlers/displayNameHandler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,65 @@
import type Documentation from '../Documentation';

import getMemberValuePath from '../utils/getMemberValuePath';
import resolveName from '../utils/resolveName';
import recast from 'recast';
import resolveToValue from '../utils/resolveToValue';
import isExportsOrModuleAssignment from '../utils/isExportsOrModuleAssignment';
import resolveExportDeclaration from '../utils/resolveExportDeclaration'

var {types: {namedTypes: types}} = recast;

function getOrInferDisplayName(path) {
var displayNamePath = getMemberValuePath(path, 'displayName');

if (displayNamePath) {
displayNamePath = resolveToValue(displayNamePath);
if (!displayNamePath || !types.Literal.check(displayNamePath.node)) {
return;
}
return displayNamePath.node.value;
} else if (!displayNamePath && path.node.id) {
return path.node.id.name;
} else if (!displayNamePath && resolveName(path)) {
return resolveName(path);
}
}

export default function displayNameHandler(
documentation: Documentation,
path: NodePath
) {
var displayNamePath = getMemberValuePath(path, 'displayName');
if (!displayNamePath) {
return;
}
displayNamePath = resolveToValue(displayNamePath);
if (!displayNamePath || !types.Literal.check(displayNamePath.node)) {
return;
var displayName;

//If not immediately exported via ES6 or CommonJS exports or an ExpressionStatement
if (!types.ExportNamedDeclaration.check(path.node) && !isExportsOrModuleAssignment(path) && !types.ExpressionStatement.check(path.node)) {
displayName = getOrInferDisplayName(path);

//ES6 Exports
} else if (types.ExportNamedDeclaration.check(path.node)) {
var declarationPath;
var declaration = path.node.declaration;

if (
declaration.type === types.ClassDeclaration.name ||
declaration.type === types.FunctionDeclaration.name ||
declaration.type === types.FunctionExpression.name
) {
declarationPath = resolveExportDeclaration(path)[0];

} else if (declaration.type === types.VariableDeclaration.name) {

declarationPath = resolveExportDeclaration(path)[0].parentPath.parentPath.parentPath;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like this solution, but I'm not sure of how else to get this value while keeping 'NodePath' type intact.

}

displayName = getOrInferDisplayName(declarationPath);

//CommonJS export.X
} else if (isExportsOrModuleAssignment(path)) {
displayName = resolveToValue(path).get('expression', 'left', 'property', 'name').value;

} else if (types.ExpressionStatement.check(path.node) && path.node.expression.id) {
displayName = path.node.expression.id.name;
}
documentation.set('displayName', displayNamePath.node.value);
documentation.set('displayName', displayName);
}
37 changes: 1 addition & 36 deletions src/utils/getMemberExpressionValuePath.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,10 @@

import getNameOrValue from './getNameOrValue';
import recast from 'recast';
import resolveName from './resolveName';

var {types: {namedTypes: types}} = recast;

function resolveName(path) {
if (types.VariableDeclaration.check(path.node)) {
var declarations = path.get('declarations');
if (declarations.value.length && declarations.value.length !== 1) {
throw new TypeError(
'Got unsupported VariableDeclaration. VariableDeclaration must only ' +
'have a single VariableDeclarator. Got ' + declarations.value.length +
' declarations.'
);
}
var value = declarations.get(0, 'id', 'name').value;
return value;
}

if (types.FunctionDeclaration.check(path.node)) {
return path.get('id', 'name').value;
}

if (
types.FunctionExpression.check(path.node) ||
types.ArrowFunctionExpression.check(path.node)
) {
if (!types.VariableDeclarator.check(path.parent.node)) {
return; // eslint-disable-line consistent-return
}

return path.parent.get('id', 'name').value;
}

throw new TypeError(
'Attempted to resolveName for an unsupported path. resolveName accepts a ' +
'VariableDeclaration, FunctionDeclaration, or FunctionExpression. Got "' +
path.node.type + '".'
);
}

function getRoot(node) {
var root = node.parent;
while (root.parent) {
Expand Down
39 changes: 39 additions & 0 deletions src/utils/resolveName.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import recast from 'recast';

var {types: {namedTypes: types}} = recast;

export default function resolveName(path) {
if (types.VariableDeclaration.check(path.node)) {
var declarations = path.get('declarations');
if (declarations.value.length && declarations.value.length !== 1) {
throw new TypeError(
'Got unsupported VariableDeclaration. VariableDeclaration must only ' +
'have a single VariableDeclarator. Got ' + declarations.value.length +
' declarations.'
);
}
var value = declarations.get(0, 'id', 'name').value;
return value;
}

if (types.FunctionDeclaration.check(path.node)) {
return path.get('id', 'name').value;
}

if (
types.FunctionExpression.check(path.node) ||
types.ArrowFunctionExpression.check(path.node)
) {
if (!types.VariableDeclarator.check(path.parent.node)) {
return; // eslint-disable-line consistent-return
}

return path.parent.get('id', 'name').value;
}

throw new TypeError(
'Attempted to resolveName for an unsupported path. resolveName accepts a ' +
'VariableDeclaration, FunctionDeclaration, or FunctionExpression. Got "' +
path.node.type + '".'
);
}