Skip to content

Commit

Permalink
Initial support for importing prop types
Browse files Browse the repository at this point in the history
  • Loading branch information
devongovett committed May 4, 2019
1 parent 16cc596 commit 77c780b
Show file tree
Hide file tree
Showing 16 changed files with 394 additions and 23 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"commander": "^2.19.0",
"doctrine": "^3.0.0",
"node-dir": "^0.1.10",
"strip-indent": "^2.0.0"
"strip-indent": "^2.0.0",
"resolve": "^1.10.1"
},
"devDependencies": {
"@babel/cli": "^7.0.0",
Expand Down
141 changes: 141 additions & 0 deletions src/__tests__/__snapshots__/main-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1421,3 +1421,144 @@ Object {
},
}
`;
exports[`main fixtures processes component "component_28.tsx" without errors 1`] = `
Object {
"description": "This is a typescript component with imported prop types",
"displayName": "ImportedComponent",
"methods": Array [],
"props": Object {
"foo": Object {
"description": "",
"required": true,
"tsType": Object {
"name": "string",
},
},
},
}
`;
exports[`main fixtures processes component "component_29.tsx" without errors 1`] = `
Object {
"description": "This is a typescript component with imported prop types",
"displayName": "ImportedExtendedComponent",
"methods": Array [],
"props": Object {
"bar": Object {
"description": "",
"required": true,
"tsType": Object {
"name": "number",
},
},
"foo": Object {
"description": "",
"required": true,
"tsType": Object {
"name": "string",
},
},
},
}
`;
exports[`main fixtures processes component "component_30.js" without errors 1`] = `
Object {
"description": "",
"displayName": "CustomButton",
"methods": Array [],
"props": Object {
"children": Object {
"description": "",
"required": true,
"type": Object {
"name": "string",
},
},
"color": Object {
"description": "",
"required": false,
"type": Object {
"name": "string",
},
},
"onClick": Object {
"description": "",
"required": false,
"type": Object {
"name": "func",
},
},
"style": Object {
"description": "",
"required": false,
"type": Object {
"name": "object",
},
},
},
}
`;
exports[`main fixtures processes component "component_31.js" without errors 1`] = `
Object {
"description": "",
"displayName": "SuperCustomButton",
"methods": Array [],
"props": Object {
"children": Object {
"description": "",
"required": true,
"type": Object {
"name": "string",
},
},
"onClick": Object {
"description": "",
"required": false,
"type": Object {
"name": "func",
},
},
"style": Object {
"description": "",
"required": false,
"type": Object {
"name": "object",
},
},
},
}
`;
exports[`main fixtures processes component "component_32.js" without errors 1`] = `
Object {
"description": "",
"displayName": "SuperDuperCustomButton",
"methods": Array [],
"props": Object {
"children": Object {
"description": "",
"required": true,
"type": Object {
"name": "string",
},
},
"onClick": Object {
"description": "",
"required": false,
"type": Object {
"name": "func",
},
},
"style": Object {
"description": "",
"required": false,
"type": Object {
"name": "object",
},
},
},
}
`;
2 changes: 1 addition & 1 deletion src/__tests__/fixtures/component_27.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import React, { Component } from 'react';

interface Props {
export interface Props {
foo: string
}

Expand Down
21 changes: 21 additions & 0 deletions src/__tests__/fixtures/component_28.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import React, { Component } from 'react';
import { Props as ImportedProps } from './component_27';

export default interface ExtendedProps extends ImportedProps {
bar: number
}

/**
* This is a typescript component with imported prop types
*/
export function ImportedComponent(props: ImportedProps) {
return <h1>Hello world</h1>;
}
17 changes: 17 additions & 0 deletions src/__tests__/fixtures/component_29.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import React, { Component } from 'react';
import ExtendedProps from './component_28';

/**
* This is a typescript component with imported prop types
*/
export function ImportedExtendedComponent(props: ExtendedProps) {
return <h1>Hello world</h1>;
}
13 changes: 13 additions & 0 deletions src/__tests__/fixtures/component_30.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import Button from './component_6';
import PropTypes from 'prop-types';

export function CustomButton({color, ...otherProps}) {
return <Button {...otherProps} style={{color}} />;
}

CustomButton.propTypes = {
...Button.propTypes,
color: PropTypes.string
};

export const sharedProps = Button.propTypes;
9 changes: 9 additions & 0 deletions src/__tests__/fixtures/component_31.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {CustomButton, sharedProps} from './component_30';
import PropTypes from 'prop-types';

export function SuperCustomButton({color, ...otherProps}) {
return <CustomButton {...otherProps} style={{color}} />;
}

SuperCustomButton.propTypes = sharedProps;
export {sharedProps};
8 changes: 8 additions & 0 deletions src/__tests__/fixtures/component_32.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import {SuperCustomButton, sharedProps} from './component_31';
import PropTypes from 'prop-types';

export function SuperDuperCustomButton({color, ...otherProps}) {
return <SuperCustomButton {...otherProps} style={{color}} />;
}

SuperDuperCustomButton.propTypes = sharedProps;
12 changes: 8 additions & 4 deletions src/babelParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,17 @@ function buildOptions(
export default function buildParse(options?: Options = {}): Parser {
const { parserOptions, ...babelOptions } = options;
const parserOpts = buildOptions(parserOptions, babelOptions);
const opts = {
parserOpts,
...babelOptions,
};

return {
parse(src: string): ASTNode {
return babel.parseSync(src, {
parserOpts,
...babelOptions,
});
const ast = babel.parseSync(src, opts);
// Attach options to the Program node, for use when processing imports.
ast.program.options = options;
return ast;
},
};
}
2 changes: 1 addition & 1 deletion src/handlers/__tests__/propDocblockHandler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ describe('propDocBlockHandler', () => {
const definition = parse(
getSrc(
`{
...Foo.propTypes,
...Bar.propTypes,
/**
* Foo comment
*/
Expand Down
2 changes: 1 addition & 1 deletion src/utils/getMemberValuePath.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const LOOKUP_METHOD = {
[t.ClassExpression.name]: getClassMemberValuePath,
};

function isSupportedDefinitionType({ node }) {
export function isSupportedDefinitionType({ node }) {
return (
t.ObjectExpression.check(node) ||
t.ClassDeclaration.check(node) ||
Expand Down
2 changes: 1 addition & 1 deletion src/utils/isReactComponentClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default function isReactComponentClass(path: NodePath): boolean {
if (!node.superClass) {
return false;
}
const superClass = resolveToValue(path.get('superClass'));
const superClass = resolveToValue(path.get('superClass'), false);
if (!match(superClass.node, { property: { name: 'Component' } })) {
return false;
}
Expand Down
108 changes: 108 additions & 0 deletions src/utils/resolveImportedValue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import types from 'ast-types';
import { traverseShallow } from './traverse';
import resolve from 'resolve';
import { dirname } from 'path';
import buildParser, { type Options } from '../babelParser';
import fs from 'fs';

const { namedTypes: t, NodePath } = types;

export default function resolveImportedValue(path: NodePath, name: string) {
t.ImportDeclaration.assert(path.node);

// Bail if no filename was provided for the current source file.
// Also never traverse into react itself.
const source = path.node.source.value;
const options = getOptions(path);
if (!options || !options.filename || source === 'react') {
return null;
}

// Resolve the imported module using the Node resolver
const basedir = dirname(options.filename);
let resolvedSource;

try {
resolvedSource = resolve.sync(source, {
basedir,
extensions: ['.js', '.jsx', '.ts', '.tsx'],
});
} catch (err) {
return null;
}

// Read and parse the code
// TODO: cache and reuse
const code = fs.readFileSync(resolvedSource, 'utf8');
const parseOptions: Options = {
...options,
parserOptions: {},
filename: resolvedSource,
};

const parser = buildParser(parseOptions);
const ast = parser.parse(code);
return findExportedValue(ast.program, name);
}

// Find the root Program node, which we attached our options too in babelParser.js
function getOptions(path: NodePath): Options {
while (!t.Program.check(path.node)) {
path = path.parentPath;
}

return path.node.options || {};
}

// Traverses the program looking for an export that matches the requested name
function findExportedValue(ast, name) {
let resultPath: ?NodePath = null;

traverseShallow(ast, {
visitExportNamedDeclaration(path: NodePath) {
const { declaration, specifiers } = path.node;
if (declaration && declaration.id && declaration.id.name === name) {
resultPath = path.get('declaration');
} else if (declaration && declaration.declarations) {
path.get('declaration', 'declarations').each((declPath: NodePath) => {
const decl = declPath.node;
// TODO: ArrayPattern and ObjectPattern
if (
t.Identifier.check(decl.id) &&
decl.id.name === name &&
decl.init
) {
resultPath = declPath.get('init');
}
});
} else if (specifiers) {
path.get('specifiers').each((specifierPath: NodePath) => {
if (specifierPath.node.exported.name === name) {
resultPath = specifierPath.get('local');
}
});
}

return false;
},
visitExportDefaultDeclaration(path: NodePath) {
if (name === 'default') {
resultPath = path.get('declaration');
}

return false;
},
// TODO: visitExportAllDeclaration
});

return resultPath;
}

0 comments on commit 77c780b

Please sign in to comment.