diff --git a/bin/__tests__/react-docgen-test.js b/bin/__tests__/react-docgen-test.js
index e7fc35fd432..3d43f299729 100644
--- a/bin/__tests__/react-docgen-test.js
+++ b/bin/__tests__/react-docgen-test.js
@@ -198,7 +198,6 @@ describe('react-docgen CLI', () => {
},
TEST_TIMEOUT,
);
-
it(
'writes to stdout',
() => {
diff --git a/package.json b/package.json
index cd5d87206ad..c7e2e31bc82 100644
--- a/package.json
+++ b/package.json
@@ -48,6 +48,7 @@
"doctrine": "^3.0.0",
"neo-async": "^2.6.1",
"node-dir": "^0.1.10",
+ "resolve": "^1.17.0",
"strip-indent": "^3.0.0"
},
"devDependencies": {
diff --git a/src/__tests__/__snapshots__/main-test.js.snap b/src/__tests__/__snapshots__/main-test.js.snap
index 30c6605c57d..99041fe6d4f 100644
--- a/src/__tests__/__snapshots__/main-test.js.snap
+++ b/src/__tests__/__snapshots__/main-test.js.snap
@@ -1008,9 +1008,6 @@ Object {
exports[`main fixtures processes component "component_19.js" without errors 1`] = `
Object {
- "composes": Array [
- undefined,
- ],
"description": "",
"displayName": "Component",
"methods": Array [],
@@ -1577,3 +1574,326 @@ Object {
},
}
`;
+
+exports[`main fixtures processes component "component_33.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_34.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_35.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_36.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",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_37.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",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_38.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",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "component_39.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_40.js" without errors 1`] = `
+Object {
+ "description": "",
+ "displayName": "SuperDuperCustomButton",
+ "methods": Array [],
+ "props": Object {
+ "size": Object {
+ "defaultValue": Object {
+ "computed": true,
+ "value": "Sizes.EXTRA_LARGE",
+ },
+ "description": "",
+ "required": false,
+ "type": Object {
+ "computed": true,
+ "name": "enum",
+ "value": "Object.values(Sizes)",
+ },
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "flow-export-type.js" without errors 1`] = `
+Object {
+ "description": "This is a Flow class component",
+ "displayName": "FlowComponent",
+ "methods": Array [
+ Object {
+ "docblock": null,
+ "modifiers": Array [],
+ "name": "foo",
+ "params": Array [
+ Object {
+ "name": "a",
+ "optional": undefined,
+ "type": Object {
+ "name": "string",
+ },
+ },
+ ],
+ "returns": Object {
+ "type": Object {
+ "name": "string",
+ },
+ },
+ },
+ Object {
+ "docblock": null,
+ "modifiers": Array [],
+ "name": "bar",
+ "params": Array [
+ Object {
+ "name": "a",
+ "optional": undefined,
+ "type": Object {
+ "name": "string",
+ },
+ },
+ ],
+ "returns": Object {
+ "type": Object {
+ "name": "string",
+ },
+ },
+ },
+ ],
+ "props": Object {
+ "foo": Object {
+ "description": "",
+ "flowType": Object {
+ "name": "string",
+ },
+ "required": true,
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "flow-import-type.js" without errors 1`] = `
+Object {
+ "description": "This is a Flow component with imported prop types",
+ "displayName": "ImportedComponent",
+ "methods": Array [],
+ "props": Object {
+ "foo": Object {
+ "description": "",
+ "flowType": Object {
+ "name": "string",
+ },
+ "required": true,
+ },
+ },
+}
+`;
+
+exports[`main fixtures processes component "flow-spread-import-type.js" without errors 1`] = `
+Object {
+ "description": "This is a Flow component with imported prop types",
+ "displayName": "ImportedExtendedComponent",
+ "methods": Array [],
+ "props": Object {
+ "bar": Object {
+ "description": "",
+ "flowType": Object {
+ "name": "number",
+ },
+ "required": true,
+ },
+ "foo": Object {
+ "description": "",
+ "flowType": Object {
+ "name": "string",
+ },
+ "required": true,
+ },
+ },
+}
+`;
diff --git a/src/__tests__/fixtures/component_27.tsx b/src/__tests__/fixtures/component_27.tsx
index ea49e8b716d..eb5cc2f3fed 100644
--- a/src/__tests__/fixtures/component_27.tsx
+++ b/src/__tests__/fixtures/component_27.tsx
@@ -8,7 +8,7 @@
import React, { Component } from 'react';
-interface Props {
+export interface Props {
foo: string
}
diff --git a/src/__tests__/fixtures/component_33.tsx b/src/__tests__/fixtures/component_33.tsx
new file mode 100644
index 00000000000..0be4a2226a9
--- /dev/null
+++ b/src/__tests__/fixtures/component_33.tsx
@@ -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_39';
+
+/**
+ * This is a typescript component with imported prop types
+ */
+export function ImportedExtendedComponent(props: ExtendedProps) {
+ return
Hello world ;
+}
diff --git a/src/__tests__/fixtures/component_34.js b/src/__tests__/fixtures/component_34.js
new file mode 100644
index 00000000000..4b227a5bc62
--- /dev/null
+++ b/src/__tests__/fixtures/component_34.js
@@ -0,0 +1,13 @@
+import Button from './component_6';
+import PropTypes from 'prop-types';
+
+export function CustomButton({color, ...otherProps}) {
+ return ;
+}
+
+CustomButton.propTypes = {
+ ...Button.propTypes,
+ color: PropTypes.string
+};
+
+export const sharedProps = Button.propTypes;
diff --git a/src/__tests__/fixtures/component_35.js b/src/__tests__/fixtures/component_35.js
new file mode 100644
index 00000000000..dfd50235b2f
--- /dev/null
+++ b/src/__tests__/fixtures/component_35.js
@@ -0,0 +1,10 @@
+import {CustomButton, sharedProps} from './component_34';
+import PropTypes from 'prop-types';
+
+export function SuperCustomButton({color, ...otherProps}) {
+ return ;
+}
+
+SuperCustomButton.propTypes = sharedProps;
+export {sharedProps};
+export * from './component_36';
diff --git a/src/__tests__/fixtures/component_36.js b/src/__tests__/fixtures/component_36.js
new file mode 100644
index 00000000000..a27b2978d31
--- /dev/null
+++ b/src/__tests__/fixtures/component_36.js
@@ -0,0 +1,9 @@
+import {SuperCustomButton, sharedProps} from './component_35';
+import PropTypes from 'prop-types';
+
+export function SuperDuperCustomButton({color, ...otherProps}) {
+ return ;
+}
+
+SuperDuperCustomButton.propTypes = sharedProps;
+export * from './component_35';
diff --git a/src/__tests__/fixtures/component_37.js b/src/__tests__/fixtures/component_37.js
new file mode 100644
index 00000000000..6ccf696e961
--- /dev/null
+++ b/src/__tests__/fixtures/component_37.js
@@ -0,0 +1,9 @@
+import * as C36 from './component_36';
+import PropTypes from 'prop-types';
+
+export function SuperDuperCustomButton({color, ...otherProps}) {
+ return ;
+}
+
+SuperDuperCustomButton.propTypes = C36.sharedProps;
+export {SuperCustomButton} from './component_36';
diff --git a/src/__tests__/fixtures/component_38.js b/src/__tests__/fixtures/component_38.js
new file mode 100644
index 00000000000..c73fde28d1b
--- /dev/null
+++ b/src/__tests__/fixtures/component_38.js
@@ -0,0 +1,8 @@
+import {SuperCustomButton} from './component_37';
+import PropTypes from 'prop-types';
+
+export function SuperDuperCustomButton({color, ...otherProps}) {
+ return ;
+}
+
+SuperDuperCustomButton.propTypes = SuperCustomButton.propTypes;
diff --git a/src/__tests__/fixtures/component_39.tsx b/src/__tests__/fixtures/component_39.tsx
new file mode 100644
index 00000000000..69265650558
--- /dev/null
+++ b/src/__tests__/fixtures/component_39.tsx
@@ -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 Hello world ;
+}
diff --git a/src/__tests__/fixtures/component_40.js b/src/__tests__/fixtures/component_40.js
new file mode 100644
index 00000000000..f9d7480923d
--- /dev/null
+++ b/src/__tests__/fixtures/component_40.js
@@ -0,0 +1,16 @@
+import { Sizes } from '../common';
+import T from 'prop-types';
+
+function SuperDuperCustomButton() {
+ return
;
+}
+
+SuperDuperCustomButton.defaultProps = {
+ size: Sizes.EXTRA_LARGE,
+};
+
+SuperDuperCustomButton.propTypes = {
+ size: T.oneOf(Object.values(Sizes)),
+};
+
+export default SuperDuperCustomButton;
diff --git a/src/__tests__/fixtures/flow-export-type.js b/src/__tests__/fixtures/flow-export-type.js
new file mode 100644
index 00000000000..1c37b8700ef
--- /dev/null
+++ b/src/__tests__/fixtures/flow-export-type.js
@@ -0,0 +1,31 @@
+/**
+ * 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 React, { Component } from 'react';
+
+export type Props = {
+ foo: string
+}
+
+/**
+ * This is a Flow class component
+ */
+export default class FlowComponent extends Component {
+ render() {
+ return Hello world ;
+ }
+
+ foo(a: string): string {
+ return a;
+ }
+
+ bar(a: string): string {
+ return a;
+ }
+}
diff --git a/src/__tests__/fixtures/flow-import-type.js b/src/__tests__/fixtures/flow-import-type.js
new file mode 100644
index 00000000000..42f3dd3d809
--- /dev/null
+++ b/src/__tests__/fixtures/flow-import-type.js
@@ -0,0 +1,23 @@
+/**
+ * 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 React, { Component } from 'react';
+import type { Props as ImportedProps } from './flow-export-type';
+
+export type ExtendedProps = {
+ ...ImportedProps,
+ bar: number
+}
+
+/**
+ * This is a Flow component with imported prop types
+ */
+export function ImportedComponent(props: ImportedProps) {
+ return Hello world ;
+}
diff --git a/src/__tests__/fixtures/flow-spread-import-type.js b/src/__tests__/fixtures/flow-spread-import-type.js
new file mode 100644
index 00000000000..9bf5ce6d044
--- /dev/null
+++ b/src/__tests__/fixtures/flow-spread-import-type.js
@@ -0,0 +1,18 @@
+/**
+ * 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 React, { Component } from 'react';
+import type {ExtendedProps} from './flow-import-type';
+
+/**
+ * This is a Flow component with imported prop types
+ */
+export function ImportedExtendedComponent(props: ExtendedProps) {
+ return Hello world ;
+}
diff --git a/src/__tests__/main-test.js b/src/__tests__/main-test.js
index 588311d404b..d02e28297c9 100644
--- a/src/__tests__/main-test.js
+++ b/src/__tests__/main-test.js
@@ -8,7 +8,7 @@
import fs from 'fs';
import path from 'path';
-import { handlers, parse } from '../main';
+import { handlers, parse, importers } from '../main';
import { ERROR_MISSING_DEFINITION } from '../parse';
describe('main', () => {
@@ -216,6 +216,8 @@ describe('main', () => {
});
});
+ // Fixures uses the filesystem importer for easier e2e tests
+ // even though it is not the default
describe('fixtures', () => {
const fixturePath = path.join(__dirname, 'fixtures');
const fileNames = fs.readdirSync(fixturePath);
@@ -227,6 +229,7 @@ describe('main', () => {
let result;
expect(() => {
result = parse(fileContent, null, null, {
+ importer: importers.makeFsImporter(),
filename: filePath,
babelrc: false,
});
diff --git a/src/__tests__/parse-test.js b/src/__tests__/parse-test.js
index 75d07628c7c..4928bc1d984 100644
--- a/src/__tests__/parse-test.js
+++ b/src/__tests__/parse-test.js
@@ -8,7 +8,7 @@
import fs from 'fs';
import temp from 'temp';
-import { expression } from '../../tests/utils';
+import { expression, noopImporter } from '../../tests/utils';
import parse, { ERROR_MISSING_DEFINITION } from '../parse';
describe('parse', () => {
@@ -16,7 +16,7 @@ describe('parse', () => {
const path = expression('{foo: "bar"}');
const resolver = jest.fn(() => path);
const handler = jest.fn();
- parse('//empty', resolver, [handler]);
+ parse('//empty', resolver, [handler], noopImporter);
expect(resolver).toBeCalled();
expect(handler.mock.calls[0][1]).toBe(path);
@@ -24,12 +24,12 @@ describe('parse', () => {
it('errors if component definition is not found', () => {
const resolver = jest.fn();
- expect(() => parse('//empty', resolver)).toThrowError(
+ expect(() => parse('//empty', resolver, [], noopImporter)).toThrowError(
ERROR_MISSING_DEFINITION,
);
expect(resolver).toBeCalled();
- expect(() => parse('//empty', resolver)).toThrowError(
+ expect(() => parse('//empty', resolver, [], noopImporter)).toThrowError(
ERROR_MISSING_DEFINITION,
);
expect(resolver).toBeCalled();
@@ -43,7 +43,7 @@ describe('parse', () => {
fs.writeFileSync(`${dir}/.babelrc`, '{}');
expect(() =>
- parse('const chained = () => a |> b', () => {}, null, {
+ parse('const chained = () => a |> b', () => {}, [], noopImporter, {
cwd: dir,
filename: `${dir}/component.js`,
}),
@@ -58,7 +58,7 @@ describe('parse', () => {
it('supports custom parserOptions with plugins', () => {
expect(() =>
- parse('const chained: Type = 1;', () => {}, null, {
+ parse('const chained: Type = 1;', () => {}, [], noopImporter, {
parserOptions: {
plugins: [
// no flow
@@ -71,7 +71,7 @@ describe('parse', () => {
it('supports custom parserOptions without plugins', () => {
expect(() =>
- parse('const chained: Type = 1;', () => {}, null, {
+ parse('const chained: Type = 1;', () => {}, [], noopImporter, {
parserOptions: {
allowSuperOutsideMethod: true,
},
diff --git a/src/babelParser.js b/src/babelParser.js
index 45477c73290..872ff6a452c 100644
--- a/src/babelParser.js
+++ b/src/babelParser.js
@@ -66,8 +66,9 @@ type BabelOptions = {
babelrcRoots?: true | string | string[],
};
-export type Options = BabelOptions & {
- parserOptions?: ParserOptions,
+export type Options = {
+ ...$Exact,
+ ...{| parserOptions?: ParserOptions |},
};
function buildOptions(
@@ -101,13 +102,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;
},
};
}
diff --git a/src/handlers/__tests__/__snapshots__/componentMethodsHandler-test.js.snap b/src/handlers/__tests__/__snapshots__/componentMethodsHandler-test.js.snap
index 1a7e29fd661..30016d48f9c 100644
--- a/src/handlers/__tests__/__snapshots__/componentMethodsHandler-test.js.snap
+++ b/src/handlers/__tests__/__snapshots__/componentMethodsHandler-test.js.snap
@@ -69,6 +69,72 @@ Array [
]
`;
+exports[`componentMethodsHandler function components resolves imported methods assigned on a component in an assignment 1`] = `
+Array [
+ Object {
+ "docblock": null,
+ "modifiers": Array [
+ "static",
+ ],
+ "name": "doFoo",
+ "params": Array [],
+ "returns": null,
+ },
+]
+`;
+
+exports[`componentMethodsHandler function components resolves imported methods assigned to static properties on a component 1`] = `
+Array [
+ Object {
+ "docblock": null,
+ "modifiers": Array [
+ "static",
+ ],
+ "name": "doFoo",
+ "params": Array [],
+ "returns": null,
+ },
+]
+`;
+
+exports[`componentMethodsHandler function components resolves imported methods on a function declaration 1`] = `
+Array [
+ Object {
+ "docblock": null,
+ "modifiers": Array [
+ "static",
+ ],
+ "name": "doFoo",
+ "params": Array [],
+ "returns": null,
+ },
+]
+`;
+
+exports[`componentMethodsHandler resolves imported methods assigned to computed properties 1`] = `
+Array [
+ Object {
+ "docblock": "The foo method",
+ "modifiers": Array [],
+ "name": "@computed#foo",
+ "params": Array [
+ Object {
+ "name": "bar",
+ "optional": undefined,
+ "type": Object {
+ "name": "number",
+ },
+ },
+ ],
+ "returns": Object {
+ "type": Object {
+ "name": "number",
+ },
+ },
+ },
+]
+`;
+
exports[`componentMethodsHandler should handle and ignore computed methods 1`] = `
Array [
Object {
diff --git a/src/handlers/__tests__/__snapshots__/defaultPropsHandler-test.js.snap b/src/handlers/__tests__/__snapshots__/defaultPropsHandler-test.js.snap
index e0803b09162..f77a409b805 100644
--- a/src/handlers/__tests__/__snapshots__/defaultPropsHandler-test.js.snap
+++ b/src/handlers/__tests__/__snapshots__/defaultPropsHandler-test.js.snap
@@ -1,5 +1,62 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`defaultPropsHandler ClassDeclaration with static defaultProps can resolve default props that are imported given a custom importer 1`] = `
+Object {
+ "baz": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler ClassDeclaration with static defaultProps resolves imported spreads 1`] = `
+Object {
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ },
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler ClassDeclaration with static defaultProps resolves imported values assigned as default props 1`] = `
+Object {
+ "abc": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "{xyz: abc.def, 123: 42}",
+ },
+ },
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "42",
+ },
+ },
+ "baz": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
exports[`defaultPropsHandler ClassDeclaration with static defaultProps should find prop default values that are imported variables 1`] = `
Object {
"foo": Object {
@@ -57,6 +114,35 @@ Object {
}
`;
+exports[`defaultPropsHandler ClassExpression with static defaultProps resolves imported values assigned as default props 1`] = `
+Object {
+ "abc": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "{xyz: abc.def, 123: 42}",
+ },
+ },
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "42",
+ },
+ },
+ "baz": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
exports[`defaultPropsHandler ClassExpression with static defaultProps should find prop default values that are literals 1`] = `
Object {
"abc": Object {
@@ -86,6 +172,56 @@ Object {
}
`;
+exports[`defaultPropsHandler Functional components with default params allows imported defaults to be aliased 1`] = `
+Object {
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler Functional components with default params can use imported values as default props 1`] = `
+Object {
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler Functional components with default params overrides with imported defaultProps 1`] = `
+Object {
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler Functional components with default params resolves imported spreads 1`] = `
+Object {
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ },
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
exports[`defaultPropsHandler Functional components with default params resolves local spreads 1`] = `
Object {
"bar": Object {
@@ -203,6 +339,35 @@ Object {
exports[`defaultPropsHandler Functional components with default params should work with no defaults 1`] = `Object {}`;
+exports[`defaultPropsHandler ObjectExpression can resolve declared functions 1`] = `
+Object {
+ "abc": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "{xyz: abc.def, 123: 42}",
+ },
+ },
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "42",
+ },
+ },
+ "baz": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
exports[`defaultPropsHandler ObjectExpression handles computed properties 1`] = `
Object {
"@computed#bar": Object {
@@ -220,6 +385,23 @@ Object {
}
`;
+exports[`defaultPropsHandler ObjectExpression handles imported values assigned to computed properties 1`] = `
+Object {
+ "@computed#bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
exports[`defaultPropsHandler ObjectExpression ignores complex computed properties 1`] = `
Object {
"foo": Object {
@@ -231,6 +413,34 @@ Object {
}
`;
+exports[`defaultPropsHandler ObjectExpression ignores imported values assigned to complex computed properties 1`] = `
+Object {
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler ObjectExpression resolves imported spreads 1`] = `
+Object {
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ },
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
exports[`defaultPropsHandler ObjectExpression resolves local spreads 1`] = `
Object {
"bar": Object {
@@ -277,6 +487,69 @@ Object {
}
`;
+exports[`defaultPropsHandler ObjectExpression should find prop default values that are literals from imported functions 1`] = `
+Object {
+ "abc": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "{xyz: abc.def, 123: 42}",
+ },
+ },
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "42",
+ },
+ },
+ "baz": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+ "foo": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler can have an importer that resolves spread properties 1`] = `
+Object {
+ "123": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "42",
+ },
+ },
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "42",
+ },
+ },
+ "xyz": Object {
+ "defaultValue": Object {
+ "computed": true,
+ "value": "abc.def",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler forwardRef also resolves imports when the function is not inline 1`] = `
+Object {
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+}
+`;
+
exports[`defaultPropsHandler forwardRef resolves default props in the parameters 1`] = `
Object {
"foo": Object {
@@ -299,6 +572,28 @@ Object {
}
`;
+exports[`defaultPropsHandler forwardRef resolves imported default props in the parameters 1`] = `
+Object {
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "[\\"foo\\", \\"bar\\"]",
+ },
+ },
+}
+`;
+
+exports[`defaultPropsHandler forwardRef resolves imported defaultProps 1`] = `
+Object {
+ "bar": Object {
+ "defaultValue": Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ },
+}
+`;
+
exports[`defaultPropsHandler forwardRef resolves when the function is not inline 1`] = `
Object {
"foo": Object {
diff --git a/src/handlers/__tests__/__snapshots__/flowTypeHandler-test.js.snap b/src/handlers/__tests__/__snapshots__/flowTypeHandler-test.js.snap
index 6d6c30da71f..83ae155a99d 100644
--- a/src/handlers/__tests__/__snapshots__/flowTypeHandler-test.js.snap
+++ b/src/handlers/__tests__/__snapshots__/flowTypeHandler-test.js.snap
@@ -59,3 +59,73 @@ Object {
},
}
`;
+
+exports[`flowTypeHandler imported prop types imported 1`] = `
+Object {
+ "abc": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+ "bar": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": false,
+ },
+ "def": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+ "foo": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+ "foobar": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+ "hal": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+}
+`;
+
+exports[`flowTypeHandler imported prop types type imported 1`] = `
+Object {
+ "abc": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+ "bar": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": false,
+ },
+ "def": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+ "foo": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+ "foobar": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+ "hal": Object {
+ "description": "",
+ "flowType": Object {},
+ "required": true,
+ },
+}
+`;
diff --git a/src/handlers/__tests__/__snapshots__/propTypeHandler-test.js.snap b/src/handlers/__tests__/__snapshots__/propTypeHandler-test.js.snap
index 59d2b63e348..d7f6922d8f8 100644
--- a/src/handlers/__tests__/__snapshots__/propTypeHandler-test.js.snap
+++ b/src/handlers/__tests__/__snapshots__/propTypeHandler-test.js.snap
@@ -1,5 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`propTypeHandler React.createClass can resolve individual imported variables assigned to props 1`] = `
+Object {
+ "@computed#bar": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "baz": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "complex_prop": Object {
+ "required": true,
+ "type": Object {},
+ },
+ "foo": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "simple_prop": Object {
+ "required": true,
+ "type": Object {},
+ },
+}
+`;
+
exports[`propTypeHandler React.createClass handles computed properties 1`] = `
Object {
"@computed#foo": Object {
@@ -22,6 +47,31 @@ Object {
}
`;
+exports[`propTypeHandler class definition class property can resolve individual imported variables assigned to props 1`] = `
+Object {
+ "@computed#bar": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "baz": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "complex_prop": Object {
+ "required": true,
+ "type": Object {},
+ },
+ "foo": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "simple_prop": Object {
+ "required": true,
+ "type": Object {},
+ },
+}
+`;
+
exports[`propTypeHandler class definition class property handles computed properties 1`] = `
Object {
"@computed#foo": Object {
@@ -44,6 +94,31 @@ Object {
}
`;
+exports[`propTypeHandler class definition static getter can resolve individual imported variables assigned to props 1`] = `
+Object {
+ "@computed#bar": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "baz": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "complex_prop": Object {
+ "required": true,
+ "type": Object {},
+ },
+ "foo": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "simple_prop": Object {
+ "required": true,
+ "type": Object {},
+ },
+}
+`;
+
exports[`propTypeHandler class definition static getter handles computed properties 1`] = `
Object {
"@computed#foo": Object {
@@ -66,6 +141,31 @@ Object {
}
`;
+exports[`propTypeHandler stateless component can resolve individual imported variables assigned to props 1`] = `
+Object {
+ "@computed#bar": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "baz": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "complex_prop": Object {
+ "required": true,
+ "type": Object {},
+ },
+ "foo": Object {
+ "required": false,
+ "type": Object {},
+ },
+ "simple_prop": Object {
+ "required": true,
+ "type": Object {},
+ },
+}
+`;
+
exports[`propTypeHandler stateless component handles computed properties 1`] = `
Object {
"@computed#foo": Object {
diff --git a/src/handlers/__tests__/componentDocblockHandler-test.js b/src/handlers/__tests__/componentDocblockHandler-test.js
index 61952acc5ef..f1bd5cb5fb8 100644
--- a/src/handlers/__tests__/componentDocblockHandler-test.js
+++ b/src/handlers/__tests__/componentDocblockHandler-test.js
@@ -8,7 +8,7 @@
jest.mock('../../Documentation');
-import { parse } from '../../../tests/utils';
+import { parse, noopImporter, makeMockImporter } from '../../../tests/utils';
describe('componentDocblockHandler', () => {
let documentation;
@@ -29,7 +29,7 @@ describe('componentDocblockHandler', () => {
componentDocblockHandler = require('../componentDocblockHandler').default;
});
- function test(definitionSrc, parseFunc) {
+ function test(definitionSrc, parseFunc: string => NodePath) {
it('finds docblocks for component definitions', () => {
const definition = parseFunc(`
import something from 'somewhere';
@@ -40,7 +40,7 @@ describe('componentDocblockHandler', () => {
${definitionSrc}
`);
- componentDocblockHandler(documentation, definition);
+ componentDocblockHandler(documentation, definition, noopImporter);
expect(documentation.description).toBe('Component description');
});
@@ -54,7 +54,7 @@ describe('componentDocblockHandler', () => {
${definitionSrc}
`);
- componentDocblockHandler(documentation, definition);
+ componentDocblockHandler(documentation, definition, noopImporter);
expect(documentation.description).toBe('');
definition = parseFunc(`
@@ -62,7 +62,7 @@ describe('componentDocblockHandler', () => {
${definitionSrc}
`);
- componentDocblockHandler(documentation, definition);
+ componentDocblockHandler(documentation, definition, noopImporter);
expect(documentation.description).toBe('');
});
@@ -77,7 +77,7 @@ describe('componentDocblockHandler', () => {
${definitionSrc}
`);
- componentDocblockHandler(documentation, definition);
+ componentDocblockHandler(documentation, definition, noopImporter);
expect(documentation.description).toBe('');
});
}
@@ -100,7 +100,7 @@ describe('componentDocblockHandler', () => {
${classSrc}
`);
- componentDocblockHandler(documentation, definition);
+ componentDocblockHandler(documentation, definition, noopImporter);
expect(documentation.description).toBe('Component description');
});
@@ -120,33 +120,106 @@ describe('componentDocblockHandler', () => {
${classSrc}
`);
- componentDocblockHandler(documentation, definition);
+ componentDocblockHandler(documentation, definition, noopImporter);
expect(documentation.description).toBe('Component description');
});
});
}
+ function testImports(exportSrc, parseFunc, importName, useDefault = false) {
+ const importDef = useDefault ? `${importName}` : `{ ${importName} }`;
+
+ const mockImporter = makeMockImporter({
+ test1: parseFunc(`
+ /**
+ * Component description
+ */
+ ${exportSrc}
+ `),
+
+ test2: lastStatement(`
+ import ${importDef} from 'test1';
+
+ export default ${importName};
+ `).get('declaration'),
+ });
+
+ describe('imports', () => {
+ it('can use a custom importer to resolve docblocks on imported components', () => {
+ const program = lastStatement(`
+ import ${importDef} from 'test1';
+
+ export default ${importName};
+ `).get('declaration');
+
+ componentDocblockHandler(documentation, program, mockImporter);
+ expect(documentation.description).toBe('Component description');
+ });
+ });
+
+ it('traverses multiple imports', () => {
+ const program = lastStatement(`
+ import ${importDef} from 'test2';
+
+ export default ${importName};
+ `).get('declaration');
+
+ componentDocblockHandler(documentation, program, mockImporter);
+ expect(documentation.description).toBe('Component description');
+ });
+ }
+
describe('React.createClass', () => {
test('var Component = React.createClass({})', src =>
lastStatement(src).get('declarations', 0, 'init', 'arguments', 0));
+ testImports(
+ 'export var Component = React.createClass({})',
+ src => lastStatement(src).get('declaration'),
+ 'Component',
+ );
});
describe('ClassDeclaration', () => {
test('class Component {}', src => lastStatement(src));
testDecorators('class Component {}', src => lastStatement(src));
+ testImports(
+ 'export class Component {}',
+ src => lastStatement(src).get('declaration'),
+ 'Component',
+ );
});
describe('ClassExpression', () => {
test('var Component = class {};', src =>
lastStatement(src).get('declarations', 0, 'init'));
+ testImports(
+ 'export var Component = class {};',
+ src => lastStatement(src).get('declaration'),
+ 'Component',
+ );
});
describe('Stateless functions', () => {
test('function Component() {}', src => lastStatement(src));
+ testImports(
+ 'export function Component() {}',
+ src => lastStatement(src).get('declaration'),
+ 'Component',
+ );
test('var Component = function () {};', src =>
lastStatement(src).get('declarations', 0, 'init'));
+ testImports(
+ 'export var Component = function () {};',
+ src => lastStatement(src).get('declaration'),
+ 'Component',
+ );
test('var Component = () => {}', src =>
lastStatement(src).get('declarations', 0, 'init'));
+ testImports(
+ 'export var Component = () => {}',
+ src => lastStatement(src).get('declaration'),
+ 'Component',
+ );
});
describe('ES6 default exports', () => {
@@ -235,24 +308,39 @@ describe('componentDocblockHandler', () => {
});
describe('forwardRef', () => {
+ const useDefault = true;
+
describe('inline implementation', () => {
- test(
- [
- 'React.forwardRef((props, ref) => {});',
- 'import React from "react";',
- ].join('\n'),
- src => beforeLastStatement(src).get('expression'),
+ test(`
+ React.forwardRef((props, ref) => {});
+ import React from "react";`, src =>
+ beforeLastStatement(src).get('expression'));
+
+ testImports(
+ `
+ export default React.forwardRef((props, ref) => {});
+ import React from 'react';`,
+ src => beforeLastStatement(src).get('declaration'),
+ 'RefComponent',
+ useDefault,
);
});
describe('out of line implementation', () => {
- test(
- [
- 'let Component = (props, ref) => {};',
- 'React.forwardRef(Component);',
- 'import React from "react";',
- ].join('\n'),
- src => beforeLastStatement(src).get('expression'),
+ test(`let Component = (props, ref) => {};
+ React.forwardRef(Component);
+ import React from "react";
+ `, src => beforeLastStatement(src).get('expression'));
+
+ testImports(
+ `
+ let Component = (props, ref) => {};
+ export default React.forwardRef(Component);
+ import React from 'react';
+ `,
+ src => beforeLastStatement(src).get('declaration'),
+ `RefComponent`,
+ useDefault,
);
});
});
diff --git a/src/handlers/__tests__/componentMethodsHandler-test.js b/src/handlers/__tests__/componentMethodsHandler-test.js
index da27ef77ab0..30051102dd8 100644
--- a/src/handlers/__tests__/componentMethodsHandler-test.js
+++ b/src/handlers/__tests__/componentMethodsHandler-test.js
@@ -8,7 +8,7 @@
jest.mock('../../Documentation');
-import { parse } from '../../../tests/utils';
+import { parse, noopImporter, makeMockImporter } from '../../../tests/utils';
describe('componentMethodsHandler', () => {
let documentation;
@@ -19,8 +19,24 @@ describe('componentMethodsHandler', () => {
componentMethodsHandler = require('../componentMethodsHandler').default;
});
- function test(definition) {
- componentMethodsHandler(documentation, definition);
+ const mockImporter = makeMockImporter({
+ baz: parse(`
+ export default (foo: string): string => {};
+ `).get('body', 0, 'declaration'),
+
+ foo: parse(`
+ export default function(bar: number): number {
+ return bar;
+ }
+ `).get('body', 0, 'declaration'),
+
+ doFoo: parse(`
+ export default () => {};
+ `).get('body', 0, 'declaration'),
+ });
+
+ function test(definition, importer) {
+ componentMethodsHandler(documentation, definition, importer);
expect(documentation.methods).toEqual([
{
name: 'foo',
@@ -89,7 +105,40 @@ describe('componentMethodsHandler', () => {
})
`;
- test(parse(src).get('body', 0, 'expression'));
+ test(parse(src).get('body', 0, 'expression'), noopImporter);
+ });
+
+ it('can resolve an imported method on an ObjectExpression', () => {
+ const src = `
+ import baz from 'baz';
+ ({
+ /**
+ * The foo method
+ */
+ foo(bar: number): number {
+ return bar;
+ },
+ /**
+ * "arrow function method"
+ */
+ baz: baz,
+ statics: {
+ /**
+ * Static function
+ */
+ bar() {}
+ },
+ state: {
+ foo: 'foo',
+ },
+ componentDidMount() {},
+ render() {
+ return null;
+ },
+ })
+ `;
+
+ test(parse(src).get('body', 1, 'expression'), mockImporter);
});
it('extracts the documentation for a ClassDeclaration', () => {
@@ -124,7 +173,43 @@ describe('componentMethodsHandler', () => {
}
`;
- test(parse(src).get('body', 0));
+ test(parse(src).get('body', 0), noopImporter);
+ });
+
+ it('can resolve an imported method on a ClassDeclaration', () => {
+ const src = `
+ import baz from 'baz';
+ class Test extends React.Component {
+ /**
+ * The foo method
+ */
+ foo(bar: number): number {
+ return bar;
+ }
+
+ /**
+ * "arrow function method"
+ */
+ baz = baz;
+
+ /**
+ * Static function
+ */
+ static bar() {}
+
+ state = {
+ foo: 'foo',
+ };
+
+ componentDidMount() {}
+
+ render() {
+ return null;
+ }
+ }
+ `;
+
+ test(parse(src).get('body', 1), mockImporter);
});
it('should handle and ignore computed methods', () => {
@@ -152,7 +237,43 @@ describe('componentMethodsHandler', () => {
}
`;
- componentMethodsHandler(documentation, parse(src).get('body', 0));
+ componentMethodsHandler(
+ documentation,
+ parse(src).get('body', 0),
+ noopImporter,
+ );
+ expect(documentation.methods).toMatchSnapshot();
+ });
+
+ it('resolves imported methods assigned to computed properties', () => {
+ const src = `
+ import foo from 'foo';
+ class Test extends React.Component {
+ /**
+ * The foo method
+ */
+ [foo] = foo;
+
+ /**
+ * Should not show up
+ */
+ [() => {}](bar: number): number {
+ return bar;
+ }
+
+ componentDidMount() {}
+
+ render() {
+ return null;
+ }
+ }
+ `;
+
+ componentMethodsHandler(
+ documentation,
+ parse(src).get('body', 1),
+ mockImporter,
+ );
expect(documentation.methods).toMatchSnapshot();
});
@@ -163,7 +284,7 @@ describe('componentMethodsHandler', () => {
`;
const definition = parse(src).get('body', 0, 'expression');
- componentMethodsHandler(documentation, definition);
+ componentMethodsHandler(documentation, definition, noopImporter);
expect(documentation.methods).toEqual([]);
});
@@ -175,8 +296,26 @@ describe('componentMethodsHandler', () => {
Test.displayName = 'Test'; // Not a method
`;
- const definition = parse(src).get('body', 0, 'declarations', 0, 'init');
- componentMethodsHandler(documentation, definition);
+ componentMethodsHandler(
+ documentation,
+ parse(src).get('body', 0, 'declarations', 0, 'init'),
+ noopImporter,
+ );
+ expect(documentation.methods).toMatchSnapshot();
+ });
+
+ it('resolves imported methods assigned to static properties on a component', () => {
+ const src = `
+ const Test = (props) => {};
+ import doFoo from 'doFoo';
+ Test.doFoo = doFoo;
+ `;
+
+ componentMethodsHandler(
+ documentation,
+ parse(src).get('body', 0, 'declarations', 0, 'init'),
+ mockImporter,
+ );
expect(documentation.methods).toMatchSnapshot();
});
@@ -188,8 +327,26 @@ describe('componentMethodsHandler', () => {
Test.displayName = 'Test'; // Not a method
`;
- const definition = parse(src).get('body', 0, 'expression', 'right');
- componentMethodsHandler(documentation, definition);
+ componentMethodsHandler(
+ documentation,
+ parse(src).get('body', 0, 'expression', 'right'),
+ noopImporter,
+ );
+ expect(documentation.methods).toMatchSnapshot();
+ });
+
+ it('resolves imported methods assigned on a component in an assignment', () => {
+ const src = `
+ Test = (props) => {};
+ import doFoo from 'doFoo';
+ Test.doFoo = doFoo;
+ `;
+
+ componentMethodsHandler(
+ documentation,
+ parse(src).get('body', 0, 'expression', 'right'),
+ mockImporter,
+ );
expect(documentation.methods).toMatchSnapshot();
});
@@ -201,8 +358,26 @@ describe('componentMethodsHandler', () => {
Test.displayName = 'Test'; // Not a method
`;
- const definition = parse(src).get('body', 0);
- componentMethodsHandler(documentation, definition);
+ componentMethodsHandler(
+ documentation,
+ parse(src).get('body', 0),
+ noopImporter,
+ );
+ expect(documentation.methods).toMatchSnapshot();
+ });
+
+ it('resolves imported methods on a function declaration', () => {
+ const src = `
+ function Test(props) {}
+ import doFoo from 'doFoo';
+ Test.doFoo = doFoo;
+ `;
+
+ componentMethodsHandler(
+ documentation,
+ parse(src).get('body', 0),
+ mockImporter,
+ );
expect(documentation.methods).toMatchSnapshot();
});
});
diff --git a/src/handlers/__tests__/defaultPropsHandler-test.js b/src/handlers/__tests__/defaultPropsHandler-test.js
index a2e739a9066..d2a48998f91 100644
--- a/src/handlers/__tests__/defaultPropsHandler-test.js
+++ b/src/handlers/__tests__/defaultPropsHandler-test.js
@@ -8,7 +8,7 @@
jest.mock('../../Documentation');
-import { parse } from '../../../tests/utils';
+import { parse, noopImporter, makeMockImporter } from '../../../tests/utils';
describe('defaultPropsHandler', () => {
let documentation;
@@ -19,6 +19,37 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler = require('../defaultPropsHandler').default;
});
+ const mockImporter = makeMockImporter({
+ getDefaultProps: parse(`
+ import baz from 'baz';
+ export default function() {
+ return {
+ foo: "bar",
+ bar: 42,
+ baz: baz,
+ abc: {xyz: abc.def, 123: 42}
+ };
+ }
+ `).get('body', 1, 'declaration'),
+
+ baz: parse(`
+ export default ["foo", "bar"];
+ `).get('body', 0, 'declaration'),
+
+ other: parse(`
+ export default { bar: "foo" };
+ `).get('body', 0, 'declaration'),
+
+ defaultProps: parse(`
+ export default {
+ foo: "bar",
+ bar: 42,
+ baz: ["foo", "bar"],
+ abc: {xyz: abc.def, 123: 42}
+ };
+ `).get('body', 0, 'declaration'),
+ });
+
describe('ObjectExpression', () => {
it('should find prop default values that are literals', () => {
const src = `
@@ -36,6 +67,45 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 0, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('can resolve declared functions', () => {
+ const src = `
+ function getDefaultProps() {
+ return {
+ foo: "bar",
+ bar: 42,
+ baz: ["foo", "bar"],
+ abc: {xyz: abc.def, 123: 42}
+ };
+ }
+ ({
+ getDefaultProps: getDefaultProps
+ })
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('should find prop default values that are literals from imported functions', () => {
+ const src = `
+ import getDefaultProps from 'getDefaultProps';
+
+ ({
+ getDefaultProps: getDefaultProps
+ })
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'expression'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -54,6 +124,27 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 0, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('handles imported values assigned to computed properties', () => {
+ const src = `
+ import baz from 'baz';
+ ({
+ getDefaultProps: function() {
+ return {
+ foo: "bar",
+ [bar]: baz,
+ };
+ }
+ })
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'expression'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -72,6 +163,27 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 0, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('ignores imported values assigned to complex computed properties', () => {
+ const src = `
+ import baz from 'baz';
+ ({
+ getDefaultProps: function() {
+ return {
+ foo: "bar",
+ [() => {}]: baz,
+ };
+ }
+ })
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'expression'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -92,6 +204,27 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 1, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('resolves imported spreads', () => {
+ const src = `
+ import other from 'other';
+ ({
+ getDefaultProps: function() {
+ return {
+ foo: "bar",
+ ...other,
+ };
+ }
+ })
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'expression'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -109,7 +242,26 @@ describe('defaultPropsHandler', () => {
};
}
`;
- defaultPropsHandler(documentation, parse(src).get('body', 0));
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 0),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('resolves imported values assigned as default props', () => {
+ const src = `
+ import defaultProps from 'defaultProps';
+ class Foo {
+ static defaultProps = defaultProps;
+ }
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1),
+ mockImporter,
+ );
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -124,7 +276,29 @@ describe('defaultPropsHandler', () => {
};
}
`;
- defaultPropsHandler(documentation, parse(src).get('body', 1));
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('resolves imported spreads', () => {
+ const src = `
+ import other from 'other';
+ class Foo {
+ static defaultProps = {
+ foo: "bar",
+ ...other
+ };
+ }
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1),
+ mockImporter,
+ );
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -138,7 +312,29 @@ describe('defaultPropsHandler', () => {
};
}
`;
- defaultPropsHandler(documentation, parse(src).get('body', 1));
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('can resolve default props that are imported given a custom importer', () => {
+ const src = `
+ import baz from 'baz';
+
+ class Foo {
+ static defaultProps = {
+ baz: baz,
+ };
+ }
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1),
+ mockImporter,
+ );
expect(documentation.descriptors).toMatchSnapshot();
});
});
@@ -157,6 +353,22 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 0, 'declarations', 0, 'init'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('resolves imported values assigned as default props', () => {
+ const src = `
+ import defaultProps from 'defaultProps';
+ var Bar = class {
+ static defaultProps = defaultProps;
+ }
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'declarations', 0, 'init'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -174,7 +386,28 @@ describe('defaultPropsHandler', () => {
})
`;
const definition = parse(src).get('body', 0, 'expression');
- expect(() => defaultPropsHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ defaultPropsHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('can have an importer that resolves spread properties', () => {
+ const src = `
+ import Props from 'defaultProps';
+ ({
+ getDefaultProps: function() {
+ return {
+ ...Props.abc,
+ bar: 42,
+ };
+ }
+ })
+ `;
+ const definition = parse(src).get('body', 1, 'expression');
+ expect(() =>
+ defaultPropsHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -191,6 +424,22 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 0, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('can use imported values as default props', () => {
+ const src = `
+ import baz from 'baz';
+ ({
+ bar = baz,
+ }) =>
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'expression'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -208,6 +457,23 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 0, 'declarations', 0, 'init'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('overrides with imported defaultProps', () => {
+ const src = `
+ import other from 'other';
+ var Foo = ({
+ bar = 42,
+ }) =>
+ Foo.defaultProps = other;
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'declarations', 0, 'init'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -221,6 +487,21 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 1, 'declarations', 0, 'init'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('resolves imported spreads', () => {
+ const src = `
+ import other from 'other';
+ var Foo = (props) =>
+ Foo.defaultProps = { foo: "bar", ...other };
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'declarations', 0, 'init'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -237,6 +518,22 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 0, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('allows imported defaults to be aliased', () => {
+ const src = `
+ import baz from 'baz';
+ ({
+ foo: bar = baz,
+ }) =>
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1, 'expression'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -252,6 +549,7 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 1, 'expression'),
+ noopImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -263,6 +561,7 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 0, 'expression'),
+ noopImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -277,6 +576,21 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 1, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('resolves imported default props in the parameters', () => {
+ const src = `
+ import baz from 'baz';
+ import React from 'react';
+ React.forwardRef(({ bar = baz }, ref) => {bar}
);
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 2, 'expression'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -287,7 +601,26 @@ describe('defaultPropsHandler', () => {
const Component = React.forwardRef(({ foo }, ref) => {foo}
);
Component.defaultProps = { foo: 'baz' };
`;
- defaultPropsHandler(documentation, parse(src).get('body', 1));
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 1),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('resolves imported defaultProps', () => {
+ const src = `
+ import other from 'other';
+ import React from 'react';
+ const Component = React.forwardRef(({ bar }, ref) => {bar}
);
+ Component.defaultProps = other;
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 2),
+ mockImporter,
+ );
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -300,6 +633,22 @@ describe('defaultPropsHandler', () => {
defaultPropsHandler(
documentation,
parse(src).get('body', 2, 'expression'),
+ noopImporter,
+ );
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
+
+ it('also resolves imports when the function is not inline', () => {
+ const src = `
+ import baz from 'baz';
+ import React from 'react';
+ const ComponentImpl = ({ bar = baz }, ref) => {bar}
;
+ React.forwardRef(ComponentImpl);
+ `;
+ defaultPropsHandler(
+ documentation,
+ parse(src).get('body', 3, 'expression'),
+ mockImporter,
);
expect(documentation.descriptors).toMatchSnapshot();
});
diff --git a/src/handlers/__tests__/displayNameHandler-test.js b/src/handlers/__tests__/displayNameHandler-test.js
index 2589fc6be01..6e3f356ed2c 100644
--- a/src/handlers/__tests__/displayNameHandler-test.js
+++ b/src/handlers/__tests__/displayNameHandler-test.js
@@ -8,7 +8,12 @@
jest.mock('../../Documentation');
-import { expression, statement } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
describe('defaultPropsHandler', () => {
let documentation;
@@ -19,18 +24,48 @@ describe('defaultPropsHandler', () => {
displayNameHandler = require('../displayNameHandler').default;
});
+ const mockImporter = makeMockImporter({
+ foobarbaz: statement(`
+ export default "FooBarBaz"
+ `).get('declaration'),
+
+ foo: statement(`
+ export default {bar: 'baz'};
+ `).get('declaration'),
+
+ bar: statement(`
+ export default {baz: 'foo'};
+ `).get('declaration'),
+ });
+
it('extracts the displayName', () => {
let definition = expression('({displayName: "FooBar"})');
- displayNameHandler(documentation, definition);
+ displayNameHandler(documentation, definition, noopImporter);
expect(documentation.displayName).toBe('FooBar');
+ definition = statement(`
+ ({displayName: foobarbaz});
+ import foobarbaz from 'foobarbaz';
+ `).get('expression');
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('FooBarBaz');
+
definition = statement(`
class Foo {
static displayName = "BarFoo";
}
`);
- displayNameHandler(documentation, definition);
+ displayNameHandler(documentation, definition, noopImporter);
expect(documentation.displayName).toBe('BarFoo');
+
+ definition = statement(`
+ class Foo {
+ static displayName = foobarbaz;
+ }
+ import foobarbaz from 'foobarbaz';
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('FooBarBaz');
});
it('resolves identifiers', () => {
@@ -38,22 +73,42 @@ describe('defaultPropsHandler', () => {
({displayName: name})
var name = 'abc';
`).get('expression');
- displayNameHandler(documentation, definition);
+ displayNameHandler(documentation, definition, noopImporter);
expect(documentation.displayName).toBe('abc');
+ definition = statement(`
+ ({displayName: name})
+ import foobarbaz from 'foobarbaz';
+ var name = foobarbaz;
+ `).get('expression');
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('FooBarBaz');
+
definition = statement(`
class Foo {
static displayName = name;
}
var name = 'xyz';
`);
- displayNameHandler(documentation, definition);
+ displayNameHandler(documentation, definition, noopImporter);
expect(documentation.displayName).toBe('xyz');
+
+ definition = statement(`
+ class Foo {
+ static displayName = name;
+ }
+ import foobarbaz from 'foobarbaz';
+ var name = foobarbaz;
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('FooBarBaz');
});
it('ignores non-literal names', () => {
let definition = expression('({displayName: foo.bar})');
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).not.toBeDefined();
definition = statement(`
@@ -61,17 +116,39 @@ describe('defaultPropsHandler', () => {
static displayName = foo.bar;
}
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).not.toBeDefined();
});
+ it('can resolve non-literal names with appropriate importer', () => {
+ let definition = statement(`
+ ({displayName: foo.bar});
+ import foo from 'foo';
+ `).get('expression');
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('baz');
+
+ definition = statement(`
+ class Foo {
+ static displayName = bar.baz;
+ }
+ import bar from 'bar';
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('foo');
+ });
+
describe('ClassDeclaration', () => {
it('considers the class name', () => {
const definition = statement(`
class Foo {
}
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
@@ -81,7 +158,9 @@ describe('defaultPropsHandler', () => {
static displayName = 'foo';
}
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('foo');
});
@@ -93,7 +172,9 @@ describe('defaultPropsHandler', () => {
}
}
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('foo');
});
@@ -106,15 +187,43 @@ describe('defaultPropsHandler', () => {
}
const abc = 'bar';
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('bar');
});
+
+ it('resolves imported variables in displayName getter', () => {
+ let definition = statement(`
+ class Foo {
+ static get displayName() {
+ return foobarbaz;
+ }
+ }
+ import foobarbaz from 'foobarbaz';
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('FooBarBaz');
+
+ definition = statement(`
+ class Foo {
+ static get displayName() {
+ return foo.bar;
+ }
+ }
+ import foo from 'foo';
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('baz');
+ });
});
describe('FunctionDeclaration', () => {
it('considers the function name', () => {
const definition = statement('function Foo () {}');
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
@@ -123,9 +232,39 @@ describe('defaultPropsHandler', () => {
function Foo () {}
Foo.displayName = 'Bar';
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.displayName).toBe('Bar');
+ });
+
+ it('resolves variable assigned to displayName object property', () => {
+ const definition = statement(`
+ function Foo () {}
+ Foo.displayName = bar;
+ var bar = 'Bar';
+ `);
+ displayNameHandler(documentation, definition, noopImporter);
expect(documentation.displayName).toBe('Bar');
});
+
+ it('resolves imported variable assigned to displayName object property', () => {
+ let definition = statement(`
+ function Foo () {}
+ import foobarbaz from 'foobarbaz';
+ Foo.displayName = foobarbaz;
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('FooBarBaz');
+
+ definition = statement(`
+ function Foo () {}
+ import foo from 'foo';
+ Foo.displayName = foo.bar;
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('baz');
+ });
});
describe('FunctionExpression', () => {
@@ -135,7 +274,9 @@ describe('defaultPropsHandler', () => {
0,
'init',
);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
@@ -144,7 +285,9 @@ describe('defaultPropsHandler', () => {
'expression',
'right',
);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
@@ -153,9 +296,39 @@ describe('defaultPropsHandler', () => {
var Foo = function () {};
Foo.displayName = 'Bar';
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.displayName).toBe('Bar');
+ });
+
+ it('resolves variable assigned to displayName object property over variable name', () => {
+ const definition = statement(`
+ var Foo = function () {};
+ Foo.displayName = bar;
+ var bar = 'Bar';
+ `);
+ displayNameHandler(documentation, definition, noopImporter);
expect(documentation.displayName).toBe('Bar');
});
+
+ it('resolves imported variable assigned to displayName object property over variable name', () => {
+ let definition = statement(`
+ var Foo = function () {};
+ import foobarbaz from 'foobarbaz';
+ Foo.displayName = foobarbaz;
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('FooBarBaz');
+
+ definition = statement(`
+ var Foo = function () {};
+ import foo from 'foo';
+ Foo.displayName = foo.bar;
+ `);
+ displayNameHandler(documentation, definition, mockImporter);
+ expect(documentation.displayName).toBe('baz');
+ });
});
describe('ArrowFunctionExpression', () => {
@@ -165,7 +338,9 @@ describe('defaultPropsHandler', () => {
0,
'init',
);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
@@ -177,18 +352,20 @@ describe('defaultPropsHandler', () => {
'arguments',
0,
);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
it('considers the variable name when handling forwardRef', () => {
- const definition = statement(
- [
- 'var Foo = React.forwardRef(() => {});',
- 'import React from "react";',
- ].join('\n'),
- ).get('declarations', 0, 'init');
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ const definition = statement(`
+ var Foo = React.forwardRef(() => {});
+ import React from "react";
+ `).get('declarations', 0, 'init');
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
@@ -197,7 +374,9 @@ describe('defaultPropsHandler', () => {
'expression',
'right',
);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
@@ -208,18 +387,20 @@ describe('defaultPropsHandler', () => {
'arguments',
0,
);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
it('considers the variable name on assign when handling forwardRef call', () => {
- const definition = statement(
- [
- 'Foo = React.forwardRef(() => {});',
- 'import React from "react";',
- ].join('\n'),
- ).get('expression', 'right');
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ const definition = statement(`
+ Foo = React.forwardRef(() => {});
+ import React from "react";
+ `).get('expression', 'right');
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Foo');
});
@@ -228,25 +409,99 @@ describe('defaultPropsHandler', () => {
var Foo = () => {};
Foo.displayName = 'Bar';
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Bar');
});
+ it('resolves a variable assigned to displayName object property over variable name', () => {
+ const definition = statement(`
+ var Foo = () => {};
+ Foo.displayName = bar;
+ var bar = 'Bar';
+ `);
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.displayName).toBe('Bar');
+ });
+
+ it('resolves imported variable assigned to displayName object property over variable name', () => {
+ let definition = statement(`
+ var Foo = () => {};
+ import foobarbaz from 'foobarbaz';
+ Foo.displayName = foobarbaz;
+ `);
+ expect(() =>
+ displayNameHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
+ expect(documentation.displayName).toBe('FooBarBaz');
+
+ definition = statement(`
+ var Foo = () => {};
+ import foo from 'foo';
+ Foo.displayName = foo.bar;
+ `);
+ expect(() =>
+ displayNameHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
+ expect(documentation.displayName).toBe('baz');
+ });
+
it('considers a static displayName object property over variable name even if wrapped', () => {
const definition = statement(`
var Foo = React.forwardRef(() => {});
Foo.displayName = 'Bar';
`);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.displayName).toBe('Bar');
+ });
+
+ it('resolves a variable assigned to displayName object property over variable name even if wrapped', () => {
+ const definition = statement(`
+ var Foo = React.forwardRef(() => {});
+ Foo.displayName = bar;
+ var bar = 'Bar';
+ `);
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).toBe('Bar');
});
+ it('resolves imported variable assigned to displayName object property over variable name even if wrapped', () => {
+ let definition = statement(`
+ var Foo = React.forwardRef(() => {});
+ import foobarbaz from 'foobarbaz';
+ Foo.displayName = foobarbaz;
+ `);
+ expect(() =>
+ displayNameHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
+ expect(documentation.displayName).toBe('FooBarBaz');
+
+ definition = statement(`
+ var Foo = React.forwardRef(() => {});
+ import foo from 'foo';
+ Foo.displayName = foo.bar;
+ `);
+ expect(() =>
+ displayNameHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
+ expect(documentation.displayName).toBe('baz');
+ });
+
it('ignores assignment to non-literal/identifier', () => {
const definition = statement('Foo.Bar = () => {};').get(
'expression',
'right',
);
- expect(() => displayNameHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ displayNameHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.displayName).not.toBeDefined();
});
});
diff --git a/src/handlers/__tests__/flowTypeHandler-test.js b/src/handlers/__tests__/flowTypeHandler-test.js
index 33c2f845cc8..d4f560f241c 100644
--- a/src/handlers/__tests__/flowTypeHandler-test.js
+++ b/src/handlers/__tests__/flowTypeHandler-test.js
@@ -8,7 +8,13 @@
jest.mock('../../Documentation');
-import { expression, statement, parse } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ parse,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
describe('flowTypeHandler', () => {
let getFlowTypeMock;
@@ -24,6 +30,20 @@ describe('flowTypeHandler', () => {
flowTypeHandler = require('../flowTypeHandler').default;
});
+ const mockImporter = makeMockImporter({
+ something: statement(`
+ export type Props = {
+ foo: string,
+ bar?: number,
+ hal: boolean,
+ [key: string]: string,
+ abc: string | number,
+ def: "test" | 1 | true,
+ foobar: Foo & Bar,
+ };
+ `).get('declaration'),
+ });
+
function template(src, typeObject) {
return `
${src}
@@ -45,7 +65,7 @@ describe('flowTypeHandler', () => {
`;
const definition = getSrc(flowTypesSrc);
- flowTypeHandler(documentation, definition);
+ flowTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
@@ -75,7 +95,7 @@ describe('flowTypeHandler', () => {
`;
const definition = getSrc(flowTypesSrc);
- flowTypeHandler(documentation, definition);
+ flowTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
@@ -100,7 +120,7 @@ describe('flowTypeHandler', () => {
`;
const definition = getSrc(flowTypesSrc);
- flowTypeHandler(documentation, definition);
+ flowTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -114,7 +134,7 @@ describe('flowTypeHandler', () => {
`;
const definition = getSrc(flowTypesSrc);
- flowTypeHandler(documentation, definition);
+ flowTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
@@ -138,7 +158,7 @@ describe('flowTypeHandler', () => {
`;
const definition = getSrc(flowTypesSrc);
- flowTypeHandler(documentation, definition);
+ flowTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
@@ -160,7 +180,7 @@ describe('flowTypeHandler', () => {
const definition = getSrc(flowTypesSrc);
- flowTypeHandler(documentation, definition);
+ flowTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
@@ -227,13 +247,19 @@ describe('flowTypeHandler', () => {
it('does not error if flowTypes cannot be found', () => {
let definition = expression('{fooBar: 42}');
- expect(() => flowTypeHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
definition = statement('class Foo extends Component {}');
- expect(() => flowTypeHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
definition = statement('() =>
');
- expect(() => flowTypeHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
});
it('supports intersection proptypes', () => {
@@ -246,7 +272,7 @@ describe('flowTypeHandler', () => {
type Props = Imported & { foo: 'bar' };
`).get('expression');
- flowTypeHandler(documentation, definition);
+ flowTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
@@ -266,7 +292,9 @@ describe('flowTypeHandler', () => {
type Props = { foo: 'fooValue' };
`).get('expression');
- expect(() => flowTypeHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -282,56 +310,90 @@ describe('flowTypeHandler', () => {
type Props = Imported | Other | { foo: 'fooValue' };
`).get('expression');
- expect(() => flowTypeHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
expect(documentation.descriptors).toEqual({});
});
- describe('does not error for unreachable type', () => {
- function testCode(code) {
- const definition = statement(code).get('expression');
-
- expect(() => flowTypeHandler(documentation, definition)).not.toThrow();
-
- expect(documentation.descriptors).toEqual({});
- }
-
- it('required', () => {
- testCode(`
+ describe('imported prop types', () => {
+ it('does not resolve type included by require', () => {
+ const definition = statement(`
(props: Props) =>
;
var React = require('React');
var Component = React.Component;
var Props = require('something');
- `);
+ `).get('expression');
+
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toEqual({});
+
+ expect(() =>
+ flowTypeHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toEqual({});
});
it('imported', () => {
- testCode(`
+ const definition = statement(`
(props: Props) =>
;
var React = require('React');
var Component = React.Component;
- import Props from 'something';
- `);
+ import { Props } from 'something';
+ `).get('expression');
+
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toEqual({});
+
+ expect(() =>
+ flowTypeHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toMatchSnapshot();
});
it('type imported', () => {
- testCode(`
+ const definition = statement(`
(props: Props) =>
;
var React = require('React');
var Component = React.Component;
- import type Props from 'something';
- `);
+ import type { Props } from 'something';
+ `).get('expression');
+
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toEqual({});
+
+ expect(() =>
+ flowTypeHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toMatchSnapshot();
});
- it('not in scope', () => {
- testCode(`
+ it('does not resolve types not in scope', () => {
+ const definition = statement(`
(props: Props) =>
;
var React = require('React');
var Component = React.Component;
- `);
+ `).get('expression');
+
+ expect(() =>
+ flowTypeHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toEqual({});
+
+ expect(() =>
+ flowTypeHandler(documentation, definition, mockImporter),
+ ).not.toThrow();
+ expect(documentation.descriptors).toEqual({});
});
});
@@ -342,7 +404,11 @@ describe('flowTypeHandler', () => {
type Props = { foo: string };
React.forwardRef((props: Props, ref) => {props.foo}
);
`;
- flowTypeHandler(documentation, parse(src).get('body', 2, 'expression'));
+ flowTypeHandler(
+ documentation,
+ parse(src).get('body', 2, 'expression'),
+ noopImporter,
+ );
expect(documentation.descriptors).toEqual({
foo: {
flowType: {},
@@ -359,7 +425,11 @@ describe('flowTypeHandler', () => {
const ComponentImpl = (props: Props, ref) => {props.foo}
;
React.forwardRef(ComponentImpl);
`;
- flowTypeHandler(documentation, parse(src).get('body', 3, 'expression'));
+ flowTypeHandler(
+ documentation,
+ parse(src).get('body', 3, 'expression'),
+ noopImporter,
+ );
expect(documentation.descriptors).toEqual({
foo: {
flowType: {},
@@ -379,6 +449,7 @@ describe('flowTypeHandler', () => {
flowTypeHandler(
documentation,
parse(src).get('body', 3, 'expression', 'right'),
+ noopImporter,
);
expect(documentation.descriptors).toEqual({
foo: {
diff --git a/src/handlers/__tests__/propDocblockHandler-test.js b/src/handlers/__tests__/propDocblockHandler-test.js
index 47e48995313..1e8dcd42d16 100644
--- a/src/handlers/__tests__/propDocblockHandler-test.js
+++ b/src/handlers/__tests__/propDocblockHandler-test.js
@@ -8,7 +8,12 @@
jest.mock('../../Documentation');
-import { expression, statement } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
describe('propDocBlockHandler', () => {
let documentation;
@@ -19,6 +24,17 @@ describe('propDocBlockHandler', () => {
propDocBlockHandler = require('../propDocBlockHandler').default;
});
+ const mockImporter = makeMockImporter({
+ props: statement(`
+ export default {
+ /**
+ * A comment on imported props
+ */
+ foo: Prop.bool,
+ };
+ `).get('declaration'),
+ });
+
function test(getSrc, parse) {
it('finds docblocks for prop types', () => {
const definition = parse(
@@ -36,7 +52,7 @@ describe('propDocBlockHandler', () => {
),
);
- propDocBlockHandler(documentation, definition);
+ propDocBlockHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment',
@@ -62,7 +78,7 @@ describe('propDocBlockHandler', () => {
),
);
- propDocBlockHandler(documentation, definition);
+ propDocBlockHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
description:
@@ -89,7 +105,7 @@ describe('propDocBlockHandler', () => {
),
);
- propDocBlockHandler(documentation, definition);
+ propDocBlockHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment',
@@ -113,7 +129,7 @@ describe('propDocBlockHandler', () => {
),
);
- propDocBlockHandler(documentation, definition);
+ propDocBlockHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment',
@@ -128,7 +144,7 @@ describe('propDocBlockHandler', () => {
const definition = parse(
getSrc(
`{
- ...Foo.propTypes,
+ ...Bar.propTypes,
/**
* Foo comment
*/
@@ -137,7 +153,7 @@ describe('propDocBlockHandler', () => {
),
);
- propDocBlockHandler(documentation, definition);
+ propDocBlockHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment',
@@ -156,13 +172,51 @@ describe('propDocBlockHandler', () => {
};
`);
- propDocBlockHandler(documentation, definition);
+ propDocBlockHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
description: 'Foo comment',
},
});
});
+
+ it('resolves imported variables', () => {
+ const definition = parse(`
+ ${getSrc('Props')}
+ import Props from 'props';
+ `);
+
+ propDocBlockHandler(documentation, definition, mockImporter);
+ expect(documentation.descriptors).toEqual({
+ foo: {
+ description: 'A comment on imported props',
+ },
+ });
+ });
+
+ it('resolves imported variables that are spread', () => {
+ const definition = parse(`
+ ${getSrc('Props')}
+ import ExtraProps from 'props';
+ var Props = {
+ ...ExtraProps,
+ /**
+ * Bar comment
+ */
+ bar: Prop.bool,
+ }
+ `);
+
+ propDocBlockHandler(documentation, definition, mockImporter);
+ expect(documentation.descriptors).toEqual({
+ foo: {
+ description: 'A comment on imported props',
+ },
+ bar: {
+ description: 'Bar comment',
+ },
+ });
+ });
}
describe('React.createClass', () => {
@@ -200,9 +254,13 @@ describe('propDocBlockHandler', () => {
it('does not error if propTypes cannot be found', () => {
let definition = expression('{fooBar: 42}');
- expect(() => propDocBlockHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ propDocBlockHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
definition = statement('class Foo {}');
- expect(() => propDocBlockHandler(documentation, definition)).not.toThrow();
+ expect(() =>
+ propDocBlockHandler(documentation, definition, noopImporter),
+ ).not.toThrow();
});
});
diff --git a/src/handlers/__tests__/propTypeCompositionHandler-test.js b/src/handlers/__tests__/propTypeCompositionHandler-test.js
index b77ea521d7c..8b7518205be 100644
--- a/src/handlers/__tests__/propTypeCompositionHandler-test.js
+++ b/src/handlers/__tests__/propTypeCompositionHandler-test.js
@@ -8,7 +8,12 @@
jest.mock('../../Documentation');
-import { expression, statement } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
describe('propTypeCompositionHandler', () => {
let getPropTypeMock;
@@ -25,6 +30,22 @@ describe('propTypeCompositionHandler', () => {
.default;
});
+ const mockImporter = makeMockImporter({
+ 'Foo.react': statement(`
+ export default Component;
+ function Component() {}
+ Component.propTypes = {
+ foo: 'bar'
+ };
+ `).get('declaration'),
+
+ SharedProps: statement(`
+ export default {
+ bar: 'baz'
+ };
+ `).get('declaration'),
+ });
+
function test(getSrc, parse) {
it('understands assignment from module', () => {
let definition = parse(`
@@ -32,7 +53,7 @@ describe('propTypeCompositionHandler', () => {
var Foo = require("Foo.react");
`);
- propTypeCompositionHandler(documentation, definition);
+ propTypeCompositionHandler(documentation, definition, noopImporter);
expect(documentation.composes).toEqual(['Foo.react']);
documentation = new (require('../../Documentation'))();
@@ -41,7 +62,7 @@ describe('propTypeCompositionHandler', () => {
var SharedProps = require("SharedProps");
`);
- propTypeCompositionHandler(documentation, definition);
+ propTypeCompositionHandler(documentation, definition, noopImporter);
expect(documentation.composes).toEqual(['SharedProps']);
});
@@ -58,9 +79,43 @@ describe('propTypeCompositionHandler', () => {
var SharedProps = require("SharedProps");
`);
- propTypeCompositionHandler(documentation, definition);
+ propTypeCompositionHandler(documentation, definition, noopImporter);
expect(documentation.composes).toEqual(['Foo.react', 'SharedProps']);
});
+
+ it('does not add any composes if spreads can be fully resolved with the importer', () => {
+ const definitionSrc = getSrc(
+ `{
+ ...Foo.propTypes,
+ ...SharedProps,
+ }`,
+ );
+ const definition = parse(`
+ ${definitionSrc}
+ import Foo from "Foo.react";
+ import SharedProps from "SharedProps";
+ `);
+
+ propTypeCompositionHandler(documentation, definition, mockImporter);
+ expect(documentation.composes).toEqual([]);
+ });
+
+ it('still adds a composes if the importer cannot resolve a value', () => {
+ const definitionSrc = getSrc(
+ `{
+ ...Foo.propTypes,
+ ...NotFound,
+ }`,
+ );
+ const definition = parse(`
+ ${definitionSrc}
+ import Foo from "Foo.react";
+ import NotFound from "NotFound";
+ `);
+
+ propTypeCompositionHandler(documentation, definition, mockImporter);
+ expect(documentation.composes).toEqual(['NotFound']);
+ });
}
describe('React.createClass', () => {
diff --git a/src/handlers/__tests__/propTypeHandler-test.js b/src/handlers/__tests__/propTypeHandler-test.js
index 7c34d1b0993..fca409a23da 100644
--- a/src/handlers/__tests__/propTypeHandler-test.js
+++ b/src/handlers/__tests__/propTypeHandler-test.js
@@ -9,7 +9,12 @@
jest.mock('../../Documentation');
jest.mock('../../utils/getPropType', () => jest.fn(() => ({})));
-import { statement, expression } from '../../../tests/utils';
+import {
+ statement,
+ expression,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import Documentation from '../../Documentation';
import { propTypeHandler } from '../propTypeHandler';
@@ -22,6 +27,39 @@ describe('propTypeHandler', () => {
documentation = new Documentation();
});
+ const mockImporter = makeMockImporter({
+ props: statement(`
+ export default {bar: PropTypes.bool};
+ import { PropTypes } from 'react';
+ `).get('declaration'),
+
+ simpleProp: statement(`
+ export default PropTypes.array.isRequired;
+ import { PropTypes } from 'react';
+ `).get('declaration'),
+
+ complexProp: statement(`
+ export default prop;
+ var prop = PropTypes.oneOfType([PropTypes.number, PropTypes.bool]).isRequired;
+ import { PropTypes } from 'react';
+ `).get('declaration'),
+
+ foo: statement(`
+ export default PropTypes.bool;
+ import { PropTypes } from 'react';
+ `).get('declaration'),
+
+ bar: statement(`
+ export default PropTypes.bool;
+ import { PropTypes } from 'react';
+ `).get('declaration'),
+
+ baz: statement(`
+ export default OtherPropTypes.bool;
+ import { PropTypes as OtherPropTypes } from 'react';
+ `).get('declaration'),
+ });
+
function template(src) {
return `
${src}
@@ -43,7 +81,7 @@ describe('propTypeHandler', () => {
const fooPath = propTypesAST.get('properties', 0, 'value');
const xyzPath = propTypesAST.get('properties', 1, 'value');
- propTypeHandler(documentation, definition);
+ propTypeHandler(documentation, definition, noopImporter);
expect(getPropTypeMock.mock.calls[0][0]).toEqualASTNode(fooPath);
expect(getPropTypeMock.mock.calls[1][0]).toEqualASTNode(xyzPath);
@@ -60,7 +98,7 @@ describe('propTypeHandler', () => {
),
);
- propTypeHandler(documentation, definition);
+ propTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
type: {},
@@ -86,7 +124,7 @@ describe('propTypeHandler', () => {
),
);
- propTypeHandler(documentation, definition);
+ propTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
foo: {
type: {},
@@ -106,7 +144,7 @@ describe('propTypeHandler', () => {
),
);
- propTypeHandler(documentation, definition);
+ propTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
simple_prop: {
type: {},
@@ -130,7 +168,7 @@ describe('propTypeHandler', () => {
),
);
- propTypeHandler(documentation, definition);
+ propTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -145,7 +183,7 @@ describe('propTypeHandler', () => {
),
);
- propTypeHandler(documentation, definition);
+ propTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toMatchSnapshot();
});
@@ -159,7 +197,7 @@ describe('propTypeHandler', () => {
),
);
- propTypeHandler(documentation, definition);
+ propTypeHandler(documentation, definition, noopImporter);
expect(documentation.descriptors).toEqual({
custom_propA: {
type: {},
@@ -182,7 +220,23 @@ describe('propTypeHandler', () => {
var props = {bar: PropTypes.bool};
`);
- propTypeHandler(documentation, definition);
+ propTypeHandler(documentation, definition, noopImporter);
+ expect(documentation.descriptors).toEqual({
+ bar: {
+ type: {},
+ required: false,
+ },
+ });
+ });
+
+ it('resolves imported variables', () => {
+ const definitionSrc = getSrc('props');
+ const definition = parse(`
+ ${definitionSrc}
+ import props from 'props';
+ `);
+
+ propTypeHandler(documentation, definition, mockImporter);
expect(documentation.descriptors).toEqual({
bar: {
type: {},
@@ -190,6 +244,27 @@ describe('propTypeHandler', () => {
},
});
});
+
+ it('can resolve individual imported variables assigned to props', () => {
+ const definitionSrc = getSrc(`{
+ foo: foo,
+ [bar]: bar,
+ baz: baz,
+ simple_prop: simpleProp,
+ complex_prop: complexProp,
+ }`);
+ const definition = parse(`
+ ${definitionSrc}
+ import foo from 'foo';
+ import bar from 'bar';
+ import baz from 'baz';
+ import simpleProp from 'simpleProp';
+ import complexProp from 'complexProp';
+ `);
+
+ propTypeHandler(documentation, definition, mockImporter);
+ expect(documentation.descriptors).toMatchSnapshot();
+ });
}
describe('React.createClass', () => {
diff --git a/src/handlers/componentDocblockHandler.js b/src/handlers/componentDocblockHandler.js
index 940fa50e662..f38a97c6460 100644
--- a/src/handlers/componentDocblockHandler.js
+++ b/src/handlers/componentDocblockHandler.js
@@ -12,13 +12,14 @@ import type Documentation from '../Documentation';
import { getDocblock } from '../utils/docblock';
import isReactForwardRefCall from '../utils/isReactForwardRefCall';
import resolveToValue from '../utils/resolveToValue';
+import type { Importer } from '../types';
function isClassDefinition(nodePath) {
const node = nodePath.node;
return t.ClassDeclaration.check(node) || t.ClassExpression.check(node);
}
-function getDocblockFromComponent(path) {
+function getDocblockFromComponent(path, importer) {
let description = null;
if (isClassDefinition(path)) {
@@ -48,10 +49,13 @@ function getDocblockFromComponent(path) {
description = getDocblock(searchPath);
}
}
- if (!description && isReactForwardRefCall(path)) {
- const inner = resolveToValue(path.get('arguments', 0));
+ if (!description) {
+ const searchPath = isReactForwardRefCall(path, importer)
+ ? path.get('arguments', 0)
+ : path;
+ const inner = resolveToValue(searchPath, importer);
if (inner.node !== path.node) {
- return getDocblockFromComponent(inner);
+ return getDocblockFromComponent(inner, importer);
}
}
return description;
@@ -63,6 +67,10 @@ function getDocblockFromComponent(path) {
export default function componentDocblockHandler(
documentation: Documentation,
path: NodePath,
+ importer: Importer,
) {
- documentation.set('description', getDocblockFromComponent(path) || '');
+ documentation.set(
+ 'description',
+ getDocblockFromComponent(path, importer) || '',
+ );
}
diff --git a/src/handlers/componentMethodsHandler.js b/src/handlers/componentMethodsHandler.js
index 31f3a11b4bb..df71c3c55b5 100644
--- a/src/handlers/componentMethodsHandler.js
+++ b/src/handlers/componentMethodsHandler.js
@@ -16,6 +16,7 @@ import type Documentation from '../Documentation';
import match from '../utils/match';
import { traverseShallow } from '../utils/traverse';
import resolveToValue from '../utils/resolveToValue';
+import type { Importer } from '../types';
/**
* The following values/constructs are considered methods:
@@ -25,16 +26,16 @@ import resolveToValue from '../utils/resolveToValue';
* - Public class fields in classes whose value are a functions
* - Object properties whose values are functions
*/
-function isMethod(path) {
+function isMethod(path, importer) {
const isProbablyMethod =
(t.MethodDefinition.check(path.node) && path.node.kind !== 'constructor') ||
((t.ClassProperty.check(path.node) || t.Property.check(path.node)) &&
- t.Function.check(path.get('value').node));
+ t.Function.check(resolveToValue(path.get('value'), importer).node));
- return isProbablyMethod && !isReactComponentMethod(path);
+ return isProbablyMethod && !isReactComponentMethod(path, importer);
}
-function findAssignedMethods(scope, idPath) {
+function findAssignedMethods(scope, idPath, importer) {
const results = [];
if (!t.Identifier.check(idPath.node)) {
@@ -53,7 +54,7 @@ function findAssignedMethods(scope, idPath) {
object: { type: 'Identifier', name },
}) &&
path.scope.lookup(name) === idScope &&
- t.Function.check(resolveToValue(path.get('right')).node)
+ t.Function.check(resolveToValue(path.get('right'), importer).node)
) {
results.push(path);
return false;
@@ -72,19 +73,24 @@ function findAssignedMethods(scope, idPath) {
export default function componentMethodsHandler(
documentation: Documentation,
path: NodePath,
+ importer: Importer,
) {
// Extract all methods from the class or object.
let methodPaths = [];
- if (isReactComponentClass(path)) {
- methodPaths = path.get('body', 'body').filter(isMethod);
+ if (isReactComponentClass(path, importer)) {
+ methodPaths = path
+ .get('body', 'body')
+ .filter(body => isMethod(body, importer));
} else if (t.ObjectExpression.check(path.node)) {
- methodPaths = path.get('properties').filter(isMethod);
+ methodPaths = path
+ .get('properties')
+ .filter(props => isMethod(props, importer));
// Add the statics object properties.
- const statics = getMemberValuePath(path, 'statics');
+ const statics = getMemberValuePath(path, 'statics', importer);
if (statics) {
statics.get('properties').each(p => {
- if (isMethod(p)) {
+ if (isMethod(p, importer)) {
p.node.static = true;
methodPaths.push(p);
}
@@ -95,7 +101,11 @@ export default function componentMethodsHandler(
path.parent.node.init === path.node &&
t.Identifier.check(path.parent.node.id)
) {
- methodPaths = findAssignedMethods(path.parent.scope, path.parent.get('id'));
+ methodPaths = findAssignedMethods(
+ path.parent.scope,
+ path.parent.get('id'),
+ importer,
+ );
} else if (
t.AssignmentExpression.check(path.parent.node) &&
path.parent.node.right === path.node &&
@@ -104,13 +114,18 @@ export default function componentMethodsHandler(
methodPaths = findAssignedMethods(
path.parent.scope,
path.parent.get('left'),
+ importer,
);
} else if (t.FunctionDeclaration.check(path.node)) {
- methodPaths = findAssignedMethods(path.parent.scope, path.get('id'));
+ methodPaths = findAssignedMethods(
+ path.parent.scope,
+ path.get('id'),
+ importer,
+ );
}
documentation.set(
'methods',
- methodPaths.map(getMethodDocumentation).filter(Boolean),
+ methodPaths.map(p => getMethodDocumentation(p, importer)).filter(Boolean),
);
}
diff --git a/src/handlers/defaultPropsHandler.js b/src/handlers/defaultPropsHandler.js
index 638f08d7781..f2f81ab386e 100644
--- a/src/handlers/defaultPropsHandler.js
+++ b/src/handlers/defaultPropsHandler.js
@@ -16,17 +16,18 @@ import resolveFunctionDefinitionToReturnValue from '../utils/resolveFunctionDefi
import isReactComponentClass from '../utils/isReactComponentClass';
import isReactForwardRefCall from '../utils/isReactForwardRefCall';
import type Documentation from '../Documentation';
+import type { Importer } from '../types';
-function getDefaultValue(path: NodePath) {
+function getDefaultValue(path: NodePath, importer: Importer) {
let node = path.node;
let defaultValue;
if (t.Literal.check(node)) {
defaultValue = node.raw;
} else {
if (t.AssignmentPattern.check(path.node)) {
- path = resolveToValue(path.get('right'));
+ path = resolveToValue(path.get('right'), importer);
} else {
- path = resolveToValue(path);
+ path = resolveToValue(path, importer);
}
if (t.ImportDeclaration.check(path.node)) {
defaultValue = node.name;
@@ -48,34 +49,42 @@ function getDefaultValue(path: NodePath) {
return null;
}
-function getStatelessPropsPath(componentDefinition): NodePath {
- const value = resolveToValue(componentDefinition);
- if (isReactForwardRefCall(value)) {
- const inner = resolveToValue(value.get('arguments', 0));
+function getStatelessPropsPath(componentDefinition, importer): NodePath {
+ const value = resolveToValue(componentDefinition, importer);
+ if (isReactForwardRefCall(value, importer)) {
+ const inner = resolveToValue(value.get('arguments', 0), importer);
return inner.get('params', 0);
}
return value.get('params', 0);
}
-function getDefaultPropsPath(componentDefinition: NodePath): ?NodePath {
+function getDefaultPropsPath(
+ componentDefinition: NodePath,
+ importer: Importer,
+): ?NodePath {
let defaultPropsPath = getMemberValuePath(
componentDefinition,
'defaultProps',
+ importer,
);
if (!defaultPropsPath) {
return null;
}
- defaultPropsPath = resolveToValue(defaultPropsPath);
+ defaultPropsPath = resolveToValue(defaultPropsPath, importer);
if (!defaultPropsPath) {
return null;
}
- if (t.FunctionExpression.check(defaultPropsPath.node)) {
+ if (
+ t.FunctionExpression.check(defaultPropsPath.node) ||
+ t.FunctionDeclaration.check(defaultPropsPath.node)
+ ) {
// Find the value that is returned from the function and process it if it is
// an object literal.
const returnValue = resolveFunctionDefinitionToReturnValue(
defaultPropsPath,
+ importer,
);
if (returnValue && t.ObjectExpression.check(returnValue.node)) {
defaultPropsPath = returnValue;
@@ -88,6 +97,7 @@ function getDefaultValuesFromProps(
properties: NodePath,
documentation: Documentation,
isStateless: boolean,
+ importer: Importer,
) {
properties
// Don't evaluate property if component is functional and the node is not an AssignmentPattern
@@ -98,7 +108,7 @@ function getDefaultValuesFromProps(
)
.forEach(propertyPath => {
if (t.Property.check(propertyPath.node)) {
- const propName = getPropertyName(propertyPath);
+ const propName = getPropertyName(propertyPath, importer);
if (!propName) return;
const propDescriptor = documentation.getPropDescriptor(propName);
@@ -106,17 +116,22 @@ function getDefaultValuesFromProps(
isStateless
? propertyPath.get('value', 'right')
: propertyPath.get('value'),
+ importer,
);
if (defaultValue) {
propDescriptor.defaultValue = defaultValue;
}
} else if (t.SpreadElement.check(propertyPath.node)) {
- const resolvedValuePath = resolveToValue(propertyPath.get('argument'));
+ const resolvedValuePath = resolveToValue(
+ propertyPath.get('argument'),
+ importer,
+ );
if (t.ObjectExpression.check(resolvedValuePath.node)) {
getDefaultValuesFromProps(
resolvedValuePath.get('properties'),
documentation,
isStateless,
+ importer,
);
}
}
@@ -126,14 +141,15 @@ function getDefaultValuesFromProps(
export default function defaultPropsHandler(
documentation: Documentation,
componentDefinition: NodePath,
+ importer: Importer,
) {
let statelessProps = null;
- const defaultPropsPath = getDefaultPropsPath(componentDefinition);
+ const defaultPropsPath = getDefaultPropsPath(componentDefinition, importer);
/**
* function, lazy, memo, forwardRef etc components can resolve default props as well
*/
- if (!isReactComponentClass(componentDefinition)) {
- statelessProps = getStatelessPropsPath(componentDefinition);
+ if (!isReactComponentClass(componentDefinition, importer)) {
+ statelessProps = getStatelessPropsPath(componentDefinition, importer);
}
// Do both statelessProps and defaultProps if both are available so defaultProps can override
@@ -142,6 +158,7 @@ export default function defaultPropsHandler(
statelessProps.get('properties'),
documentation,
true,
+ importer,
);
}
if (defaultPropsPath && t.ObjectExpression.check(defaultPropsPath.node)) {
@@ -149,6 +166,7 @@ export default function defaultPropsHandler(
defaultPropsPath.get('properties'),
documentation,
false,
+ importer,
);
}
}
diff --git a/src/handlers/displayNameHandler.js b/src/handlers/displayNameHandler.js
index 7bdde7d06ad..f3977a210c2 100644
--- a/src/handlers/displayNameHandler.js
+++ b/src/handlers/displayNameHandler.js
@@ -14,12 +14,14 @@ import isReactForwardRefCall from '../utils/isReactForwardRefCall';
import resolveToValue from '../utils/resolveToValue';
import resolveFunctionDefinitionToReturnValue from '../utils/resolveFunctionDefinitionToReturnValue';
import type Documentation from '../Documentation';
+import type { Importer } from '../types';
export default function displayNameHandler(
documentation: Documentation,
path: NodePath,
+ importer: Importer,
) {
- let displayNamePath = getMemberValuePath(path, 'displayName');
+ let displayNamePath = getMemberValuePath(path, 'displayName', importer);
if (!displayNamePath) {
// Function and class declarations need special treatment. The name of the
// function / class is the displayName
@@ -31,7 +33,7 @@ export default function displayNameHandler(
} else if (
t.ArrowFunctionExpression.check(path.node) ||
t.FunctionExpression.check(path.node) ||
- isReactForwardRefCall(path)
+ isReactForwardRefCall(path, importer)
) {
let currentPath = path;
while (currentPath.parent) {
@@ -56,13 +58,16 @@ export default function displayNameHandler(
}
return;
}
- displayNamePath = resolveToValue(displayNamePath);
+ displayNamePath = resolveToValue(displayNamePath, importer);
// 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 (t.FunctionExpression.check(displayNamePath.node)) {
- displayNamePath = resolveFunctionDefinitionToReturnValue(displayNamePath);
+ displayNamePath = resolveFunctionDefinitionToReturnValue(
+ displayNamePath,
+ importer,
+ );
}
if (!displayNamePath || !t.Literal.check(displayNamePath.node)) {
return;
diff --git a/src/handlers/flowTypeHandler.js b/src/handlers/flowTypeHandler.js
index 15ce594ff8a..97ad4792ce7 100644
--- a/src/handlers/flowTypeHandler.js
+++ b/src/handlers/flowTypeHandler.js
@@ -19,11 +19,13 @@ import getTSType from '../utils/getTSType';
import { type TypeParameters } from '../utils/getTypeParameters';
import resolveToValue from '../utils/resolveToValue';
import setPropDescription from '../utils/setPropDescription';
+import type { Importer } from '../types';
function setPropDescriptor(
documentation: Documentation,
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): void {
if (t.ObjectTypeSpreadProperty.check(path.node)) {
const argument = unwrapUtilityType(path.get('argument'));
@@ -33,15 +35,25 @@ function setPropDescriptor(
documentation,
argument,
(propertyPath, innerTypeParams) => {
- setPropDescriptor(documentation, propertyPath, innerTypeParams);
+ setPropDescriptor(
+ documentation,
+ propertyPath,
+ innerTypeParams,
+ importer,
+ );
},
typeParams,
+ importer,
);
return;
}
const name = argument.get('id').get('name');
- const resolvedPath = resolveToValue(name);
+ const resolvedPath = resolveToValue(
+ name,
+ // TODO: Make this configurable with a pragma comment?
+ importer,
+ );
if (resolvedPath && t.TypeAlias.check(resolvedPath.node)) {
const right = resolvedPath.get('right');
@@ -49,16 +61,22 @@ function setPropDescriptor(
documentation,
right,
(propertyPath, innerTypeParams) => {
- setPropDescriptor(documentation, propertyPath, innerTypeParams);
+ setPropDescriptor(
+ documentation,
+ propertyPath,
+ innerTypeParams,
+ importer,
+ );
},
typeParams,
+ importer,
);
- } else {
+ } else if (!argument.node.typeParameters) {
documentation.addComposes(name.node.name);
}
} else if (t.ObjectTypeProperty.check(path.node)) {
- const type = getFlowType(path.get('value'), typeParams);
- const propName = getPropertyName(path);
+ const type = getFlowType(path.get('value'), typeParams, importer);
+ const propName = getPropertyName(path, importer);
if (!propName) return;
const propDescriptor = documentation.getPropDescriptor(propName);
@@ -68,11 +86,11 @@ function setPropDescriptor(
// We are doing this here instead of in a different handler
// to not need to duplicate the logic for checking for
// imported types that are spread in to props.
- setPropDescription(documentation, path);
+ setPropDescription(documentation, path, importer);
} else if (t.TSPropertySignature.check(path.node)) {
- const type = getTSType(path.get('typeAnnotation'), typeParams);
+ const type = getTSType(path.get('typeAnnotation'), typeParams, importer);
- const propName = getPropertyName(path);
+ const propName = getPropertyName(path, importer);
if (!propName) return;
const propDescriptor = documentation.getPropDescriptor(propName);
@@ -82,7 +100,7 @@ function setPropDescriptor(
// We are doing this here instead of in a different handler
// to not need to duplicate the logic for checking for
// imported types that are spread in to props.
- setPropDescription(documentation, path);
+ setPropDescription(documentation, path, importer);
}
}
@@ -94,8 +112,9 @@ function setPropDescriptor(
export default function flowTypeHandler(
documentation: Documentation,
path: NodePath,
+ importer: Importer,
) {
- const flowTypesPath = getFlowTypeFromReactComponent(path);
+ const flowTypesPath = getFlowTypeFromReactComponent(path, importer);
if (!flowTypesPath) {
return;
@@ -105,7 +124,9 @@ export default function flowTypeHandler(
documentation,
flowTypesPath,
(propertyPath, typeParams) => {
- setPropDescriptor(documentation, propertyPath, typeParams);
+ setPropDescriptor(documentation, propertyPath, typeParams, importer);
},
+ null,
+ importer,
);
}
diff --git a/src/handlers/propDocBlockHandler.js b/src/handlers/propDocBlockHandler.js
index 21f45d829d4..40f97751571 100644
--- a/src/handlers/propDocBlockHandler.js
+++ b/src/handlers/propDocBlockHandler.js
@@ -12,10 +12,12 @@ import getMemberValuePath from '../utils/getMemberValuePath';
import resolveToValue from '../utils/resolveToValue';
import setPropDescription from '../utils/setPropDescription';
import type Documentation from '../Documentation';
+import type { Importer } from '../types';
function resolveDocumentation(
documentation: Documentation,
path: NodePath,
+ importer: Importer,
): void {
if (!t.ObjectExpression.check(path.node)) {
return;
@@ -23,10 +25,13 @@ function resolveDocumentation(
path.get('properties').each(propertyPath => {
if (t.Property.check(propertyPath.node)) {
- setPropDescription(documentation, propertyPath);
+ setPropDescription(documentation, propertyPath, importer);
} else if (t.SpreadElement.check(propertyPath.node)) {
- const resolvedValuePath = resolveToValue(propertyPath.get('argument'));
- resolveDocumentation(documentation, resolvedValuePath);
+ const resolvedValuePath = resolveToValue(
+ propertyPath.get('argument'),
+ importer,
+ );
+ resolveDocumentation(documentation, resolvedValuePath, importer);
}
});
}
@@ -34,15 +39,16 @@ function resolveDocumentation(
export default function propDocBlockHandler(
documentation: Documentation,
path: NodePath,
+ importer: Importer,
) {
- let propTypesPath = getMemberValuePath(path, 'propTypes');
+ let propTypesPath = getMemberValuePath(path, 'propTypes', importer);
if (!propTypesPath) {
return;
}
- propTypesPath = resolveToValue(propTypesPath);
+ propTypesPath = resolveToValue(propTypesPath, importer);
if (!propTypesPath) {
return;
}
- resolveDocumentation(documentation, propTypesPath);
+ resolveDocumentation(documentation, propTypesPath, importer);
}
diff --git a/src/handlers/propTypeCompositionHandler.js b/src/handlers/propTypeCompositionHandler.js
index 17d289e32a5..4f2987880ef 100644
--- a/src/handlers/propTypeCompositionHandler.js
+++ b/src/handlers/propTypeCompositionHandler.js
@@ -12,25 +12,27 @@ import getMemberValuePath from '../utils/getMemberValuePath';
import resolveToModule from '../utils/resolveToModule';
import resolveToValue from '../utils/resolveToValue';
import type Documentation from '../Documentation';
+import type { Importer } from '../types';
/**
* It resolves the path to its module name and adds it to the "composes" entry
* in the documentation.
*/
-function amendComposes(documentation, path) {
- const moduleName = resolveToModule(path);
+function amendComposes(documentation, path, importer) {
+ const moduleName = resolveToModule(path, importer);
if (moduleName) {
documentation.addComposes(moduleName);
}
}
-function processObjectExpression(documentation, path) {
+function processObjectExpression(documentation, path, importer) {
path.get('properties').each(function(propertyPath) {
switch (propertyPath.node.type) {
case t.SpreadElement.name:
amendComposes(
documentation,
- resolveToValue(propertyPath.get('argument')),
+ resolveToValue(propertyPath.get('argument'), importer),
+ importer,
);
break;
}
@@ -40,22 +42,23 @@ function processObjectExpression(documentation, path) {
export default function propTypeCompositionHandler(
documentation: Documentation,
path: NodePath,
+ importer: Importer,
) {
- let propTypesPath = getMemberValuePath(path, 'propTypes');
+ let propTypesPath = getMemberValuePath(path, 'propTypes', importer);
if (!propTypesPath) {
return;
}
- propTypesPath = resolveToValue(propTypesPath);
+ propTypesPath = resolveToValue(propTypesPath, importer);
if (!propTypesPath) {
return;
}
switch (propTypesPath.node.type) {
case t.ObjectExpression.name:
- processObjectExpression(documentation, propTypesPath);
+ processObjectExpression(documentation, propTypesPath, importer);
break;
default:
- amendComposes(documentation, propTypesPath);
+ amendComposes(documentation, propTypesPath, importer);
break;
}
}
diff --git a/src/handlers/propTypeHandler.js b/src/handlers/propTypeHandler.js
index 5aa5b7e5816..2959633932b 100644
--- a/src/handlers/propTypeHandler.js
+++ b/src/handlers/propTypeHandler.js
@@ -17,16 +17,17 @@ import printValue from '../utils/printValue';
import resolveToModule from '../utils/resolveToModule';
import resolveToValue from '../utils/resolveToValue';
import type Documentation from '../Documentation';
+import type { Importer } from '../types';
-function isPropTypesExpression(path) {
- const moduleName = resolveToModule(path);
+function isPropTypesExpression(path, importer) {
+ const moduleName = resolveToModule(path, importer);
if (moduleName) {
return isReactModuleName(moduleName) || moduleName === 'ReactPropTypes';
}
return false;
}
-function amendPropTypes(getDescriptor, path) {
+function amendPropTypes(getDescriptor, path, importer) {
if (!t.ObjectExpression.check(path.node)) {
return;
}
@@ -34,13 +35,13 @@ function amendPropTypes(getDescriptor, path) {
path.get('properties').each(propertyPath => {
switch (propertyPath.node.type) {
case t.Property.name: {
- const propName = getPropertyName(propertyPath);
+ const propName = getPropertyName(propertyPath, importer);
if (!propName) return;
const propDescriptor = getDescriptor(propName);
- const valuePath = propertyPath.get('value');
- const type = isPropTypesExpression(valuePath)
- ? getPropType(valuePath)
+ const valuePath = resolveToValue(propertyPath.get('value'), importer);
+ const type = isPropTypesExpression(valuePath, importer)
+ ? getPropType(valuePath, importer)
: { name: 'custom', raw: printValue(valuePath) };
if (type) {
@@ -51,10 +52,13 @@ function amendPropTypes(getDescriptor, path) {
break;
}
case t.SpreadElement.name: {
- const resolvedValuePath = resolveToValue(propertyPath.get('argument'));
+ const resolvedValuePath = resolveToValue(
+ propertyPath.get('argument'),
+ importer,
+ );
switch (resolvedValuePath.node.type) {
case t.ObjectExpression.name: // normal object literal
- amendPropTypes(getDescriptor, resolvedValuePath);
+ amendPropTypes(getDescriptor, resolvedValuePath, importer);
break;
}
break;
@@ -64,12 +68,16 @@ function amendPropTypes(getDescriptor, path) {
}
function getPropTypeHandler(propName: string) {
- return function(documentation: Documentation, path: NodePath) {
- let propTypesPath = getMemberValuePath(path, propName);
+ return function(
+ documentation: Documentation,
+ path: NodePath,
+ importer: Importer,
+ ) {
+ let propTypesPath = getMemberValuePath(path, propName, importer);
if (!propTypesPath) {
return;
}
- propTypesPath = resolveToValue(propTypesPath);
+ propTypesPath = resolveToValue(propTypesPath, importer);
if (!propTypesPath) {
return;
}
@@ -84,7 +92,7 @@ function getPropTypeHandler(propName: string) {
default:
getDescriptor = documentation.getPropDescriptor;
}
- amendPropTypes(getDescriptor.bind(documentation), propTypesPath);
+ amendPropTypes(getDescriptor.bind(documentation), propTypesPath, importer);
};
}
diff --git a/src/importer/ignoreImports.js b/src/importer/ignoreImports.js
new file mode 100644
index 00000000000..9e6870f8a58
--- /dev/null
+++ b/src/importer/ignoreImports.js
@@ -0,0 +1,12 @@
+/**
+ * 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
+ */
+
+export default function ignoreImports() {
+ return null;
+}
diff --git a/src/importer/index.js b/src/importer/index.js
new file mode 100644
index 00000000000..c75d0b972d0
--- /dev/null
+++ b/src/importer/index.js
@@ -0,0 +1,13 @@
+/**
+ * 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 ignoreImports from './ignoreImports';
+import makeFsImporter from './makeFsImporter';
+
+export { ignoreImports, makeFsImporter };
diff --git a/src/importer/makeFsImporter.js b/src/importer/makeFsImporter.js
new file mode 100644
index 00000000000..a22f3946fba
--- /dev/null
+++ b/src/importer/makeFsImporter.js
@@ -0,0 +1,148 @@
+/**
+ * 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 { namedTypes as t, NodePath } from 'ast-types';
+import { traverseShallow } from '../utils/traverse';
+import resolve from 'resolve';
+import { dirname } from 'path';
+import buildParser, { type Options } from '../babelParser';
+import fs from 'fs';
+
+function defaultLookupModule(filename, basedir) {
+ return resolve.sync(filename, {
+ basedir,
+ extensions: ['.js', '.jsx', '.mjs', '.ts', '.tsx'],
+ });
+}
+
+// Factory for the resolveImports importer
+export default function makeFsImporter(
+ lookupModule: (
+ filename: string,
+ basedir: string,
+ ) => string = defaultLookupModule,
+ cache: Map = new Map(),
+) {
+ return resolveImportedValue;
+
+ function resolveImportedValue(
+ path: NodePath,
+ name: string,
+ seen: Set = new Set(),
+ ) {
+ // 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 = lookupModule(source, basedir);
+ } catch (err) {
+ return null;
+ }
+
+ // Prevent recursive imports
+ if (seen.has(resolvedSource)) {
+ return null;
+ }
+
+ seen.add(resolvedSource);
+
+ let nextPath = cache.get(resolvedSource);
+ if (!nextPath) {
+ // Read and parse the code
+ const src = fs.readFileSync(resolvedSource, 'utf8');
+ const parseOptions: Options = {
+ ...options,
+ parserOptions: {},
+ filename: resolvedSource,
+ };
+
+ const parser = buildParser(parseOptions);
+ const ast = parser.parse(src);
+ ast.__src = src;
+ nextPath = new NodePath(ast).get('program');
+ cache.set(resolvedSource, nextPath);
+ }
+
+ return findExportedValue(nextPath, name, seen);
+ }
+
+ // 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(programPath, name, seen) {
+ let resultPath: ?NodePath = null;
+
+ traverseShallow(programPath, {
+ visitExportNamedDeclaration(path: NodePath) {
+ const { declaration, specifiers, source } = 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) {
+ if (source) {
+ const local = specifierPath.node.local.name;
+ resultPath = resolveImportedValue(path, local, seen);
+ } else {
+ resultPath = specifierPath.get('local');
+ }
+ }
+ });
+ }
+
+ return false;
+ },
+ visitExportDefaultDeclaration(path: NodePath) {
+ if (name === 'default') {
+ resultPath = path.get('declaration');
+ }
+
+ return false;
+ },
+ visitExportAllDeclaration(path: NodePath) {
+ const resolvedPath = resolveImportedValue(path, name, seen);
+ if (resolvedPath) {
+ resultPath = resolvedPath;
+ }
+
+ return false;
+ },
+ });
+
+ return resultPath;
+ }
+}
diff --git a/src/main.js b/src/main.js
index 8db42557353..fde7f5e60cc 100644
--- a/src/main.js
+++ b/src/main.js
@@ -10,10 +10,11 @@
import * as allHandlers from './handlers';
import parse from './parse';
import * as AllResolver from './resolver';
+import * as AllImporter from './importer';
import * as utils from './utils';
import type { Options } from './babelParser';
import type { DocumentationObject } from './Documentation';
-import type { Handler, Resolver } from './types';
+import type { Handler, Resolver, Importer } from './types';
const defaultResolver = AllResolver.findExportedComponentDefinition;
const defaultHandlers = [
@@ -29,6 +30,7 @@ const defaultHandlers = [
allHandlers.componentMethodsHandler,
allHandlers.componentMethodsJsDocHandler,
];
+const defaultImporter = AllImporter.ignoreImports;
/**
* See `lib/parse.js` for more information about the arguments. This function
@@ -46,7 +48,8 @@ function defaultParse(
src: string | Buffer,
resolver?: ?Resolver,
handlers?: ?Array,
- options?: Options = {},
+ // Used for backwards compatibility of this method
+ options?: Options & { importer?: Importer } = {},
): Array | DocumentationObject {
if (!resolver) {
resolver = defaultResolver;
@@ -55,7 +58,9 @@ function defaultParse(
handlers = defaultHandlers;
}
- return parse(String(src), resolver, handlers, options);
+ const { importer = defaultImporter, ...opts } = options;
+
+ return parse(String(src), resolver, handlers, importer, opts);
}
export {
@@ -63,5 +68,6 @@ export {
defaultHandlers,
allHandlers as handlers,
AllResolver as resolver,
+ AllImporter as importers,
utils,
};
diff --git a/src/parse.js b/src/parse.js
index d4a87fec5fc..fa80feffa69 100644
--- a/src/parse.js
+++ b/src/parse.js
@@ -10,7 +10,7 @@
import Documentation, { type DocumentationObject } from './Documentation';
import postProcessDocumentation from './utils/postProcessDocumentation';
import buildParser, { type Options, type Parser } from './babelParser';
-import type { Handler, Resolver } from './types';
+import type { Handler, Resolver, Importer } from './types';
const ERROR_MISSING_DEFINITION = 'No suitable component definition found.';
@@ -18,12 +18,13 @@ function executeHandlers(
handlers: Array,
componentDefinitions: Array,
parser: Parser,
+ importer: Importer,
): Array {
return componentDefinitions.map(
(componentDefinition: NodePath): DocumentationObject => {
const documentation = new Documentation();
handlers.forEach(handler =>
- handler(documentation, componentDefinition, parser),
+ handler(documentation, componentDefinition, importer),
);
return postProcessDocumentation(documentation.toObject());
},
@@ -55,20 +56,26 @@ export default function parse(
src: string,
resolver: Resolver,
handlers: Array,
+ importer: Importer,
options: Options,
): Array | DocumentationObject {
const parser = buildParser(options);
const ast = parser.parse(src);
ast.__src = src;
- const componentDefinitions = resolver(ast, parser);
+ const componentDefinitions = resolver(ast, parser, importer);
if (Array.isArray(componentDefinitions)) {
if (componentDefinitions.length === 0) {
throw new Error(ERROR_MISSING_DEFINITION);
}
- return executeHandlers(handlers, componentDefinitions, parser);
+ return executeHandlers(handlers, componentDefinitions, parser, importer);
} else if (componentDefinitions) {
- return executeHandlers(handlers, [componentDefinitions], parser)[0];
+ return executeHandlers(
+ handlers,
+ [componentDefinitions],
+ parser,
+ importer,
+ )[0];
}
throw new Error(ERROR_MISSING_DEFINITION);
diff --git a/src/resolver/__tests__/findAllComponentDefinitions-test.js b/src/resolver/__tests__/findAllComponentDefinitions-test.js
index 3e49205d5a9..4d41c5f61b4 100644
--- a/src/resolver/__tests__/findAllComponentDefinitions-test.js
+++ b/src/resolver/__tests__/findAllComponentDefinitions-test.js
@@ -7,14 +7,59 @@
*/
import { NodePath } from 'ast-types';
-import * as utils from '../../../tests/utils';
+import {
+ getParser,
+ parse as parseSource,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import findAllComponentDefinitions from '../findAllComponentDefinitions';
describe('findAllComponentDefinitions', () => {
- function parse(source) {
- return findAllComponentDefinitions(utils.parse(source), utils.getParser());
+ function parse(source, importer = noopImporter) {
+ return findAllComponentDefinitions(
+ parseSource(source),
+ getParser(),
+ importer,
+ );
}
+ const mockImporter = makeMockImporter({
+ obj: statement(`
+ export default {};
+ `).get('declaration'),
+
+ reactComponent: statement(`
+ export default React.Component;
+ import React from 'react';
+ `).get('declaration'),
+
+ reactPureComponent: statement(`
+ export default React.PureComponent;
+ import React from 'react';
+ `).get('declaration'),
+
+ jsxDiv: statement(`
+ export default
;
+ `).get('declaration'),
+
+ createElement: statement(`
+ export default React.createElement('div', null);
+ import React from 'react';
+ `).get('declaration'),
+
+ arrowJsx: statement(`
+ export default (props) => {props.children}
;
+ `).get('declaration'),
+
+ coloredView: statement(`
+ export default function ColoredView(props, ref) {
+ return
+ };
+ `).get('declaration'),
+ });
+
describe('React.createClass', () => {
it('finds React.createClass', () => {
const source = `
@@ -30,6 +75,21 @@ describe('findAllComponentDefinitions', () => {
expect(result[0].node.type).toBe('ObjectExpression');
});
+ it('resolves imported values inside React.createClass', () => {
+ const source = `
+ import obj from 'obj';
+ var React = require("React");
+ var Component = React.createClass(obj);
+ module.exports = Component;
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(Array.isArray(result)).toBe(true);
+ expect(result.length).toBe(1);
+ expect(result[0] instanceof NodePath).toBe(true);
+ expect(result[0].node.type).toBe('ObjectExpression');
+ });
+
it('finds React.createClass, independent of the var name', () => {
const source = `
var R = require("React");
@@ -108,7 +168,20 @@ describe('findAllComponentDefinitions', () => {
expect(result.length).toBe(4);
});
- it('finds React.createClass, independent of the var name', () => {
+ it('resolves extends React.Component/React.PureComponent from import', () => {
+ const source = `
+ import Component from 'reactComponent';
+ import PureComponent from 'reactPureComponent';
+ class ComponentA extends Component {}
+ var ComponentC = class extends PureComponent {}
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(Array.isArray(result)).toBe(true);
+ expect(result.length).toBe(2);
+ });
+
+ it('finds React.Component, independent of the var name', () => {
const source = `
import R from 'React';
class Component extends R.Component {};
@@ -119,7 +192,7 @@ describe('findAllComponentDefinitions', () => {
expect(result.length).toBe(1);
});
- it('does not process X.createClass of other modules', () => {
+ it('does not process X.Component of other modules', () => {
const source = `
import R from 'FakeReact';
class Component extends R.Component {};
@@ -162,6 +235,21 @@ describe('findAllComponentDefinitions', () => {
expect(result.length).toBe(7);
});
+ it('resolve renders from imports', () => {
+ const source = `
+ import jsxDiv from 'jsxDiv';
+ import createElement from 'createElement';
+ import arrowJsx from 'arrowJsx';
+ let ComponentA = () => jsxDiv;
+ function ComponentB () { return createElement; }
+ const ComponentC = function(props) { return arrowJsx(props); };
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(Array.isArray(result)).toBe(true);
+ expect(result.length).toBe(3);
+ });
+
it('finds React.createElement, independent of the var name', () => {
const source = `
import AlphaBetters from 'react';
@@ -174,7 +262,7 @@ describe('findAllComponentDefinitions', () => {
expect(result.length).toBe(1);
});
- it('does not process X.createClass of other modules', () => {
+ it('does not process X.createElement of other modules', () => {
const source = `
import R from 'FakeReact';
const ComponentA = () => R.createElement('div', null);
@@ -224,6 +312,19 @@ describe('findAllComponentDefinitions', () => {
expect(result.length).toBe(1);
expect(result[0].value.type).toEqual('CallExpression');
});
+
+ it('resolves imported component wrapped with forwardRef', () => {
+ const source = `
+ import React from 'react';
+ import ColoredView from 'coloredView';
+ const ForwardedColoredView = React.forwardRef(ColoredView);
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(Array.isArray(result)).toBe(true);
+ expect(result.length).toBe(1);
+ expect(result[0].value.type).toEqual('CallExpression');
+ });
});
describe('regressions', () => {
diff --git a/src/resolver/__tests__/findAllExportedComponentDefinitions-test.js b/src/resolver/__tests__/findAllExportedComponentDefinitions-test.js
index d8985095000..ef664028c7e 100644
--- a/src/resolver/__tests__/findAllExportedComponentDefinitions-test.js
+++ b/src/resolver/__tests__/findAllExportedComponentDefinitions-test.js
@@ -6,19 +6,59 @@
*
*/
-import * as utils from '../../../tests/utils';
+import {
+ getParser,
+ parse as parseSource,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import findAllExportedComponentDefinitions from '../findAllExportedComponentDefinitions';
describe('findAllExportedComponentDefinitions', () => {
function parse(source) {
- return utils.parse(source);
+ return parseSource(source);
}
- function findComponents(path) {
- return findAllExportedComponentDefinitions(path, utils.getParser());
+ function findComponents(path, importer = noopImporter) {
+ return findAllExportedComponentDefinitions(path, getParser(), importer);
}
+ const mockImporter = makeMockImporter({
+ createClass: statement(`
+ export default React.createClass({});
+ import React from 'react';
+ `).get('declaration'),
+
+ classDec: statement(`
+ export default class Component extends React.Component {};
+ import React from 'react';
+ `).get('declaration'),
+
+ classExpr: statement(`
+ export default Component;
+ var Component = class extends React.Component {};
+ import React from 'react';
+ `).get('declaration'),
+
+ statelessJsx: statement(`
+ export default () =>
;
+ `).get('declaration'),
+
+ statelessCreateElement: statement(`
+ export default () => React.createElement('div', {});
+ import React from 'react';
+ `).get('declaration'),
+
+ forwardRef: statement(`
+ export default React.forwardRef((props, ref) => (
+
+ ));
+ import React from 'react';
+ `).get('declaration'),
+ });
+
describe('CommonJS module exports', () => {
describe('React.createClass', () => {
it('finds React.createClass', () => {
@@ -63,6 +103,16 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(0);
});
+
+ it('resolves an imported variable to React.createClass', () => {
+ const parsed = parse(`
+ import Component from 'createClass';
+ module.exports = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('class definitions', () => {
@@ -100,6 +150,26 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
});
+
+ it('resolves an imported variable to class declaration', () => {
+ const parsed = parse(`
+ import Component from 'classDec';
+ module.exports = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
+
+ it('resolves an imported variable to class expression', () => {
+ const parsed = parse(`
+ import Component from 'classExpr';
+ module.exports = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('stateless components', () => {
@@ -137,6 +207,26 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(0);
});
+
+ it('resolves an imported stateless component with JSX', () => {
+ const parsed = parse(`
+ import Component from 'statelessJsx';
+ module.exports = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
+
+ it('resolves an imported stateless component with React.createElement', () => {
+ const parsed = parse(`
+ import Component from 'statelessCreateElement';
+ module.exports = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('forwardRef components', () => {
@@ -181,6 +271,17 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
expect(actual[0].value.type).toEqual('CallExpression');
});
+
+ it('resolves an imported forwardRef component', () => {
+ const parsed = parse(`
+ import Component from 'forwardRef';
+ module.exports = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ expect(actual[0].value.type).toEqual('CallExpression');
+ });
});
describe('module.exports = ; / exports.foo = ;', () => {
@@ -306,6 +407,17 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'createClass';
+ exports.ComponentA = Component;
+ exports.ComponentB = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('class definition', () => {
@@ -373,6 +485,17 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'classDec';
+ exports.ComponentA = Component;
+ exports.ComponentB = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
});
});
@@ -459,6 +582,16 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'createClass';
+ export default Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('class definition', () => {
@@ -525,6 +658,16 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'classDec';
+ export default Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('forwardRef components', () => {
@@ -569,6 +712,16 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
expect(actual[0].value.type).toEqual('CallExpression');
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'forwardRef';
+ export default Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
});
@@ -653,6 +806,17 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'createClass';
+ export let ComponentA = Component;
+ export let ComponentB = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('class definition', () => {
@@ -739,6 +903,17 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
expect(actual[0].node.type).toBe('ClassExpression');
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'classDec';
+ export let ComponentA = Component;
+ export let ComponentB = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('stateless components', () => {
@@ -817,6 +992,17 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
expect(actual[0].node.type).toBe('FunctionExpression');
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component1 from 'statelessJsx';
+ import Component2 from 'statelessCreateElement';
+ export var ComponentA = Component1, ComponentB = Component2;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(2);
+ });
});
describe('forwardRef components', () => {
@@ -837,6 +1023,17 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
expect(actual[0].value.type).toEqual('CallExpression');
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'forwardRef';
+ export let ComponentA = Component;
+ export let ComponentB = Component;
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
});
@@ -931,6 +1128,16 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'createClass';
+ export { Component, Component as ComponentB };
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('class definition', () => {
@@ -1021,6 +1228,16 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
expect(actual[0].node.type).toBe('ClassExpression');
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'classDec';
+ export { Component, Component as ComponentB };
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
describe('stateless components', () => {
@@ -1097,6 +1314,17 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
expect(actual[0].node.type).toBe('ArrowFunctionExpression');
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import ComponentA from 'statelessJsx';
+ import ComponentB from 'statelessCreateElement';
+ export { ComponentA, ComponentB };
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(2);
+ });
});
describe('forwardRef components', () => {
@@ -1119,6 +1347,16 @@ describe('findAllExportedComponentDefinitions', () => {
expect(actual.length).toBe(1);
expect(actual[0].value.type).toEqual('CallExpression');
});
+
+ it('supports imported components', () => {
+ const parsed = parse(`
+ import Component from 'forwardRef';
+ export { Component, Component as ComponentB };
+ `);
+ const actual = findComponents(parsed, mockImporter);
+
+ expect(actual.length).toBe(1);
+ });
});
});
diff --git a/src/resolver/__tests__/findExportedComponentDefinition-test.js b/src/resolver/__tests__/findExportedComponentDefinition-test.js
index 4875deb649a..4f453207182 100644
--- a/src/resolver/__tests__/findExportedComponentDefinition-test.js
+++ b/src/resolver/__tests__/findExportedComponentDefinition-test.js
@@ -6,17 +6,58 @@
*
*/
-import * as utils from '../../../tests/utils';
+import {
+ getParser,
+ parse as parseSource,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import findExportedComponentDefinition from '../findExportedComponentDefinition';
describe('findExportedComponentDefinition', () => {
- function parse(source) {
+ function parse(source, importer = noopImporter) {
return findExportedComponentDefinition(
- utils.parse(source),
- utils.getParser(),
+ parseSource(source),
+ getParser(),
+ importer,
);
}
+ const mockImporter = makeMockImporter({
+ createClass: statement(`
+ export default React.createClass({});
+ import React from 'react';
+ `).get('declaration'),
+
+ classDec: statement(`
+ export default class Component extends React.Component {};
+ import React from 'react';
+ `).get('declaration'),
+
+ classExpr: statement(`
+ export default Component;
+ var Component = class extends React.Component {};
+ import React from 'react';
+ `).get('declaration'),
+
+ statelessJsx: statement(`
+ export default () =>
;
+ `).get('declaration'),
+
+ statelessCreateElement: statement(`
+ export default () => React.createElement('div', {});
+ import React from 'react';
+ `).get('declaration'),
+
+ forwardRef: statement(`
+ export default React.forwardRef((props, ref) => (
+
+ ));
+ import React from 'react';
+ `).get('declaration'),
+ });
+
describe('CommonJS module exports', () => {
describe('React.createClass', () => {
it('finds React.createClass', () => {
@@ -94,6 +135,15 @@ describe('findExportedComponentDefinition', () => {
expect(parse(source)).toBeUndefined();
});
+
+ it('resolves an imported variable to React.createClass', () => {
+ const source = `
+ import Component from 'createClass';
+ module.exports = Component;
+ `;
+
+ expect(parse(source, mockImporter)).toBeDefined();
+ });
});
describe('class definitions', () => {
@@ -132,6 +182,28 @@ describe('findExportedComponentDefinition', () => {
expect(result).toBeDefined();
expect(result.node.type).toBe('ClassDeclaration');
});
+
+ it('resolves an imported variable to class declaration', () => {
+ const source = `
+ import Component from 'classDec';
+ module.exports = Component;
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ClassDeclaration');
+ });
+
+ it('resolves an imported variable to class expression', () => {
+ const source = `
+ import Component from 'classExpr';
+ module.exports = Component;
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ClassExpression');
+ });
});
describe('stateless components', () => {
@@ -164,6 +236,24 @@ describe('findExportedComponentDefinition', () => {
expect(parse(source)).toBeUndefined();
});
+
+ it('resolves an imported stateless component with JSX', () => {
+ const source = `
+ import Component from 'statelessJsx';
+ module.exports = Component;
+ `;
+
+ expect(parse(source, mockImporter)).toBeDefined();
+ });
+
+ it('resolves an imported stateless component with React.createElement', () => {
+ const source = `
+ import Component from 'statelessCreateElement';
+ module.exports = Component;
+ `;
+
+ expect(parse(source, mockImporter)).toBeDefined();
+ });
});
describe('module.exports = ; / exports.foo = ;', () => {
@@ -210,6 +300,15 @@ describe('findExportedComponentDefinition', () => {
expect(parse(source)).toBeDefined();
});
+
+ it('supports imported components', () => {
+ const source = `
+ import Component from 'createClass';
+ exports.ComponentB = Component;
+ `;
+
+ expect(parse(source, mockImporter)).toBeDefined();
+ });
});
describe('class definition', () => {
@@ -261,6 +360,17 @@ describe('findExportedComponentDefinition', () => {
expect(result).toBeDefined();
expect(result.node.type).toBe('ClassDeclaration');
});
+
+ it('supports imported components', () => {
+ const source = `
+ import Component from 'classDec';
+ exports.ComponentB = Component;
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ClassDeclaration');
+ });
});
});
});
@@ -311,6 +421,15 @@ describe('findExportedComponentDefinition', () => {
expect(parse(source)).toBeDefined();
});
+
+ it('supports imported components', () => {
+ const source = `
+ import Component from 'createClass';
+ export default Component;
+ `;
+
+ expect(parse(source, mockImporter)).toBeDefined();
+ });
});
describe('class definition', () => {
@@ -401,6 +520,17 @@ describe('findExportedComponentDefinition', () => {
expect(result).toBeDefined();
expect(result.node.type).toBe('ClassDeclaration');
});
+
+ it('supports imported components', () => {
+ const source = `
+ import Component from 'classDec';
+ export default Component;
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ClassDeclaration');
+ });
});
});
@@ -463,6 +593,15 @@ describe('findExportedComponentDefinition', () => {
expect(parse(source)).toBeDefined();
});
+
+ it('supports imported components', () => {
+ const source = `
+ import Component from 'createClass';
+ export let ComponentB = Component;
+ `;
+
+ expect(parse(source, mockImporter)).toBeDefined();
+ });
});
describe('class definition', () => {
@@ -532,6 +671,17 @@ describe('findExportedComponentDefinition', () => {
expect(result).toBeDefined();
expect(result.node.type).toBe('ClassExpression');
});
+
+ it('supports imported components', () => {
+ const source = `
+ import Component from 'classDec';
+ export let ComponentB = Component;
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ClassDeclaration');
+ });
});
describe('stateless components', () => {
@@ -601,6 +751,26 @@ describe('findExportedComponentDefinition', () => {
expect(result).toBeDefined();
expect(result.node.type).toBe('FunctionExpression');
});
+
+ it('supports imported components', () => {
+ let source = `
+ import Component from 'statelessJsx';
+ export var ComponentA = Component;
+ `;
+
+ let result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ArrowFunctionExpression');
+
+ source = `
+ import Component from 'statelessCreateElement';
+ export var ComponentB = Component;
+ `;
+
+ result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ArrowFunctionExpression');
+ });
});
});
@@ -654,6 +824,15 @@ describe('findExportedComponentDefinition', () => {
expect(parse(source)).toBeDefined();
});
+
+ it('supports imported components', () => {
+ const source = `
+ import Component from 'createClass';
+ export { Component };
+ `;
+
+ expect(parse(source, mockImporter)).toBeDefined();
+ });
});
describe('class definition', () => {
@@ -712,6 +891,17 @@ describe('findExportedComponentDefinition', () => {
expect(result).toBeDefined();
expect(result.node.type).toBe('ClassExpression');
});
+
+ it('supports imported components', () => {
+ const source = `
+ import Component from 'classDec';
+ export { Component };
+ `;
+
+ const result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ClassDeclaration');
+ });
});
describe('stateless components', () => {
@@ -770,6 +960,26 @@ describe('findExportedComponentDefinition', () => {
expect(result).toBeDefined();
expect(result.node.type).toBe('ArrowFunctionExpression');
});
+
+ it('supports imported components', () => {
+ let source = `
+ import Component from 'statelessJsx';
+ export { Component as ComponentA };
+ `;
+
+ let result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ArrowFunctionExpression');
+
+ source = `
+ import Component from 'statelessCreateElement';
+ export { Component as ComponentB };
+ `;
+
+ result = parse(source, mockImporter);
+ expect(result).toBeDefined();
+ expect(result.node.type).toBe('ArrowFunctionExpression');
+ });
});
});
diff --git a/src/resolver/findAllComponentDefinitions.js b/src/resolver/findAllComponentDefinitions.js
index 3e3396e8c7e..7eaa4ef3e3f 100644
--- a/src/resolver/findAllComponentDefinitions.js
+++ b/src/resolver/findAllComponentDefinitions.js
@@ -14,6 +14,8 @@ import isReactForwardRefCall from '../utils/isReactForwardRefCall';
import isStatelessComponent from '../utils/isStatelessComponent';
import normalizeClassDefinition from '../utils/normalizeClassDefinition';
import resolveToValue from '../utils/resolveToValue';
+import type { Parser } from '../babelParser';
+import type { Importer } from '../types';
/**
* Given an AST, this function tries to find all object expressions that are
@@ -21,11 +23,13 @@ import resolveToValue from '../utils/resolveToValue';
*/
export default function findAllReactCreateClassCalls(
ast: ASTNode,
+ parser: Parser,
+ importer: Importer,
): Array {
const definitions = new Set();
function classVisitor(path) {
- if (isReactComponentClass(path)) {
+ if (isReactComponentClass(path, importer)) {
normalizeClassDefinition(path);
definitions.add(path);
}
@@ -33,7 +37,7 @@ export default function findAllReactCreateClassCalls(
}
function statelessVisitor(path) {
- if (isStatelessComponent(path)) {
+ if (isStatelessComponent(path, importer)) {
definitions.add(path);
}
return false;
@@ -46,17 +50,17 @@ export default function findAllReactCreateClassCalls(
visitClassExpression: classVisitor,
visitClassDeclaration: classVisitor,
visitCallExpression: function(path) {
- if (isReactForwardRefCall(path)) {
+ if (isReactForwardRefCall(path, importer)) {
// If the the inner function was previously identified as a component
// replace it with the parent node
- const inner = resolveToValue(path.get('arguments', 0));
+ const inner = resolveToValue(path.get('arguments', 0), importer);
definitions.delete(inner);
definitions.add(path);
// Do not traverse into arguments
return false;
- } else if (isReactCreateClassCall(path)) {
- const resolvedPath = resolveToValue(path.get('arguments', 0));
+ } else if (isReactCreateClassCall(path, importer)) {
+ const resolvedPath = resolveToValue(path.get('arguments', 0), importer);
if (t.ObjectExpression.check(resolvedPath.node)) {
definitions.add(resolvedPath);
}
diff --git a/src/resolver/findAllExportedComponentDefinitions.js b/src/resolver/findAllExportedComponentDefinitions.js
index a0f26d02b53..c52c0be3681 100644
--- a/src/resolver/findAllExportedComponentDefinitions.js
+++ b/src/resolver/findAllExportedComponentDefinitions.js
@@ -17,33 +17,41 @@ import normalizeClassDefinition from '../utils/normalizeClassDefinition';
import resolveExportDeclaration from '../utils/resolveExportDeclaration';
import resolveToValue from '../utils/resolveToValue';
import resolveHOC from '../utils/resolveHOC';
+import type { Parser } from '../babelParser';
+import type { Importer } from '../types';
function ignore(): false {
return false;
}
-function isComponentDefinition(path: NodePath): boolean {
+function isComponentDefinition(path: NodePath, importer: Importer): boolean {
return (
- isReactCreateClassCall(path) ||
- isReactComponentClass(path) ||
- isStatelessComponent(path) ||
- isReactForwardRefCall(path)
+ isReactCreateClassCall(path, importer) ||
+ isReactComponentClass(path, importer) ||
+ isStatelessComponent(path, importer) ||
+ isReactForwardRefCall(path, importer)
);
}
-function resolveDefinition(definition: NodePath): ?NodePath {
- if (isReactCreateClassCall(definition)) {
+function resolveDefinition(
+ definition: NodePath,
+ importer: Importer,
+): ?NodePath {
+ if (isReactCreateClassCall(definition, importer)) {
// return argument
- const resolvedPath = resolveToValue(definition.get('arguments', 0));
+ const resolvedPath = resolveToValue(
+ definition.get('arguments', 0),
+ importer,
+ );
if (t.ObjectExpression.check(resolvedPath.node)) {
return resolvedPath;
}
- } else if (isReactComponentClass(definition)) {
+ } else if (isReactComponentClass(definition, importer)) {
normalizeClassDefinition(definition);
return definition;
} else if (
- isStatelessComponent(definition) ||
- isReactForwardRefCall(definition)
+ isStatelessComponent(definition, importer) ||
+ isReactForwardRefCall(definition, importer)
) {
return definition;
}
@@ -67,23 +75,31 @@ function resolveDefinition(definition: NodePath): ?NodePath {
*/
export default function findExportedComponentDefinitions(
ast: ASTNode,
+ parser: Parser,
+ importer: Importer,
): Array {
const components: Array = [];
function exportDeclaration(path: NodePath): ?boolean {
- const definitions: Array = resolveExportDeclaration(path)
+ const definitions: Array = resolveExportDeclaration(
+ path,
+ importer,
+ )
.reduce((acc, definition) => {
- if (isComponentDefinition(definition)) {
+ if (isComponentDefinition(definition, importer)) {
acc.push(definition);
} else {
- const resolved = resolveToValue(resolveHOC(definition));
- if (isComponentDefinition(resolved)) {
+ const resolved = resolveToValue(
+ resolveHOC(definition, importer),
+ importer,
+ );
+ if (isComponentDefinition(resolved, importer)) {
acc.push(resolved);
}
}
return acc;
}, [])
- .map(definition => resolveDefinition(definition));
+ .map(definition => resolveDefinition(definition, importer));
if (definitions.length === 0) {
return false;
@@ -117,19 +133,19 @@ export default function findExportedComponentDefinitions(
visitAssignmentExpression: function(path: NodePath): ?boolean {
// Ignore anything that is not `exports.X = ...;` or
// `module.exports = ...;`
- if (!isExportsOrModuleAssignment(path)) {
+ if (!isExportsOrModuleAssignment(path, importer)) {
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 (!isComponentDefinition(path)) {
- path = resolveToValue(resolveHOC(path));
- if (!isComponentDefinition(path)) {
+ path = resolveToValue(path.get('right'), importer);
+ if (!isComponentDefinition(path, importer)) {
+ path = resolveToValue(resolveHOC(path, importer), importer);
+ if (!isComponentDefinition(path, importer)) {
return false;
}
}
- const definition = resolveDefinition(path);
+ const definition = resolveDefinition(path, importer);
if (definition && components.indexOf(definition) === -1) {
components.push(definition);
}
diff --git a/src/resolver/findExportedComponentDefinition.js b/src/resolver/findExportedComponentDefinition.js
index 0ad8b0028a0..2563e426263 100644
--- a/src/resolver/findExportedComponentDefinition.js
+++ b/src/resolver/findExportedComponentDefinition.js
@@ -17,6 +17,8 @@ import normalizeClassDefinition from '../utils/normalizeClassDefinition';
import resolveExportDeclaration from '../utils/resolveExportDeclaration';
import resolveToValue from '../utils/resolveToValue';
import resolveHOC from '../utils/resolveHOC';
+import type { Parser } from '../babelParser';
+import type { Importer } from '../types';
const ERROR_MULTIPLE_DEFINITIONS =
'Multiple exported component definitions found.';
@@ -25,28 +27,31 @@ function ignore() {
return false;
}
-function isComponentDefinition(path) {
+function isComponentDefinition(path, importer) {
return (
- isReactCreateClassCall(path) ||
- isReactComponentClass(path) ||
- isStatelessComponent(path) ||
- isReactForwardRefCall(path)
+ isReactCreateClassCall(path, importer) ||
+ isReactComponentClass(path, importer) ||
+ isStatelessComponent(path, importer) ||
+ isReactForwardRefCall(path, importer)
);
}
-function resolveDefinition(definition) {
- if (isReactCreateClassCall(definition)) {
+function resolveDefinition(definition, importer) {
+ if (isReactCreateClassCall(definition, importer)) {
// return argument
- const resolvedPath = resolveToValue(definition.get('arguments', 0));
+ const resolvedPath = resolveToValue(
+ definition.get('arguments', 0),
+ importer,
+ );
if (t.ObjectExpression.check(resolvedPath.node)) {
return resolvedPath;
}
- } else if (isReactComponentClass(definition)) {
+ } else if (isReactComponentClass(definition, importer)) {
normalizeClassDefinition(definition);
return definition;
} else if (
- isStatelessComponent(definition) ||
- isReactForwardRefCall(definition)
+ isStatelessComponent(definition, importer) ||
+ isReactForwardRefCall(definition, importer)
) {
return definition;
}
@@ -70,17 +75,22 @@ function resolveDefinition(definition) {
*/
export default function findExportedComponentDefinition(
ast: ASTNode,
+ parser: Parser,
+ importer: Importer,
): ?NodePath {
let foundDefinition;
function exportDeclaration(path) {
- const definitions = resolveExportDeclaration(path).reduce(
+ const definitions = resolveExportDeclaration(path, importer).reduce(
(acc, definition) => {
- if (isComponentDefinition(definition)) {
+ if (isComponentDefinition(definition, importer)) {
acc.push(definition);
} else {
- const resolved = resolveToValue(resolveHOC(definition));
- if (isComponentDefinition(resolved)) {
+ const resolved = resolveToValue(
+ resolveHOC(definition, importer),
+ importer,
+ );
+ if (isComponentDefinition(resolved, importer)) {
acc.push(resolved);
}
}
@@ -96,7 +106,7 @@ export default function findExportedComponentDefinition(
// If a file exports multiple components, ... complain!
throw new Error(ERROR_MULTIPLE_DEFINITIONS);
}
- foundDefinition = resolveDefinition(definitions[0]);
+ foundDefinition = resolveDefinition(definitions[0], importer);
return false;
}
@@ -121,15 +131,15 @@ export default function findExportedComponentDefinition(
visitAssignmentExpression: function(path) {
// Ignore anything that is not `exports.X = ...;` or
// `module.exports = ...;`
- if (!isExportsOrModuleAssignment(path)) {
+ if (!isExportsOrModuleAssignment(path, importer)) {
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 (!isComponentDefinition(path)) {
- path = resolveToValue(resolveHOC(path));
- if (!isComponentDefinition(path)) {
+ path = resolveToValue(path.get('right'), importer);
+ if (!isComponentDefinition(path, importer)) {
+ path = resolveToValue(resolveHOC(path, importer), importer);
+ if (!isComponentDefinition(path, importer)) {
return false;
}
}
@@ -137,7 +147,7 @@ export default function findExportedComponentDefinition(
// If a file exports multiple components, ... complain!
throw new Error(ERROR_MULTIPLE_DEFINITIONS);
}
- foundDefinition = resolveDefinition(path);
+ foundDefinition = resolveDefinition(path, importer);
return false;
},
});
diff --git a/src/types.js b/src/types.js
index ac82bff9067..da861e720ae 100644
--- a/src/types.js
+++ b/src/types.js
@@ -119,12 +119,15 @@ export type PropDescriptor = {
description?: string,
};
+export type Importer = (path: NodePath, name: string) => ?NodePath;
+
export type Handler = (
documentation: Documentation,
path: NodePath,
- parser: Parser,
+ importer: Importer,
) => void;
export type Resolver = (
node: ASTNode,
parser: Parser,
+ importer: Importer,
) => ?NodePath | ?Array;
diff --git a/src/utils/__tests__/__snapshots__/getPropType-test.js.snap b/src/utils/__tests__/__snapshots__/getPropType-test.js.snap
index a277a74a0c0..23477fa8d86 100644
--- a/src/utils/__tests__/__snapshots__/getPropType-test.js.snap
+++ b/src/utils/__tests__/__snapshots__/getPropType-test.js.snap
@@ -146,6 +146,22 @@ Object {
}
`;
+exports[`getPropType resolve identifier to their values correctly resolves SpreadElements in arrays from imported values 1`] = `
+Object {
+ "name": "enum",
+ "value": Array [
+ Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ ],
+}
+`;
+
exports[`getPropType resolve identifier to their values correctly resolves nested SpreadElements in arrays 1`] = `
Object {
"name": "enum",
@@ -162,7 +178,7 @@ Object {
}
`;
-exports[`getPropType resolve identifier to their values does not resolve external values 1`] = `
+exports[`getPropType resolve identifier to their values does not resolve external values without proper importer 1`] = `
Object {
"computed": true,
"name": "enum",
@@ -202,6 +218,50 @@ Object {
}
`;
+exports[`getPropType resolve identifier to their values resolves imported identifier to their initialization value in array 1`] = `
+Object {
+ "name": "enum",
+ "value": Array [
+ Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ ],
+}
+`;
+
+exports[`getPropType resolve identifier to their values resolves imported variables to their values 1`] = `
+Object {
+ "name": "shape",
+ "value": Object {
+ "bar": Object {
+ "name": "string",
+ "required": false,
+ },
+ },
+}
+`;
+
+exports[`getPropType resolve identifier to their values resolves importer identifier to initialization value 1`] = `
+Object {
+ "name": "enum",
+ "value": Array [
+ Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ ],
+}
+`;
+
exports[`getPropType resolve identifier to their values resolves memberExpressions 1`] = `
Object {
"name": "enum",
@@ -218,6 +278,22 @@ Object {
}
`;
+exports[`getPropType resolve identifier to their values resolves memberExpressions from imported objects 1`] = `
+Object {
+ "name": "enum",
+ "value": Array [
+ Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ ],
+}
+`;
+
exports[`getPropType resolve identifier to their values resolves simple identifier to their initialization value 1`] = `
Object {
"name": "enum",
@@ -250,6 +326,38 @@ Object {
}
`;
+exports[`getPropType resolve identifier to their values resolves values from imported Object.keys call 1`] = `
+Object {
+ "name": "enum",
+ "value": Array [
+ Object {
+ "computed": false,
+ "value": "\\"FOO\\"",
+ },
+ Object {
+ "computed": false,
+ "value": "\\"BAR\\"",
+ },
+ ],
+}
+`;
+
+exports[`getPropType resolve identifier to their values resolves values from imported Object.values call 1`] = `
+Object {
+ "name": "enum",
+ "value": Array [
+ Object {
+ "computed": false,
+ "value": "\\"bar\\"",
+ },
+ Object {
+ "computed": false,
+ "value": "\\"foo\\"",
+ },
+ ],
+}
+`;
+
exports[`getPropType resolve identifier to their values resolves variables to their values 1`] = `
Object {
"name": "shape",
diff --git a/src/utils/__tests__/getFlowType-test.js b/src/utils/__tests__/getFlowType-test.js
index 367c974ecb4..8ae5207e827 100644
--- a/src/utils/__tests__/getFlowType-test.js
+++ b/src/utils/__tests__/getFlowType-test.js
@@ -7,9 +7,61 @@
*/
import getFlowType from '../getFlowType';
-import { expression, statement } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
describe('getFlowType', () => {
+ const mockImporter = makeMockImporter({
+ abc: statement(`
+ export type abc = number;
+ `).get('declaration'),
+
+ def: statement(`
+ export type def = boolean;
+ `).get('declaration'),
+
+ xyz: statement(`
+ export type xyz = string;
+ `).get('declaration'),
+
+ maybe: statement(`
+ export type maybe = ?string;
+ `).get('declaration'),
+
+ barbaz: statement(`
+ export type barbaz = "bar" | "baz";
+ `).get('declaration'),
+
+ recTup: statement(`
+ export type recTup = [abc, xyz];
+ import type { abc } from 'abc';
+ import type { xyz } from 'xyz';
+ `).get('declaration'),
+
+ MyType: statement(`
+ export type MyType = { a: string, b: ?notImported };
+ `).get('declaration'),
+
+ MyGenericType: statement(`
+ export type MyType = { a: T, b: Array };
+ `).get('declaration'),
+
+ fruits: statement(`
+ export default {
+ 'apple': '🍎',
+ 'banana': '🍌',
+ };
+ `).get('declaration'),
+
+ otherFruits: statement(`
+ export type OtherFruits = { orange: string };
+ `).get('declaration'),
+ });
+
it('detects simple types', () => {
const simplePropTypes = [
'string',
@@ -31,7 +83,7 @@ describe('getFlowType', () => {
const typePath = expression('x: ' + type)
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({ name: type });
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({ name: type });
});
});
@@ -42,7 +94,7 @@ describe('getFlowType', () => {
const typePath = expression(`x: ${value}`)
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'literal',
value: `${value}`,
});
@@ -53,21 +105,45 @@ describe('getFlowType', () => {
const typePath = expression('x: xyz')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({ name: 'xyz' });
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({ name: 'xyz' });
+ });
+
+ it('resolves an imported type', () => {
+ const typePath = statement(`
+ (x: xyz);
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'string',
+ });
});
it('detects external nullable type', () => {
const typePath = expression('x: ?xyz')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({ name: 'xyz', nullable: true });
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
+ name: 'xyz',
+ nullable: true,
+ });
+ });
+
+ it('resolves an imported nullable type', () => {
+ const typePath = statement(`
+ (x: ?xyz);
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'string',
+ nullable: true,
+ });
});
it('detects array type shorthand optional', () => {
const typePath = expression('x: ?number[]')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'Array',
elements: [{ name: 'number' }],
raw: 'number[]',
@@ -79,7 +155,7 @@ describe('getFlowType', () => {
const typePath = expression('x: (?number)[]')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'Array',
elements: [{ name: 'number', nullable: true }],
raw: '(?number)[]',
@@ -90,7 +166,7 @@ describe('getFlowType', () => {
const typePath = expression('x: number[]')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'Array',
elements: [{ name: 'number' }],
raw: 'number[]',
@@ -101,51 +177,131 @@ describe('getFlowType', () => {
const typePath = expression('x: Array')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'Array',
elements: [{ name: 'number' }],
raw: 'Array',
});
});
+ it('resolves imported types used for arrays', () => {
+ let typePath = statement(`
+ (x: Array);
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'Array',
+ elements: [{ name: 'string' }],
+ raw: 'Array',
+ });
+
+ typePath = statement(`
+ (x: xyz[]);
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'Array',
+ elements: [{ name: 'string' }],
+ raw: 'xyz[]',
+ });
+
+ typePath = statement(`
+ (x: ?xyz[]);
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'Array',
+ elements: [{ name: 'string' }],
+ raw: 'xyz[]',
+ nullable: true,
+ });
+
+ typePath = statement(`
+ (x: (?xyz)[]);
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'Array',
+ elements: [{ name: 'string', nullable: true }],
+ raw: '(?xyz)[]',
+ });
+ });
+
it('detects array type with multiple types', () => {
const typePath = expression('x: Array')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'Array',
elements: [{ name: 'number' }, { name: 'xyz' }],
raw: 'Array',
});
});
+ it('resolves array type with multiple imported types', () => {
+ const typePath = statement(`
+ (x: Array);
+ import type { abc } from 'abc';
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'Array',
+ elements: [{ name: 'number' }, { name: 'string' }],
+ raw: 'Array',
+ });
+ });
+
it('detects class type', () => {
const typePath = expression('x: Class')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'Class',
elements: [{ name: 'Boolean' }],
raw: 'Class',
});
});
+ it('resolves imported subtype for class type', () => {
+ const typePath = statement(`
+ (x: Class);
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'Class',
+ elements: [{ name: 'string' }],
+ raw: 'Class',
+ });
+ });
+
it('detects function type with subtype', () => {
const typePath = expression('x: Function')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'Function',
elements: [{ name: 'xyz' }],
raw: 'Function',
});
});
+ it('resolves imported subtype for function type', () => {
+ const typePath = statement(`
+ (x: Function);
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'Function',
+ elements: [{ name: 'string' }],
+ raw: 'Function',
+ });
+ });
+
it('detects object types', () => {
const typePath = expression('x: { a: string, b?: xyz }')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -162,7 +318,7 @@ describe('getFlowType', () => {
const typePath = expression('x: { a: string, b: ?xyz }')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -175,11 +331,46 @@ describe('getFlowType', () => {
});
});
+ it('resolves imported types used for objects', () => {
+ const typePath = statement(`
+ (x: { a: abc, b: ?xyz, c?: xyz, d: maybe, e?: maybe });
+ import type { abc } from 'abc';
+ import type { xyz } from 'xyz';
+ import type { maybe } from 'maybe';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ signature: {
+ properties: [
+ { key: 'a', value: { name: 'number', required: true } },
+ {
+ key: 'b',
+ value: { name: 'string', nullable: true, required: false },
+ },
+ {
+ key: 'c',
+ value: { name: 'string', nullable: true, required: false },
+ },
+ {
+ key: 'd',
+ value: { name: 'string', nullable: true, required: false },
+ },
+ {
+ key: 'e',
+ value: { name: 'string', nullable: true, required: false },
+ },
+ ],
+ },
+ raw: '{ a: abc, b: ?xyz, c?: xyz, d: maybe, e?: maybe }',
+ });
+ });
+
it('detects union type', () => {
const typePath = expression('x: string | xyz | "foo" | void')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'union',
elements: [
{ name: 'string' },
@@ -191,11 +382,35 @@ describe('getFlowType', () => {
});
});
+ it('resolves imported types within union type', () => {
+ const typePath = statement(`
+ (x: string | barbaz | "foo" | void);
+ import type { barbaz } from 'barbaz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'union',
+ elements: [
+ { name: 'string' },
+ {
+ name: 'union',
+ elements: [
+ { name: 'literal', value: '"bar"' },
+ { name: 'literal', value: '"baz"' },
+ ],
+ raw: '"bar" | "baz"',
+ },
+ { name: 'literal', value: '"foo"' },
+ { name: 'void' },
+ ],
+ raw: 'string | barbaz | "foo" | void',
+ });
+ });
+
it('detects intersection type', () => {
const typePath = expression('x: string & xyz & "foo" & void')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'intersection',
elements: [
{ name: 'string' },
@@ -207,13 +422,37 @@ describe('getFlowType', () => {
});
});
+ it('resolves imported types within intersection type', () => {
+ const typePath = statement(`
+ (x: string & barbaz & "foo" & void);
+ import type { barbaz } from 'barbaz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'intersection',
+ elements: [
+ { name: 'string' },
+ {
+ name: 'union',
+ elements: [
+ { name: 'literal', value: '"bar"' },
+ { name: 'literal', value: '"baz"' },
+ ],
+ raw: '"bar" | "baz"',
+ },
+ { name: 'literal', value: '"foo"' },
+ { name: 'void' },
+ ],
+ raw: 'string & barbaz & "foo" & void',
+ });
+ });
+
it('detects function signature type', () => {
const typePath = expression(
'x: (p1: number, p2: ?string, ...rest: Array) => boolean',
)
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'function',
signature: {
@@ -240,7 +479,7 @@ describe('getFlowType', () => {
const typePath = expression('x: (number, ?string) => boolean')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'function',
signature: {
@@ -258,7 +497,7 @@ describe('getFlowType', () => {
const typePath = expression('x: string => boolean')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'function',
signature: {
@@ -273,7 +512,7 @@ describe('getFlowType', () => {
const typePath = expression('x: { (str: string): string, token: string }')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -294,13 +533,107 @@ describe('getFlowType', () => {
});
});
+ it('resolves function signature types with imported types', () => {
+ let typePath = statement(`
+ (x: (p1: abc, p2: ?xyz, ...rest: Array) => def);
+ import type { abc } from 'abc';
+ import type { def } from 'def';
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'function',
+ signature: {
+ arguments: [
+ { name: 'p1', type: { name: 'number' } },
+ { name: 'p2', type: { name: 'string', nullable: true } },
+ {
+ name: 'rest',
+ rest: true,
+ type: {
+ name: 'Array',
+ elements: [{ name: 'string', nullable: true }],
+ raw: 'Array',
+ },
+ },
+ ],
+ return: { name: 'boolean' },
+ },
+ raw: '(p1: abc, p2: ?xyz, ...rest: Array) => def',
+ });
+
+ typePath = statement(`
+ (x: (abc, ?xyz) => def);
+ import type { abc } from 'abc';
+ import type { def } from 'def';
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'function',
+ signature: {
+ arguments: [
+ { name: '', type: { name: 'number' } },
+ { name: '', type: { name: 'string', nullable: true } },
+ ],
+ return: { name: 'boolean' },
+ },
+ raw: '(abc, ?xyz) => def',
+ });
+
+ typePath = statement(`
+ (x: xyz => def);
+ import type { def } from 'def';
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'function',
+ signature: {
+ arguments: [{ name: '', type: { name: 'string' } }],
+ return: { name: 'boolean' },
+ },
+ raw: 'xyz => def',
+ });
+
+ typePath = statement(`
+ (x: { (str: xyz): abc, token: def });
+ import type { abc } from 'abc';
+ import type { def } from 'def';
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ signature: {
+ constructor: {
+ name: 'signature',
+ type: 'function',
+ signature: {
+ arguments: [{ name: 'str', type: { name: 'string' } }],
+ return: { name: 'number' },
+ },
+ raw: '(str: xyz): abc',
+ },
+ properties: [
+ { key: 'token', value: { name: 'boolean', required: true } },
+ ],
+ },
+ raw: '{ (str: xyz): abc, token: def }',
+ });
+ });
+
it('detects map signature', () => {
const typePath = expression(
'x: { [key: string]: number, [key: "xl"]: string, token: "a" | "b" }',
)
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -331,11 +664,50 @@ describe('getFlowType', () => {
});
});
+ it('resolves imported types in map signature', () => {
+ const typePath = statement(`
+ (x: { [key: xyz]: abc, [key: "xl"]: def, token: barbaz });
+ import type { abc } from 'abc';
+ import type { def } from 'def';
+ import type { xyz } from 'xyz';
+ import type { barbaz } from 'barbaz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ signature: {
+ properties: [
+ {
+ key: { name: 'string' },
+ value: { name: 'number', required: true },
+ },
+ {
+ key: { name: 'literal', value: '"xl"' },
+ value: { name: 'boolean', required: true },
+ },
+ {
+ key: 'token',
+ value: {
+ name: 'union',
+ required: true,
+ raw: '"bar" | "baz"',
+ elements: [
+ { name: 'literal', value: '"bar"' },
+ { name: 'literal', value: '"baz"' },
+ ],
+ },
+ },
+ ],
+ },
+ raw: '{ [key: xyz]: abc, [key: "xl"]: def, token: barbaz }',
+ });
+ });
+
it('detects tuple signature', () => {
const typePath = expression('x: [string, number]')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'tuple',
elements: [{ name: 'string' }, { name: 'number' }],
raw: '[string, number]',
@@ -346,7 +718,7 @@ describe('getFlowType', () => {
const typePath = expression('x: [string, number] | [number, string]')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'union',
elements: [
{
@@ -364,6 +736,44 @@ describe('getFlowType', () => {
});
});
+ it('resolves imported types used in tuple signature', () => {
+ let typePath = statement(`
+ (x: [xyz, abc]);
+ import type { abc } from 'abc';
+ import type { xyz } from 'xyz';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'tuple',
+ elements: [{ name: 'string' }, { name: 'number' }],
+ raw: '[xyz, abc]',
+ });
+
+ typePath = statement(`
+ (x: [xyz, abc] | recTup);
+ import type { abc } from 'abc';
+ import type { xyz } from 'xyz';
+ import type { recTup } from 'recTup';
+ `).get('expression', 'typeAnnotation', 'typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'union',
+ elements: [
+ {
+ name: 'tuple',
+ elements: [{ name: 'string' }, { name: 'number' }],
+ raw: '[xyz, abc]',
+ },
+ {
+ name: 'tuple',
+ elements: [{ name: 'number' }, { name: 'string' }],
+ raw: '[abc, xyz]',
+ },
+ ],
+ raw: '[xyz, abc] | recTup',
+ });
+ });
+
it('resolves types in scope', () => {
const typePath = statement(`
var x: MyType = 2;
@@ -375,7 +785,9 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({ name: 'string' });
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
+ name: 'string',
+ });
});
it('handles typeof types', () => {
@@ -389,7 +801,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -402,6 +814,32 @@ describe('getFlowType', () => {
});
});
+ it('resolves typeof of imported types', () => {
+ const typePath = statement(`
+ var x: typeof MyType = {};
+ import type { MyType } from 'MyType';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ signature: {
+ properties: [
+ { key: 'a', value: { name: 'string', required: true } },
+ {
+ key: 'b',
+ value: { name: 'notImported', nullable: true, required: true },
+ },
+ ],
+ },
+ raw: '{ a: string, b: ?notImported }',
+ });
+ });
+
it('handles qualified type identifiers', () => {
const typePath = statement(`
var x: MyType.x = {};
@@ -413,7 +851,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'MyType.x',
});
});
@@ -429,7 +867,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'MyType.x',
raw: 'MyType.x',
elements: [
@@ -451,7 +889,44 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ raw: '{ a: T, b: Array }',
+ signature: {
+ properties: [
+ {
+ key: 'a',
+ value: {
+ name: 'string',
+ required: true,
+ },
+ },
+ {
+ key: 'b',
+ value: {
+ name: 'Array',
+ raw: 'Array',
+ required: true,
+ elements: [{ name: 'string' }],
+ },
+ },
+ ],
+ },
+ });
+ });
+
+ it('resolves imported types that need subtypes', () => {
+ const typePath = statement(`
+ var x: MyGenericType = {};
+ import type { MyGenericType } from 'MyGenericType';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
name: 'signature',
type: 'object',
raw: '{ a: T, b: Array }',
@@ -490,7 +965,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
...expected,
name: type.replace('.', '').replace(/<.+>/, ''),
raw: type,
@@ -543,7 +1018,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'union',
elements: [
{ name: 'literal', value: "'apple'" },
@@ -566,7 +1041,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'union',
elements: [
{ name: 'literal', value: "'apple'" },
@@ -585,7 +1060,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'union',
elements: [
{ name: 'literal', value: 'apple' },
@@ -605,7 +1080,64 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
+ name: 'union',
+ elements: [
+ { name: 'literal', value: 'apple' },
+ { name: 'literal', value: 'banana' },
+ { name: 'literal', value: 'orange' },
+ ],
+ raw: '$Keys<{| apple: string, banana: string, ...OtherFruits |}>',
+ });
+ });
+
+ it('resolves $Keys to imported types', () => {
+ let typePath = statement(`
+ var x: $Keys = 2;
+ import CONTENTS from 'fruits';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'union',
+ elements: [
+ { name: 'literal', value: "'apple'" },
+ { name: 'literal', value: "'banana'" },
+ ],
+ raw: '$Keys',
+ });
+
+ typePath = statement(`
+ var x: $Keys = 2;
+ import CONTENTS from 'fruits';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
+ name: 'union',
+ elements: [
+ { name: 'literal', value: "'apple'" },
+ { name: 'literal', value: "'banana'" },
+ ],
+ raw: '$Keys',
+ });
+
+ typePath = statement(`
+ var x: $Keys<{| apple: string, banana: string, ...OtherFruits |}> = 2;
+ import type { OtherFruits } from 'otherFruits';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getFlowType(typePath, null, mockImporter)).toEqual({
name: 'union',
elements: [
{ name: 'literal', value: 'apple' },
@@ -626,7 +1158,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -667,7 +1199,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -692,7 +1224,7 @@ describe('getFlowType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getFlowType(typePath)).toEqual({
+ expect(getFlowType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
diff --git a/src/utils/__tests__/getMemberExpressionValuePath-test.js b/src/utils/__tests__/getMemberExpressionValuePath-test.js
index fa8ba03c10d..9d6b1104361 100644
--- a/src/utils/__tests__/getMemberExpressionValuePath-test.js
+++ b/src/utils/__tests__/getMemberExpressionValuePath-test.js
@@ -6,7 +6,7 @@
*
*/
-import { statement } from '../../../tests/utils';
+import { statement, noopImporter } from '../../../tests/utils';
import getMemberExpressionValuePath from '../getMemberExpressionValuePath';
describe('getMemberExpressionValuePath', () => {
@@ -17,7 +17,7 @@ describe('getMemberExpressionValuePath', () => {
Foo.propTypes = {};
`);
- expect(getMemberExpressionValuePath(def, 'propTypes')).toBe(
+ expect(getMemberExpressionValuePath(def, 'propTypes', noopImporter)).toBe(
def.parent.get('body', 1, 'expression', 'right'),
);
});
@@ -29,7 +29,7 @@ describe('getMemberExpressionValuePath', () => {
Bar.propTypes = { unrelated: true };
`);
- expect(getMemberExpressionValuePath(def, 'propTypes')).toBe(
+ expect(getMemberExpressionValuePath(def, 'propTypes', noopImporter)).toBe(
def.parent.get('body', 1, 'expression', 'right'),
);
});
@@ -40,7 +40,7 @@ describe('getMemberExpressionValuePath', () => {
Foo['render'] = () => {};
`);
- expect(getMemberExpressionValuePath(def, 'render')).toBe(
+ expect(getMemberExpressionValuePath(def, 'render', noopImporter)).toBe(
def.parent.get('body', 1, 'expression', 'right'),
);
});
@@ -51,7 +51,9 @@ describe('getMemberExpressionValuePath', () => {
Foo[imComputed] = () => {};
`);
- expect(getMemberExpressionValuePath(def, 'imComputed')).not.toBeDefined();
+ expect(
+ getMemberExpressionValuePath(def, 'imComputed', noopImporter),
+ ).not.toBeDefined();
});
});
describe('TaggedTemplateLiteral', () => {
@@ -61,7 +63,7 @@ describe('getMemberExpressionValuePath', () => {
Foo.propTypes = {};
`);
- expect(getMemberExpressionValuePath(def, 'propTypes')).toBe(
+ expect(getMemberExpressionValuePath(def, 'propTypes', noopImporter)).toBe(
def.parent.get('body', 1, 'expression', 'right'),
);
});
@@ -73,7 +75,7 @@ describe('getMemberExpressionValuePath', () => {
Foo.propTypes = {};
`);
- expect(getMemberExpressionValuePath(def, 'propTypes')).toBe(
+ expect(getMemberExpressionValuePath(def, 'propTypes', noopImporter)).toBe(
def.parent.get('body', 1, 'expression', 'right'),
);
});
diff --git a/src/utils/__tests__/getMemberValuePath-test.js b/src/utils/__tests__/getMemberValuePath-test.js
index 6588c568264..a70d6ddf031 100644
--- a/src/utils/__tests__/getMemberValuePath-test.js
+++ b/src/utils/__tests__/getMemberValuePath-test.js
@@ -10,7 +10,7 @@ jest.mock('../getPropertyValuePath');
jest.mock('../getClassMemberValuePath');
jest.mock('../getMemberExpressionValuePath');
-import { expression, statement } from '../../../tests/utils';
+import { expression, statement, noopImporter } from '../../../tests/utils';
import getPropertyValuePath from '../getPropertyValuePath';
import getClassMemberValuePath from '../getClassMemberValuePath';
@@ -21,50 +21,74 @@ describe('getMemberValuePath', () => {
it('handles ObjectExpressions', () => {
const path = expression('{}');
- getMemberValuePath(path, 'foo');
- expect(getPropertyValuePath).toBeCalledWith(path, 'foo');
+ getMemberValuePath(path, 'foo', noopImporter);
+ expect(getPropertyValuePath).toBeCalledWith(path, 'foo', noopImporter);
});
it('handles ClassDeclarations', () => {
const path = statement('class Foo {}');
- getMemberValuePath(path, 'foo');
- expect(getClassMemberValuePath).toBeCalledWith(path, 'foo');
+ getMemberValuePath(path, 'foo', noopImporter);
+ expect(getClassMemberValuePath).toBeCalledWith(path, 'foo', noopImporter);
});
it('handles TaggedTemplateLiterals', () => {
const path = expression('foo``');
- getMemberValuePath(path, 'foo');
- expect(getMemberExpressionValuePath).toBeCalledWith(path, 'foo');
+ getMemberValuePath(path, 'foo', noopImporter);
+ expect(getMemberExpressionValuePath).toBeCalledWith(
+ path,
+ 'foo',
+ noopImporter,
+ );
});
it('handles ClassExpressions', () => {
const path = expression('class {}');
- getMemberValuePath(path, 'foo');
- expect(getClassMemberValuePath).toBeCalledWith(path, 'foo');
+ getMemberValuePath(path, 'foo', noopImporter);
+ expect(getClassMemberValuePath).toBeCalledWith(path, 'foo', noopImporter);
});
it('handles CallExpressions', () => {
const path = expression('system({is: "button"}, "space")');
- getMemberValuePath(path, 'foo');
- expect(getMemberExpressionValuePath).toBeCalledWith(path, 'foo');
+ getMemberValuePath(path, 'foo', noopImporter);
+ expect(getMemberExpressionValuePath).toBeCalledWith(
+ path,
+ 'foo',
+ noopImporter,
+ );
});
it('tries synonyms', () => {
let path = expression('{}');
- getMemberValuePath(path, 'defaultProps');
- expect(getPropertyValuePath).toBeCalledWith(path, 'defaultProps');
- expect(getPropertyValuePath).toBeCalledWith(path, 'getDefaultProps');
+ getMemberValuePath(path, 'defaultProps', noopImporter);
+ expect(getPropertyValuePath).toBeCalledWith(
+ path,
+ 'defaultProps',
+ noopImporter,
+ );
+ expect(getPropertyValuePath).toBeCalledWith(
+ path,
+ 'getDefaultProps',
+ noopImporter,
+ );
path = statement('class Foo {}');
- getMemberValuePath(path, 'defaultProps');
- expect(getClassMemberValuePath).toBeCalledWith(path, 'defaultProps');
- expect(getClassMemberValuePath).toBeCalledWith(path, 'getDefaultProps');
+ getMemberValuePath(path, 'defaultProps', noopImporter);
+ expect(getClassMemberValuePath).toBeCalledWith(
+ path,
+ 'defaultProps',
+ noopImporter,
+ );
+ expect(getClassMemberValuePath).toBeCalledWith(
+ path,
+ 'getDefaultProps',
+ noopImporter,
+ );
});
it('returns the result of getPropertyValuePath and getClassMemberValuePath', () => {
@@ -72,10 +96,10 @@ describe('getMemberValuePath', () => {
getClassMemberValuePath.mockReturnValue(21);
let path = expression('{}');
- expect(getMemberValuePath(path, 'defaultProps')).toBe(42);
+ expect(getMemberValuePath(path, 'defaultProps', noopImporter)).toBe(42);
path = statement('class Foo {}');
- expect(getMemberValuePath(path, 'defaultProps')).toBe(21);
+ expect(getMemberValuePath(path, 'defaultProps', noopImporter)).toBe(21);
});
});
diff --git a/src/utils/__tests__/getMethodDocumentation-test.js b/src/utils/__tests__/getMethodDocumentation-test.js
index ab3bbc20f3f..da7611fa0b4 100644
--- a/src/utils/__tests__/getMethodDocumentation-test.js
+++ b/src/utils/__tests__/getMethodDocumentation-test.js
@@ -6,10 +6,28 @@
*
*/
-import { statement } from '../../../tests/utils';
+import {
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import getMethodDocumentation from '../getMethodDocumentation';
describe('getMethodDocumentation', () => {
+ const mockImporter = makeMockImporter({
+ hello: statement(`
+ export default () => {};
+ `).get('declaration'),
+
+ bar: statement(`
+ export default (bar: number) => {};
+ `).get('declaration'),
+
+ baz: statement(`
+ export default (): number => {};
+ `).get('declaration'),
+ });
+
describe('name', () => {
it('extracts the method name', () => {
const def = statement(`
@@ -18,7 +36,23 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual({
+ expect(getMethodDocumentation(method, noopImporter)).toEqual({
+ name: 'hello',
+ docblock: null,
+ modifiers: [],
+ returns: null,
+ params: [],
+ });
+ });
+
+ it('handles function assignment', () => {
+ const def = statement(`
+ class Foo {
+ hello = () => {}
+ }
+ `);
+ const method = def.get('body', 'body', 0);
+ expect(getMethodDocumentation(method, noopImporter)).toEqual({
name: 'hello',
docblock: null,
modifiers: [],
@@ -34,7 +68,7 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toMatchSnapshot();
+ expect(getMethodDocumentation(method, noopImporter)).toMatchSnapshot();
});
it('ignores complex computed method name', () => {
@@ -44,7 +78,24 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toMatchSnapshot();
+ expect(getMethodDocumentation(method, noopImporter)).toMatchSnapshot();
+ });
+
+ it('resolves assignment of imported function', () => {
+ const def = statement(`
+ class Foo {
+ hello = hello;
+ }
+ import hello from 'hello';
+ `);
+ const method = def.get('body', 'body', 0);
+ expect(getMethodDocumentation(method, mockImporter)).toEqual({
+ name: 'hello',
+ docblock: null,
+ modifiers: [],
+ returns: null,
+ params: [],
+ });
});
});
@@ -59,7 +110,26 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual({
+ expect(getMethodDocumentation(method, noopImporter)).toEqual({
+ name: 'foo',
+ docblock: "Don't use this!",
+ modifiers: [],
+ returns: null,
+ params: [],
+ });
+ });
+
+ it('extracts docblock on function assignment', () => {
+ const def = statement(`
+ class Foo {
+ /**
+ * Don't use this!
+ */
+ foo = () => {}
+ }
+ `);
+ const method = def.get('body', 'body', 0);
+ expect(getMethodDocumentation(method, noopImporter)).toEqual({
name: 'foo',
docblock: "Don't use this!",
modifiers: [],
@@ -87,7 +157,42 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual(
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
+ methodParametersDoc([
+ {
+ name: 'bar',
+ type: { name: 'number' },
+ },
+ ]),
+ );
+ });
+
+ it('extracts flow type info on function assignment', () => {
+ const def = statement(`
+ class Foo {
+ foo = (bar: number) => {}
+ }
+ `);
+ const method = def.get('body', 'body', 0);
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
+ methodParametersDoc([
+ {
+ name: 'bar',
+ type: { name: 'number' },
+ },
+ ]),
+ );
+ });
+
+ it('resolves flow type info on imported functions', () => {
+ const def = statement(`
+ class Foo {
+ foo = bar
+ }
+ import bar from 'bar';
+ `);
+ const method = def.get('body', 'body', 0);
+ expect(getMethodDocumentation(method, mockImporter)).toEqual(
methodParametersDoc([
{
name: 'bar',
@@ -115,7 +220,9 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual(methodModifiersDoc([]));
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
+ methodModifiersDoc([]),
+ );
});
it('detects static functions', () => {
@@ -125,7 +232,7 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual(
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
methodModifiersDoc(['static']),
);
});
@@ -137,7 +244,7 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual(
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
methodModifiersDoc(['generator']),
);
});
@@ -149,7 +256,7 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual(
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
methodModifiersDoc(['async']),
);
});
@@ -161,7 +268,7 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual(
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
methodModifiersDoc(['static', 'async']),
);
});
@@ -185,7 +292,9 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual(methodReturnDoc(null));
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
+ methodReturnDoc(null),
+ );
});
it('extracts flow types', () => {
@@ -195,7 +304,36 @@ describe('getMethodDocumentation', () => {
}
`);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toEqual(
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
+ methodReturnDoc({
+ type: { name: 'number' },
+ }),
+ );
+ });
+
+ it('extracts flow types on function assignment', () => {
+ const def = statement(`
+ class Foo {
+ foo = (): number => {}
+ }
+ `);
+ const method = def.get('body', 'body', 0);
+ expect(getMethodDocumentation(method, noopImporter)).toEqual(
+ methodReturnDoc({
+ type: { name: 'number' },
+ }),
+ );
+ });
+
+ it('resolves flow types on imported functions', () => {
+ const def = statement(`
+ class Foo {
+ foo = baz
+ }
+ import baz from 'baz';
+ `);
+ const method = def.get('body', 'body', 0);
+ expect(getMethodDocumentation(method, mockImporter)).toEqual(
methodReturnDoc({
type: { name: 'number' },
}),
@@ -214,7 +352,7 @@ describe('getMethodDocumentation', () => {
{ parserOptions: { plugins: ['typescript'] } },
);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toMatchSnapshot();
+ expect(getMethodDocumentation(method, noopImporter)).toMatchSnapshot();
});
it.skip('ignores private methods', () => {
@@ -227,7 +365,7 @@ describe('getMethodDocumentation', () => {
{ parserOptions: { plugins: ['classPrivateMethods'] } },
);
const method = def.get('body', 'body', 0);
- expect(getMethodDocumentation(method)).toMatchSnapshot();
+ expect(getMethodDocumentation(method, noopImporter)).toMatchSnapshot();
});
});
});
diff --git a/src/utils/__tests__/getPropType-test.js b/src/utils/__tests__/getPropType-test.js
index 217c1ebadeb..b90ab657e45 100644
--- a/src/utils/__tests__/getPropType-test.js
+++ b/src/utils/__tests__/getPropType-test.js
@@ -6,7 +6,12 @@
*
*/
-import { expression, statement } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import getPropType from '../getPropType';
describe('getPropType', () => {
@@ -26,26 +31,34 @@ describe('getPropType', () => {
];
simplePropTypes.forEach(type =>
- expect(getPropType(expression('React.PropTypes.' + type))).toEqual({
+ expect(
+ getPropType(expression('React.PropTypes.' + type), noopImporter),
+ ).toEqual({
name: type,
}),
);
// It doesn't actually matter what the MemberExpression is
simplePropTypes.forEach(type =>
- expect(getPropType(expression('Foo.' + type + '.bar'))).toEqual({
+ expect(
+ getPropType(expression('Foo.' + type + '.bar'), noopImporter),
+ ).toEqual({
name: type,
}),
);
// Doesn't even have to be a MemberExpression
simplePropTypes.forEach(type =>
- expect(getPropType(expression(type))).toEqual({ name: type }),
+ expect(getPropType(expression(type), noopImporter)).toEqual({
+ name: type,
+ }),
);
});
it('detects complex prop types', () => {
- expect(getPropType(expression('oneOf(["foo", "bar"])'))).toEqual({
+ expect(
+ getPropType(expression('oneOf(["foo", "bar"])'), noopImporter),
+ ).toEqual({
name: 'enum',
value: [
{ value: '"foo"', computed: false },
@@ -54,7 +67,9 @@ describe('getPropType', () => {
});
// line comments are ignored
- expect(getPropType(expression('oneOf(["foo", // baz\n"bar"])'))).toEqual({
+ expect(
+ getPropType(expression('oneOf(["foo", // baz\n"bar"])'), noopImporter),
+ ).toEqual({
name: 'enum',
value: [
{ value: '"foo"', computed: false },
@@ -62,33 +77,37 @@ describe('getPropType', () => {
],
});
- expect(getPropType(expression('oneOfType([number, bool])'))).toEqual({
+ expect(
+ getPropType(expression('oneOfType([number, bool])'), noopImporter),
+ ).toEqual({
name: 'union',
value: [{ name: 'number' }, { name: 'bool' }],
});
// custom type
- expect(getPropType(expression('oneOfType([foo])'))).toEqual({
+ expect(getPropType(expression('oneOfType([foo])'), noopImporter)).toEqual({
name: 'union',
value: [{ name: 'custom', raw: 'foo' }],
});
- expect(getPropType(expression('instanceOf(Foo)'))).toEqual({
+ expect(getPropType(expression('instanceOf(Foo)'), noopImporter)).toEqual({
name: 'instanceOf',
value: 'Foo',
});
- expect(getPropType(expression('arrayOf(string)'))).toEqual({
+ expect(getPropType(expression('arrayOf(string)'), noopImporter)).toEqual({
name: 'arrayOf',
value: { name: 'string' },
});
- expect(getPropType(expression('objectOf(string)'))).toEqual({
+ expect(getPropType(expression('objectOf(string)'), noopImporter)).toEqual({
name: 'objectOf',
value: { name: 'string' },
});
- expect(getPropType(expression('shape({foo: string, bar: bool})'))).toEqual({
+ expect(
+ getPropType(expression('shape({foo: string, bar: bool})'), noopImporter),
+ ).toEqual({
name: 'shape',
value: {
foo: {
@@ -102,7 +121,9 @@ describe('getPropType', () => {
},
});
- expect(getPropType(expression('exact({foo: string, bar: bool})'))).toEqual({
+ expect(
+ getPropType(expression('exact({foo: string, bar: bool})'), noopImporter),
+ ).toEqual({
name: 'exact',
value: {
foo: {
@@ -117,7 +138,7 @@ describe('getPropType', () => {
});
// custom
- expect(getPropType(expression('shape({foo: xyz})'))).toEqual({
+ expect(getPropType(expression('shape({foo: xyz})'), noopImporter)).toEqual({
name: 'shape',
value: {
foo: {
@@ -129,7 +150,7 @@ describe('getPropType', () => {
});
// custom
- expect(getPropType(expression('exact({foo: xyz})'))).toEqual({
+ expect(getPropType(expression('exact({foo: xyz})'), noopImporter)).toEqual({
name: 'exact',
value: {
foo: {
@@ -141,14 +162,18 @@ describe('getPropType', () => {
});
// computed
- expect(getPropType(expression('shape(Child.propTypes)'))).toEqual({
+ expect(
+ getPropType(expression('shape(Child.propTypes)'), noopImporter),
+ ).toEqual({
name: 'shape',
value: 'Child.propTypes',
computed: true,
});
// computed
- expect(getPropType(expression('exact(Child.propTypes)'))).toEqual({
+ expect(
+ getPropType(expression('exact(Child.propTypes)'), noopImporter),
+ ).toEqual({
name: 'exact',
value: 'Child.propTypes',
computed: true,
@@ -156,13 +181,58 @@ describe('getPropType', () => {
});
describe('resolve identifier to their values', () => {
+ const mockImporter = makeMockImporter({
+ shape: statement(`
+ export default {bar: PropTypes.string};
+ `).get('declaration'),
+
+ types: statement(`
+ export default ["foo", "bar"];
+ `).get('declaration'),
+
+ foo: statement(`
+ export default "foo";
+ `).get('declaration'),
+
+ bar: statement(`
+ export default "bar";
+ `).get('declaration'),
+
+ obj: statement(`
+ export default { FOO: "foo", BAR: "bar" };
+ `).get('declaration'),
+
+ arr: statement(`
+ export default ["foo", "bar"];
+ `).get('declaration'),
+
+ keys: statement(`
+ export default Object.keys(obj);
+ import obj from 'obj';
+ `).get('declaration'),
+
+ values: statement(`
+ export default Object.values(obj);
+ import obj from 'obj';
+ `).get('declaration'),
+ });
+
it('resolves variables to their values', () => {
const propTypeExpression = statement(`
PropTypes.shape(shape);
var shape = {bar: PropTypes.string};
`).get('expression');
- expect(getPropType(propTypeExpression)).toMatchSnapshot();
+ expect(getPropType(propTypeExpression, noopImporter)).toMatchSnapshot();
+ });
+
+ it('resolves imported variables to their values', () => {
+ const propTypeExpression = statement(`
+ PropTypes.shape(shape);
+ import shape from 'shape';
+ `).get('expression');
+
+ expect(getPropType(propTypeExpression, mockImporter)).toMatchSnapshot();
});
it('resolves simple identifier to their initialization value', () => {
@@ -171,7 +241,16 @@ describe('getPropType', () => {
var TYPES = ["foo", "bar"];
`).get('expression');
- expect(getPropType(propTypeIdentifier)).toMatchSnapshot();
+ expect(getPropType(propTypeIdentifier, noopImporter)).toMatchSnapshot();
+ });
+
+ it('resolves importer identifier to initialization value', () => {
+ const propTypeIdentifier = statement(`
+ PropTypes.oneOf(TYPES);
+ import TYPES from 'types';
+ `).get('expression');
+
+ expect(getPropType(propTypeIdentifier, mockImporter)).toMatchSnapshot();
});
it('resolves simple identifier to their initialization value in array', () => {
@@ -181,7 +260,21 @@ describe('getPropType', () => {
var BAR = "bar";
`).get('expression');
- expect(getPropType(identifierInsideArray)).toMatchSnapshot();
+ expect(
+ getPropType(identifierInsideArray, noopImporter),
+ ).toMatchSnapshot();
+ });
+
+ it('resolves imported identifier to their initialization value in array', () => {
+ const identifierInsideArray = statement(`
+ PropTypes.oneOf([FOO, BAR]);
+ import FOO from 'foo';
+ import BAR from 'bar';
+ `).get('expression');
+
+ expect(
+ getPropType(identifierInsideArray, mockImporter),
+ ).toMatchSnapshot();
});
it('resolves memberExpressions', () => {
@@ -190,7 +283,16 @@ describe('getPropType', () => {
var TYPES = { FOO: "foo", BAR: "bar" };
`).get('expression');
- expect(getPropType(propTypeExpression)).toMatchSnapshot();
+ expect(getPropType(propTypeExpression, noopImporter)).toMatchSnapshot();
+ });
+
+ it('resolves memberExpressions from imported objects', () => {
+ const propTypeExpression = statement(`
+ PropTypes.oneOf([TYPES.FOO, TYPES.BAR]);
+ import TYPES from 'obj';
+ `).get('expression');
+
+ expect(getPropType(propTypeExpression, mockImporter)).toMatchSnapshot();
});
it('correctly resolves SpreadElements in arrays', () => {
@@ -199,7 +301,16 @@ describe('getPropType', () => {
var TYPES = ["foo", "bar"];
`).get('expression');
- expect(getPropType(propTypeExpression)).toMatchSnapshot();
+ expect(getPropType(propTypeExpression, noopImporter)).toMatchSnapshot();
+ });
+
+ it('correctly resolves SpreadElements in arrays from imported values', () => {
+ const propTypeExpression = statement(`
+ PropTypes.oneOf([...TYPES]);
+ import TYPES from 'arr';
+ `).get('expression');
+
+ expect(getPropType(propTypeExpression, mockImporter)).toMatchSnapshot();
});
it('correctly resolves nested SpreadElements in arrays', () => {
@@ -209,7 +320,7 @@ describe('getPropType', () => {
var TYPES2 = ["bar"];
`).get('expression');
- expect(getPropType(propTypeExpression)).toMatchSnapshot();
+ expect(getPropType(propTypeExpression, noopImporter)).toMatchSnapshot();
});
it('does resolve object keys values', () => {
@@ -218,7 +329,16 @@ describe('getPropType', () => {
var TYPES = { FOO: "foo", BAR: "bar" };
`).get('expression');
- expect(getPropType(propTypeExpression)).toMatchSnapshot();
+ expect(getPropType(propTypeExpression, noopImporter)).toMatchSnapshot();
+ });
+
+ it('resolves values from imported Object.keys call', () => {
+ const propTypeExpression = statement(`
+ PropTypes.oneOf(keys);
+ import keys from 'keys';
+ `).get('expression');
+
+ expect(getPropType(propTypeExpression, mockImporter)).toMatchSnapshot();
});
it('does resolve object values', () => {
@@ -227,25 +347,36 @@ describe('getPropType', () => {
var TYPES = { FOO: "foo", BAR: "bar" };
`).get('expression');
- expect(getPropType(propTypeExpression)).toMatchSnapshot();
+ expect(getPropType(propTypeExpression, noopImporter)).toMatchSnapshot();
});
- it('does not resolve external values', () => {
+ it('resolves values from imported Object.values call', () => {
+ const propTypeExpression = statement(`
+ PropTypes.oneOf(values);
+ import values from 'values';
+ `).get('expression');
+
+ expect(getPropType(propTypeExpression, mockImporter)).toMatchSnapshot();
+ });
+
+ it('does not resolve external values without proper importer', () => {
const propTypeExpression = statement(`
PropTypes.oneOf(TYPES);
import { TYPES } from './foo';
`).get('expression');
- expect(getPropType(propTypeExpression)).toMatchSnapshot();
+ expect(getPropType(propTypeExpression, noopImporter)).toMatchSnapshot();
});
});
it('detects custom validation functions for function', () => {
- expect(getPropType(expression('(function() {})'))).toMatchSnapshot();
+ expect(
+ getPropType(expression('(function() {})'), noopImporter),
+ ).toMatchSnapshot();
});
it('detects custom validation functions for arrow function', () => {
- expect(getPropType(expression('() => {}'))).toMatchSnapshot();
+ expect(getPropType(expression('() => {}'), noopImporter)).toMatchSnapshot();
});
it('detects descriptions on nested types in arrayOf', () => {
@@ -257,6 +388,7 @@ describe('getPropType', () => {
*/
string
)`),
+ noopImporter,
),
).toMatchSnapshot();
});
@@ -270,6 +402,7 @@ describe('getPropType', () => {
*/
string
)`),
+ noopImporter,
),
).toMatchSnapshot();
});
@@ -287,6 +420,7 @@ describe('getPropType', () => {
*/
bar: bool
})`),
+ noopImporter,
),
).toMatchSnapshot();
});
@@ -298,6 +432,7 @@ describe('getPropType', () => {
foo: string.isRequired,
bar: bool
})`),
+ noopImporter,
),
).toMatchSnapshot();
});
@@ -315,6 +450,7 @@ describe('getPropType', () => {
*/
bar: bool
})`),
+ noopImporter,
),
).toMatchSnapshot();
});
@@ -326,6 +462,7 @@ describe('getPropType', () => {
foo: string.isRequired,
bar: bool
})`),
+ noopImporter,
),
).toMatchSnapshot();
});
@@ -337,6 +474,7 @@ describe('getPropType', () => {
[foo]: string.isRequired,
bar: bool
})`),
+ noopImporter,
),
).toMatchSnapshot();
});
@@ -348,6 +486,7 @@ describe('getPropType', () => {
[() => {}]: string.isRequired,
bar: bool
})`),
+ noopImporter,
),
).toMatchSnapshot();
});
diff --git a/src/utils/__tests__/getPropertyName-test.js b/src/utils/__tests__/getPropertyName-test.js
index 1b4523cb7a2..c89b94bfc22 100644
--- a/src/utils/__tests__/getPropertyName-test.js
+++ b/src/utils/__tests__/getPropertyName-test.js
@@ -8,7 +8,13 @@
*
*/
-import { parse, expression } from '../../../tests/utils';
+import {
+ parse,
+ statement,
+ expression,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import getPropertyName from '../getPropertyName';
describe('getPropertyName', () => {
@@ -17,53 +23,63 @@ describe('getPropertyName', () => {
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default "name";
+ `).get('declaration'),
+
+ bar: statement(`
+ export default { baz: "name" };
+ `).get('declaration'),
+ });
+
it('returns the name for a normal property', () => {
const def = expression('{ foo: 1 }');
const param = def.get('properties', 0);
- expect(getPropertyName(param)).toBe('foo');
+ expect(getPropertyName(param, noopImporter)).toBe('foo');
});
it('returns the name of a object type spread property', () => {
const def = expression('(a: { ...foo })');
const param = def.get('typeAnnotation', 'typeAnnotation', 'properties', 0);
- expect(getPropertyName(param)).toBe('foo');
+ expect(getPropertyName(param, noopImporter)).toBe('foo');
});
it('creates name for computed properties', () => {
const def = expression('{ [foo]: 21 }');
const param = def.get('properties', 0);
- expect(getPropertyName(param)).toBe('@computed#foo');
+ expect(getPropertyName(param, noopImporter)).toBe('@computed#foo');
});
it('creates name for computed properties from string', () => {
const def = expression('{ ["foo"]: 21 }');
const param = def.get('properties', 0);
- expect(getPropertyName(param)).toBe('foo');
+ expect(getPropertyName(param, noopImporter)).toBe('foo');
});
it('creates name for computed properties from int', () => {
const def = expression('{ [31]: 21 }');
const param = def.get('properties', 0);
- expect(getPropertyName(param)).toBe('31');
+ expect(getPropertyName(param, noopImporter)).toBe('31');
});
it('returns null for computed properties from regex', () => {
const def = expression('{ [/31/]: 21 }');
const param = def.get('properties', 0);
- expect(getPropertyName(param)).toBe(null);
+ expect(getPropertyName(param, noopImporter)).toBe(null);
});
it('returns null for to complex computed properties', () => {
const def = expression('{ [() => {}]: 21 }');
const param = def.get('properties', 0);
- expect(getPropertyName(param)).toBe(null);
+ expect(getPropertyName(param, noopImporter)).toBe(null);
});
it('resolves simple variables', () => {
@@ -74,7 +90,18 @@ describe('getPropertyName', () => {
`);
const param = def.get('properties', 0);
- expect(getPropertyName(param)).toBe('name');
+ expect(getPropertyName(param, noopImporter)).toBe('name');
+ });
+
+ it('resolves imported variables', () => {
+ const def = parsePath(`
+ import foo from 'foo';
+
+ ({ [foo]: 21 });
+ `);
+ const param = def.get('properties', 0);
+
+ expect(getPropertyName(param, mockImporter)).toBe('name');
});
it('resolves simple member expressions', () => {
@@ -85,6 +112,17 @@ describe('getPropertyName', () => {
`);
const param = def.get('properties', 0);
- expect(getPropertyName(param)).toBe('name');
+ expect(getPropertyName(param, noopImporter)).toBe('name');
+ });
+
+ it('resolves imported member expressions', () => {
+ const def = parsePath(`
+ import bar from 'bar';
+
+ ({ [bar.baz]: 21 });
+ `);
+ const param = def.get('properties', 0);
+
+ expect(getPropertyName(param, mockImporter)).toBe('name');
});
});
diff --git a/src/utils/__tests__/getPropertyValuePath-test.js b/src/utils/__tests__/getPropertyValuePath-test.js
index b98afea40a6..fe03b5f2476 100644
--- a/src/utils/__tests__/getPropertyValuePath-test.js
+++ b/src/utils/__tests__/getPropertyValuePath-test.js
@@ -6,23 +6,55 @@
*
*/
-import { statement } from '../../../tests/utils';
+import {
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import getPropertyValuePath from '../getPropertyValuePath';
describe('getPropertyValuePath', () => {
+ const mockImporter = makeMockImporter({
+ bar: statement(`
+ export default 'bar';
+ `).get('declaration'),
+ });
+
it('returns the value path if the property exists', () => {
const objectExpressionPath = statement('({foo: 21, bar: 42})').get(
'expression',
);
- expect(getPropertyValuePath(objectExpressionPath, 'bar')).toBe(
- objectExpressionPath.get('properties', 1).get('value'),
- );
+ expect(
+ getPropertyValuePath(objectExpressionPath, 'bar', noopImporter),
+ ).toBe(objectExpressionPath.get('properties', 1).get('value'));
+ });
+
+ it('returns the value path for a computed property in scope', () => {
+ const objectExpressionPath = statement(`
+ ({foo: 21, [a]: 42});
+ var a = 'bar';
+ `).get('expression');
+ expect(
+ getPropertyValuePath(objectExpressionPath, 'bar', noopImporter),
+ ).toBe(objectExpressionPath.get('properties', 1).get('value'));
});
it('returns undefined if the property does not exist', () => {
const objectExpressionPath = statement('({foo: 21, bar: 42})').get(
'expression',
);
- expect(getPropertyValuePath(objectExpressionPath, 'baz')).toBeUndefined();
+ expect(
+ getPropertyValuePath(objectExpressionPath, 'baz', noopImporter),
+ ).toBeUndefined();
+ });
+
+ it('returns the value path for a computed property that was imported', () => {
+ const objectExpressionPath = statement(`
+ ({foo: 21, [a]: 42});
+ import a from 'bar';
+ `).get('expression');
+ expect(
+ getPropertyValuePath(objectExpressionPath, 'bar', mockImporter),
+ ).toBe(objectExpressionPath.get('properties', 1).get('value'));
});
});
diff --git a/src/utils/__tests__/getTSType-test.js b/src/utils/__tests__/getTSType-test.js
index bc8a8914814..019ee5577c3 100644
--- a/src/utils/__tests__/getTSType-test.js
+++ b/src/utils/__tests__/getTSType-test.js
@@ -6,7 +6,11 @@
*
*/
-import { statement as stmt } from '../../../tests/utils';
+import {
+ statement as stmt,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import getTSType from '../getTSType';
function statement(code) {
@@ -16,6 +20,50 @@ function statement(code) {
});
}
+const mockImporter = makeMockImporter({
+ abc: statement(`
+ export type abc = number;
+ `).get('declaration'),
+
+ def: statement(`
+ export type def = boolean;
+ `).get('declaration'),
+
+ xyz: statement(`
+ export type xyz = string;
+ `).get('declaration'),
+
+ barbaz: statement(`
+ export type barbaz = "bar" | "baz";
+ `).get('declaration'),
+
+ recTup: statement(`
+ export type recTup = [abc, xyz];
+ import { abc } from 'abc';
+ import { xyz } from 'xyz';
+ `).get('declaration'),
+
+ obj: statement(`
+ export type A = { x: string };
+ `).get('declaration'),
+
+ MyType: statement(`
+ export type MyType = { a: number, b: xyz };
+ import { xyz } from 'xyz';
+ `).get('declaration'),
+
+ MyGenericType: statement(`
+ export type MyGenericType = { a: T, b: Array };
+ `).get('declaration'),
+
+ fruits: statement(`
+ export default {
+ 'apple': '🍎',
+ 'banana': '🍌',
+ };
+ `).get('declaration'),
+});
+
describe('getTSType', () => {
it('detects simple types', () => {
const simplePropTypes = [
@@ -42,7 +90,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({ name: type });
+ expect(getTSType(typePath, null, noopImporter)).toEqual({ name: type });
});
});
@@ -55,7 +103,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'literal',
value: `${value}`,
});
@@ -68,7 +116,19 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({ name: 'xyz' });
+ expect(getTSType(typePath, null, noopImporter)).toEqual({ name: 'xyz' });
+ });
+
+ it('resolves external type', () => {
+ const typePath = statement(`
+ let x: xyz;
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({ name: 'string' });
});
it('detects array type shorthand', () => {
@@ -77,7 +137,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'Array',
elements: [{ name: 'number' }],
raw: 'number[]',
@@ -90,7 +150,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'Array',
elements: [{ name: 'number' }],
raw: 'Array',
@@ -103,46 +163,122 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'Array',
elements: [{ name: 'number' }, { name: 'xyz' }],
raw: 'Array',
});
});
+ it('resolves imported types used for arrays', () => {
+ let typePath = statement(`
+ let x: xyz[];
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'Array',
+ elements: [{ name: 'string' }],
+ raw: 'xyz[]',
+ });
+
+ typePath = statement(`
+ let x: Array;
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'Array',
+ elements: [{ name: 'string' }],
+ raw: 'Array',
+ });
+
+ typePath = statement(`
+ let x: Array;
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'Array',
+ elements: [{ name: 'number' }, { name: 'string' }],
+ raw: 'Array',
+ });
+ });
+
it('detects class type', () => {
const typePath = statement('let x: Class;')
.get('declarations', 0)
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'Class',
elements: [{ name: 'Boolean' }],
raw: 'Class',
});
});
+ it('resolves imported subtype for class type', () => {
+ const typePath = statement(`
+ let x: Class;
+ import { xyz } from 'xyz'
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'Class',
+ elements: [{ name: 'string' }],
+ raw: 'Class',
+ });
+ });
+
it('detects function type with subtype', () => {
const typePath = statement('let x: Function;')
.get('declarations', 0)
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'Function',
elements: [{ name: 'xyz' }],
raw: 'Function',
});
});
+ it('resolves imported subtype for function type', () => {
+ const typePath = statement(`
+ let x: Function;
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'Function',
+ elements: [{ name: 'string' }],
+ raw: 'Function',
+ });
+ });
+
it('detects object types', () => {
const typePath = statement('let x: { a: string, b?: xyz };')
.get('declarations', 0)
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -155,13 +291,35 @@ describe('getTSType', () => {
});
});
+ it('resolves imported types for object property types', () => {
+ const typePath = statement(`
+ let x: { a: number, b?: xyz };
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ signature: {
+ properties: [
+ { key: 'a', value: { name: 'number', required: true } },
+ { key: 'b', value: { name: 'string', required: false } },
+ ],
+ },
+ raw: '{ a: number, b?: xyz }',
+ });
+ });
+
it('detects union type', () => {
const typePath = statement('let x: string | xyz | "foo" | void;')
.get('declarations', 0)
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'union',
elements: [
{ name: 'string' },
@@ -173,13 +331,41 @@ describe('getTSType', () => {
});
});
+ it('resolves imported types within union type', () => {
+ const typePath = statement(`
+ let x: string | barbaz | "foo" | void;
+ import { barbaz } from 'barbaz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'union',
+ elements: [
+ { name: 'string' },
+ {
+ name: 'union',
+ elements: [
+ { name: 'literal', value: '"bar"' },
+ { name: 'literal', value: '"baz"' },
+ ],
+ raw: '"bar" | "baz"',
+ },
+ { name: 'literal', value: '"foo"' },
+ { name: 'void' },
+ ],
+ raw: 'string | barbaz | "foo" | void',
+ });
+ });
+
it('detects intersection type', () => {
const typePath = statement('let x: string & xyz & "foo" & void;')
.get('declarations', 0)
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'intersection',
elements: [
{ name: 'string' },
@@ -191,6 +377,34 @@ describe('getTSType', () => {
});
});
+ it('resolves imported types within intersection type', () => {
+ const typePath = statement(`
+ let x: string & barbaz & "foo" & void;
+ import { barbaz } from 'barbaz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'intersection',
+ elements: [
+ { name: 'string' },
+ {
+ name: 'union',
+ elements: [
+ { name: 'literal', value: '"bar"' },
+ { name: 'literal', value: '"baz"' },
+ ],
+ raw: '"bar" | "baz"',
+ },
+ { name: 'literal', value: '"foo"' },
+ { name: 'void' },
+ ],
+ raw: 'string & barbaz & "foo" & void',
+ });
+ });
+
it('detects function signature type', () => {
const typePath = statement(
'let x: (p1: number, p2: string, ...rest: Array) => boolean;',
@@ -199,7 +413,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'function',
signature: {
@@ -228,7 +442,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'function',
signature: {
@@ -248,7 +462,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -269,6 +483,89 @@ describe('getTSType', () => {
});
});
+ it('resolves function signature types with imported types', () => {
+ let typePath = statement(`
+ let x: (p1: abc, p2: xyz, ...rest: Array) => def;
+ import { abc } from 'abc';
+ import { def } from 'def';
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'function',
+ signature: {
+ arguments: [
+ { name: 'p1', type: { name: 'number' } },
+ { name: 'p2', type: { name: 'string' } },
+ {
+ name: 'rest',
+ rest: true,
+ type: {
+ name: 'Array',
+ elements: [{ name: 'string' }],
+ raw: 'Array',
+ },
+ },
+ ],
+ return: { name: 'boolean' },
+ },
+ raw: '(p1: abc, p2: xyz, ...rest: Array) => def',
+ });
+
+ typePath = statement(`
+ let x: (this: xyz, p1: number) => boolean;
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'function',
+ signature: {
+ arguments: [{ name: 'p1', type: { name: 'number' } }],
+ this: { name: 'string' },
+ return: { name: 'boolean' },
+ },
+ raw: '(this: xyz, p1: number) => boolean',
+ });
+
+ typePath = statement(`
+ let x: { (str: xyz): abc, token: def };
+ import { abc } from 'abc';
+ import { def } from 'def';
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ signature: {
+ constructor: {
+ name: 'signature',
+ type: 'function',
+ signature: {
+ arguments: [{ name: 'str', type: { name: 'string' } }],
+ return: { name: 'number' },
+ },
+ raw: '(str: xyz): abc,',
+ },
+ properties: [
+ { key: 'token', value: { name: 'boolean', required: true } },
+ ],
+ },
+ raw: '{ (str: xyz): abc, token: def }',
+ });
+ });
+
it('detects map signature', () => {
const typePath = statement(
'let x: { [key: string]: number, [key: "xl"]: string, token: "a" | "b" };',
@@ -277,7 +574,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -308,13 +605,55 @@ describe('getTSType', () => {
});
});
+ it('resolves imported types in map signature', () => {
+ const typePath = statement(`
+ let x: { [key: xyz]: abc, [key: "xl"]: xyz, token: barbaz };
+ import { abc } from 'abc';
+ import { xyz } from 'xyz';
+ import { barbaz } from 'barbaz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ signature: {
+ properties: [
+ {
+ key: { name: 'string', required: true },
+ value: { name: 'number', required: true },
+ },
+ {
+ key: { name: 'literal', value: '"xl"' },
+ value: { name: 'string', required: true },
+ },
+ {
+ key: 'token',
+ value: {
+ name: 'union',
+ required: true,
+ raw: '"bar" | "baz"',
+ elements: [
+ { name: 'literal', value: '"bar"' },
+ { name: 'literal', value: '"baz"' },
+ ],
+ },
+ },
+ ],
+ },
+ raw: '{ [key: xyz]: abc, [key: "xl"]: xyz, token: barbaz }',
+ });
+ });
+
it('detects tuple signature', () => {
const typePath = statement('let x: [string, number];')
.get('declarations', 0)
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'tuple',
elements: [{ name: 'string' }, { name: 'number' }],
raw: '[string, number]',
@@ -327,7 +666,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'union',
elements: [
{
@@ -345,6 +684,50 @@ describe('getTSType', () => {
});
});
+ it('resolves imported types in tuple signatures', () => {
+ let typePath = statement(`
+ let x: [xyz, abc];
+ import { abc } from 'abc';
+ import { xyz } from 'xyz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'tuple',
+ elements: [{ name: 'string' }, { name: 'number' }],
+ raw: '[xyz, abc]',
+ });
+
+ typePath = statement(`
+ let x: [xyz, abc] | recTup;
+ import { abc } from 'abc';
+ import { xyz } from 'xyz';
+ import { recTup } from 'recTup';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'union',
+ elements: [
+ {
+ name: 'tuple',
+ elements: [{ name: 'string' }, { name: 'number' }],
+ raw: '[xyz, abc]',
+ },
+ {
+ name: 'tuple',
+ elements: [{ name: 'number' }, { name: 'string' }],
+ raw: '[abc, xyz]',
+ },
+ ],
+ raw: '[xyz, abc] | recTup',
+ });
+ });
+
it('detects indexed access', () => {
const typePath = statement(`
var x: A["x"] = 2;
@@ -355,7 +738,7 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'A["x"]',
raw: 'A["x"]',
});
@@ -371,7 +754,22 @@ describe('getTSType', () => {
.get('id')
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
+ name: 'string',
+ raw: 'A["x"]',
+ });
+ });
+
+ it('can resolve indexed access to imported type', () => {
+ const typePath = statement(`
+ var x: A["x"] = 2;
+ import { A } from 'obj';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
name: 'string',
raw: 'A["x"]',
});
@@ -388,7 +786,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({ name: 'string' });
+ expect(getTSType(typePath, null, noopImporter)).toEqual({ name: 'string' });
});
it('handles typeof types', () => {
@@ -402,7 +800,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -415,6 +813,29 @@ describe('getTSType', () => {
});
});
+ it('resolves typeof of imported types', () => {
+ const typePath = statement(`
+ var x: typeof MyType = {};
+ import { MyType } from 'MyType';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ signature: {
+ properties: [
+ { key: 'a', value: { name: 'number', required: true } },
+ { key: 'b', value: { name: 'string', required: true } },
+ ],
+ },
+ raw: '{ a: number, b: xyz }',
+ });
+ });
+
it('handles qualified type identifiers', () => {
const typePath = statement(`
var x: MyType.x = {};
@@ -426,7 +847,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'MyType.x',
});
});
@@ -442,7 +863,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'MyType.x',
raw: 'MyType.x',
elements: [
@@ -464,7 +885,44 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ raw: '{ a: T, b: Array }',
+ signature: {
+ properties: [
+ {
+ key: 'a',
+ value: {
+ name: 'string',
+ required: true,
+ },
+ },
+ {
+ key: 'b',
+ value: {
+ name: 'Array',
+ raw: 'Array',
+ required: true,
+ elements: [{ name: 'string' }],
+ },
+ },
+ ],
+ },
+ });
+ });
+
+ it('resolves imported types that need subtypes', () => {
+ const typePath = statement(`
+ var x: MyGenericType = {};
+ import { MyGenericType } from 'MyGenericType';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
name: 'signature',
type: 'object',
raw: '{ a: T, b: Array }',
@@ -500,7 +958,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
raw: "{ [key in 'x' | 'y']: boolean}",
@@ -531,6 +989,47 @@ describe('getTSType', () => {
});
});
+ it('resolves imported types applied to mapped types', () => {
+ const typePath = statement(`
+ var x: { [key in barbaz]: boolean};
+ import { barbaz } from 'barbaz';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
+ name: 'signature',
+ type: 'object',
+ raw: '{ [key in barbaz]: boolean}',
+ signature: {
+ properties: [
+ {
+ key: {
+ elements: [
+ {
+ name: 'literal',
+ value: '"bar"',
+ },
+ {
+ name: 'literal',
+ value: '"baz"',
+ },
+ ],
+ name: 'union',
+ raw: '"bar" | "baz"',
+ required: true,
+ },
+ value: {
+ name: 'boolean',
+ },
+ },
+ ],
+ },
+ });
+ });
+
describe('React types', () => {
function test(type, expected) {
const typePath = statement(`
@@ -543,7 +1042,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
...expected,
name: type.replace('.', '').replace(/<.+>/, ''),
raw: type,
@@ -596,7 +1095,27 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
+ name: 'union',
+ elements: [
+ { name: 'literal', value: "'apple'" },
+ { name: 'literal', value: "'banana'" },
+ ],
+ raw: 'keyof typeof CONTENTS',
+ });
+ });
+
+ it('resolves keyof with imported types', () => {
+ const typePath = statement(`
+ var x: keyof typeof CONTENTS = 2;
+ import CONTENTS from 'fruits';
+ `)
+ .get('declarations', 0)
+ .get('id')
+ .get('typeAnnotation')
+ .get('typeAnnotation');
+
+ expect(getTSType(typePath, null, mockImporter)).toEqual({
name: 'union',
elements: [
{ name: 'literal', value: "'apple'" },
@@ -615,7 +1134,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'union',
elements: [
{ name: 'literal', value: 'apple' },
@@ -635,7 +1154,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -679,7 +1198,7 @@ describe('getTSType', () => {
.get('body', 0)
.get('typeAnnotation');
- getTSType(typePath);
+ getTSType(typePath, null, noopImporter);
});
it('handles self-referencing type cycles', () => {
@@ -692,7 +1211,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
@@ -717,7 +1236,7 @@ describe('getTSType', () => {
.get('typeAnnotation')
.get('typeAnnotation');
- expect(getTSType(typePath)).toEqual({
+ expect(getTSType(typePath, null, noopImporter)).toEqual({
name: 'signature',
type: 'object',
signature: {
diff --git a/src/utils/__tests__/isExportsOrModuleAssignment-test.js b/src/utils/__tests__/isExportsOrModuleAssignment-test.js
index bea36371742..a162059b0d6 100644
--- a/src/utils/__tests__/isExportsOrModuleAssignment-test.js
+++ b/src/utils/__tests__/isExportsOrModuleAssignment-test.js
@@ -6,26 +6,32 @@
*
*/
-import { statement } from '../../../tests/utils';
+import { statement, noopImporter } from '../../../tests/utils';
import isExportsOrModuleAssignment from '../isExportsOrModuleAssignment';
describe('isExportsOrModuleAssignment', () => {
it('detects "module.exports = ...;"', () => {
expect(
- isExportsOrModuleAssignment(statement('module.exports = foo;')),
+ isExportsOrModuleAssignment(
+ statement('module.exports = foo;'),
+ noopImporter,
+ ),
).toBe(true);
});
it('detects "exports.foo = ..."', () => {
- expect(isExportsOrModuleAssignment(statement('exports.foo = foo;'))).toBe(
- true,
- );
+ expect(
+ isExportsOrModuleAssignment(
+ statement('exports.foo = foo;'),
+ noopImporter,
+ ),
+ ).toBe(true);
});
it('does not accept "exports = foo;"', () => {
// That doesn't actually export anything
- expect(isExportsOrModuleAssignment(statement('exports = foo;'))).toBe(
- false,
- );
+ expect(
+ isExportsOrModuleAssignment(statement('exports = foo;'), noopImporter),
+ ).toBe(false);
});
});
diff --git a/src/utils/__tests__/isReactCloneElementCall-test.js b/src/utils/__tests__/isReactCloneElementCall-test.js
index d2596453cc8..d656f719fe5 100644
--- a/src/utils/__tests__/isReactCloneElementCall-test.js
+++ b/src/utils/__tests__/isReactCloneElementCall-test.js
@@ -6,7 +6,12 @@
*
*/
-import { parse } from '../../../tests/utils';
+import {
+ parse,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import isReactCloneElementCall from '../isReactCloneElementCall';
describe('isReactCloneElementCall', () => {
@@ -15,13 +20,20 @@ describe('isReactCloneElementCall', () => {
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default React.cloneElement;
+ import React from 'react';
+ `).get('declaration'),
+ });
+
describe('built in React.createClass', () => {
it('accepts cloneElement called on React', () => {
const def = parsePath(`
var React = require("React");
React.cloneElement({});
`);
- expect(isReactCloneElementCall(def)).toBe(true);
+ expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
});
it('accepts cloneElement called on aliased React', () => {
@@ -29,7 +41,7 @@ describe('isReactCloneElementCall', () => {
var other = require("React");
other.cloneElement({});
`);
- expect(isReactCloneElementCall(def)).toBe(true);
+ expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
});
it('ignores other React calls', () => {
@@ -37,7 +49,7 @@ describe('isReactCloneElementCall', () => {
var React = require("React");
React.isValidElement({});
`);
- expect(isReactCloneElementCall(def)).toBe(false);
+ expect(isReactCloneElementCall(def, noopImporter)).toBe(false);
});
it('ignores non React calls to cloneElement', () => {
@@ -45,7 +57,7 @@ describe('isReactCloneElementCall', () => {
var React = require("bob");
React.cloneElement({});
`);
- expect(isReactCloneElementCall(def)).toBe(false);
+ expect(isReactCloneElementCall(def, noopImporter)).toBe(false);
});
it('accepts cloneElement called on destructed value', () => {
@@ -53,7 +65,7 @@ describe('isReactCloneElementCall', () => {
var { cloneElement } = require("react");
cloneElement({});
`);
- expect(isReactCloneElementCall(def)).toBe(true);
+ expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
});
it('accepts cloneElement called on destructed aliased value', () => {
@@ -61,7 +73,7 @@ describe('isReactCloneElementCall', () => {
var { cloneElement: foo } = require("react");
foo({});
`);
- expect(isReactCloneElementCall(def)).toBe(true);
+ expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
});
it('accepts cloneElement called on imported value', () => {
@@ -69,7 +81,7 @@ describe('isReactCloneElementCall', () => {
import { cloneElement } from "react";
cloneElement({});
`);
- expect(isReactCloneElementCall(def)).toBe(true);
+ expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
});
it('accepts cloneElement called on imported aliased value', () => {
@@ -77,7 +89,15 @@ describe('isReactCloneElementCall', () => {
import { cloneElement as foo } from "react";
foo({});
`);
- expect(isReactCloneElementCall(def)).toBe(true);
+ expect(isReactCloneElementCall(def, noopImporter)).toBe(true);
+ });
+
+ it('can resolve cloneElement imported from an intermediate module', () => {
+ const def = parsePath(`
+ import foo from "foo";
+ foo({});
+ `);
+ expect(isReactCloneElementCall(def, mockImporter)).toBe(true);
});
});
});
diff --git a/src/utils/__tests__/isReactComponentClass-test.js b/src/utils/__tests__/isReactComponentClass-test.js
index 844ad034634..f5942ff9ea0 100644
--- a/src/utils/__tests__/isReactComponentClass-test.js
+++ b/src/utils/__tests__/isReactComponentClass-test.js
@@ -6,37 +6,55 @@
*
*/
-import { expression, statement, parse } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ parse,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import isReactComponentClass from '../isReactComponentClass';
describe('isReactComponentClass', () => {
+ const mockImporter = makeMockImporter({
+ component: statement(`
+ export default React.Component;
+ import React from 'react';
+ `).get('declaration'),
+
+ pureComponent: statement(`
+ export default React.PureComponent;
+ import React from 'react';
+ `).get('declaration'),
+ });
+
describe('render method', () => {
it('ignores class declarations with a render method without superclass', () => {
const def = statement('class Foo { render() {}}');
- expect(isReactComponentClass(def)).toBe(false);
+ expect(isReactComponentClass(def, noopImporter)).toBe(false);
});
it('ignores class expression with a render method without superclass', () => {
const def = expression('class { render() {}}');
- expect(isReactComponentClass(def)).toBe(false);
+ expect(isReactComponentClass(def, noopImporter)).toBe(false);
});
it('ignores static render methods', () => {
const def = statement('class Foo { static render() {}}');
- expect(isReactComponentClass(def)).toBe(false);
+ expect(isReactComponentClass(def, noopImporter)).toBe(false);
});
it('ignores dynamic render methods', () => {
const def = statement('class Foo { static [render]() {}}');
- expect(isReactComponentClass(def)).toBe(false);
+ expect(isReactComponentClass(def, noopImporter)).toBe(false);
});
it('ignores getter or setter render methods', () => {
let def = statement('class Foo { get render() {}}');
- expect(isReactComponentClass(def)).toBe(false);
+ expect(isReactComponentClass(def, noopImporter)).toBe(false);
def = statement('class Foo { set render(value) {}}');
- expect(isReactComponentClass(def)).toBe(false);
+ expect(isReactComponentClass(def, noopImporter)).toBe(false);
});
});
@@ -51,7 +69,7 @@ describe('isReactComponentClass', () => {
class Foo extends Bar {}
`).get('body', 1);
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
});
});
@@ -62,7 +80,7 @@ describe('isReactComponentClass', () => {
class Foo extends React.Component {}
`).get('body', 1);
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
});
it('accepts class expressions extending React.Component', () => {
@@ -71,7 +89,7 @@ describe('isReactComponentClass', () => {
var Foo = class extends React.Component {}
`).get('body', 1, 'declarations', 0, 'init');
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
});
it('resolves the super class reference', () => {
@@ -81,7 +99,7 @@ describe('isReactComponentClass', () => {
class Foo extends C {}
`).get('body', 2);
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
});
it('does not accept references to other modules', () => {
@@ -90,7 +108,7 @@ describe('isReactComponentClass', () => {
class Foo extends Component {}
`).get('body', 1);
- expect(isReactComponentClass(def)).toBe(false);
+ expect(isReactComponentClass(def, noopImporter)).toBe(false);
});
it('does not consider super class if render method is present', () => {
@@ -99,7 +117,16 @@ describe('isReactComponentClass', () => {
class Foo extends Component { render() {} }
`).get('body', 1);
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
+ });
+
+ it('can resolve Component from an intermediate module', () => {
+ const def = parse(`
+ import RC from 'component';
+ class Foo extends RC {}
+ `).get('body', 1);
+
+ expect(isReactComponentClass(def, mockImporter)).toBe(true);
});
});
@@ -110,7 +137,7 @@ describe('isReactComponentClass', () => {
class Foo extends React.PureComponent {}
`).get('body', 1);
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
});
it('accepts class expressions extending React.PureComponent', () => {
@@ -119,7 +146,7 @@ describe('isReactComponentClass', () => {
var Foo = class extends React.PureComponent {}
`).get('body', 1, 'declarations', 0, 'init');
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
});
it('resolves the super class reference', () => {
@@ -129,7 +156,7 @@ describe('isReactComponentClass', () => {
class Foo extends C {}
`).get('body', 2);
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
});
it('does not accept references to other modules', () => {
@@ -138,7 +165,7 @@ describe('isReactComponentClass', () => {
class Foo extends PureComponent {}
`).get('body', 1);
- expect(isReactComponentClass(def)).toBe(false);
+ expect(isReactComponentClass(def, noopImporter)).toBe(false);
});
it('does not consider super class if render method is present', () => {
@@ -147,7 +174,16 @@ describe('isReactComponentClass', () => {
class Foo extends PureComponent { render() {} }
`).get('body', 1);
- expect(isReactComponentClass(def)).toBe(true);
+ expect(isReactComponentClass(def, noopImporter)).toBe(true);
+ });
+
+ it('can resolve PureComponent from an intermediate module', () => {
+ const def = parse(`
+ import PC from 'pureComponent';
+ class Foo extends PC {}
+ `).get('body', 1);
+
+ expect(isReactComponentClass(def, mockImporter)).toBe(true);
});
});
});
diff --git a/src/utils/__tests__/isReactComponentMethod-test.js b/src/utils/__tests__/isReactComponentMethod-test.js
index c0b4920517b..dd3f043612c 100644
--- a/src/utils/__tests__/isReactComponentMethod-test.js
+++ b/src/utils/__tests__/isReactComponentMethod-test.js
@@ -6,36 +6,56 @@
*
*/
-import { expression, statement } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import isReactComponentMethod from '../isReactComponentMethod';
describe('isReactComponentMethod', () => {
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default 'render';
+ `).get('declaration'),
+ });
+
it('returns true if the method is a component class method', () => {
const def = statement('class Foo { render() {}}');
const method = def.get('body', 'body', 0);
- expect(isReactComponentMethod(method)).toBe(true);
+ expect(isReactComponentMethod(method, noopImporter)).toBe(true);
});
it('returns true if the method is a component `createClass` object method', () => {
const def = expression('{ render() {}}');
const method = def.get('properties', 0);
- expect(isReactComponentMethod(method)).toBe(true);
+ expect(isReactComponentMethod(method, noopImporter)).toBe(true);
});
it('returns false if the method is not a component class method', () => {
const def = statement('class Foo { bar() {}}');
const method = def.get('body', 'body', 0);
- expect(isReactComponentMethod(method)).toBe(false);
+ expect(isReactComponentMethod(method, noopImporter)).toBe(false);
});
it('returns false if the method is not a component `createClass` object method', () => {
const def = expression('{ bar() {}}');
const method = def.get('properties', 0);
- expect(isReactComponentMethod(method)).toBe(false);
+ expect(isReactComponentMethod(method, noopImporter)).toBe(false);
});
it('returns false if the path is not a method or object property', () => {
const def = statement('let foo = "bar";');
- expect(isReactComponentMethod(def)).toBe(false);
+ expect(isReactComponentMethod(def, noopImporter)).toBe(false);
+ });
+
+ it('resolves imported value of computed property', () => {
+ const def = statement(`
+ class Foo { [foo]() {}}
+ import foo from 'foo';
+ `);
+ const method = def.get('body', 'body', 0);
+ expect(isReactComponentMethod(method, mockImporter)).toBe(true);
});
});
diff --git a/src/utils/__tests__/isReactCreateClassCall-test.js b/src/utils/__tests__/isReactCreateClassCall-test.js
index c7ea0c6abd0..ee6b5313cdc 100644
--- a/src/utils/__tests__/isReactCreateClassCall-test.js
+++ b/src/utils/__tests__/isReactCreateClassCall-test.js
@@ -6,7 +6,12 @@
*
*/
-import { parse } from '../../../tests/utils';
+import {
+ parse,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import isReactCreateClassCall from '../isReactCreateClassCall';
describe('isReactCreateClassCall', () => {
@@ -15,6 +20,18 @@ describe('isReactCreateClassCall', () => {
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default React.createClass;
+ import React from 'react';
+ `).get('declaration'),
+
+ bar: statement(`
+ export default makeClass;
+ import makeClass from "create-react-class";
+ `).get('declaration'),
+ });
+
describe('built in React.createClass', () => {
it('accepts createClass called on React', () => {
const def = parsePath(`
@@ -23,7 +40,7 @@ describe('isReactCreateClassCall', () => {
render() {}
});
`);
- expect(isReactCreateClassCall(def)).toBe(true);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(true);
});
it('accepts createClass called on aliased React', () => {
@@ -33,7 +50,7 @@ describe('isReactCreateClassCall', () => {
render() {}
});
`);
- expect(isReactCreateClassCall(def)).toBe(true);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(true);
});
it('ignores other React calls', () => {
@@ -41,7 +58,7 @@ describe('isReactCreateClassCall', () => {
var React = require("React");
React.isValidElement({});
`);
- expect(isReactCreateClassCall(def)).toBe(false);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(false);
});
it('ignores non React calls to createClass', () => {
@@ -51,7 +68,7 @@ describe('isReactCreateClassCall', () => {
render() {}
});
`);
- expect(isReactCreateClassCall(def)).toBe(false);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(false);
});
it('accepts createClass called on destructed value', () => {
@@ -59,7 +76,7 @@ describe('isReactCreateClassCall', () => {
var { createClass } = require("react");
createClass({});
`);
- expect(isReactCreateClassCall(def)).toBe(true);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(true);
});
it('accepts createClass called on destructed aliased value', () => {
@@ -67,7 +84,7 @@ describe('isReactCreateClassCall', () => {
var { createClass: foo } = require("react");
foo({});
`);
- expect(isReactCreateClassCall(def)).toBe(true);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(true);
});
it('accepts createClass called on imported value', () => {
@@ -75,7 +92,7 @@ describe('isReactCreateClassCall', () => {
import { createClass } from "react";
createClass({});
`);
- expect(isReactCreateClassCall(def)).toBe(true);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(true);
});
it('accepts createClass called on imported aliased value', () => {
@@ -83,7 +100,15 @@ describe('isReactCreateClassCall', () => {
import { createClass as foo } from "react";
foo({});
`);
- expect(isReactCreateClassCall(def)).toBe(true);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(true);
+ });
+
+ it('resolves createClass imported from intermediate module', () => {
+ const def = parsePath(`
+ import foo from "foo";
+ foo({});
+ `);
+ expect(isReactCreateClassCall(def, mockImporter)).toBe(true);
});
});
@@ -95,7 +120,7 @@ describe('isReactCreateClassCall', () => {
render() {}
});
`);
- expect(isReactCreateClassCall(def)).toBe(true);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(true);
});
it('accepts create-react-class calls on another name', () => {
@@ -105,7 +130,7 @@ describe('isReactCreateClassCall', () => {
render() {}
});
`);
- expect(isReactCreateClassCall(def)).toBe(true);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(true);
});
it('ignores non create-react-class calls to createReactClass', () => {
@@ -115,7 +140,15 @@ describe('isReactCreateClassCall', () => {
render() {}
});
`);
- expect(isReactCreateClassCall(def)).toBe(false);
+ expect(isReactCreateClassCall(def, noopImporter)).toBe(false);
+ });
+
+ it('resolves create-react-class imported from intermediate module', () => {
+ const def = parsePath(`
+ import bar from "bar";
+ bar({});
+ `);
+ expect(isReactCreateClassCall(def, mockImporter)).toBe(true);
});
});
});
diff --git a/src/utils/__tests__/isReactCreateElementCall-test.js b/src/utils/__tests__/isReactCreateElementCall-test.js
index 67662b391b7..58c4e30b353 100644
--- a/src/utils/__tests__/isReactCreateElementCall-test.js
+++ b/src/utils/__tests__/isReactCreateElementCall-test.js
@@ -6,7 +6,12 @@
*
*/
-import { parse } from '../../../tests/utils';
+import {
+ parse,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import isReactCreateElementCall from '../isReactCreateElementCall';
describe('isReactCreateElementCall', () => {
@@ -15,6 +20,13 @@ describe('isReactCreateElementCall', () => {
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default React.createElement;
+ import React from 'react';
+ `).get('declaration'),
+ });
+
describe('built in React.createElement', () => {
it('accepts createElement called on React', () => {
const def = parsePath(`
@@ -23,7 +35,7 @@ describe('isReactCreateElementCall', () => {
render() {}
});
`);
- expect(isReactCreateElementCall(def)).toBe(true);
+ expect(isReactCreateElementCall(def, noopImporter)).toBe(true);
});
it('accepts createElement called on aliased React', () => {
@@ -33,7 +45,7 @@ describe('isReactCreateElementCall', () => {
render() {}
});
`);
- expect(isReactCreateElementCall(def)).toBe(true);
+ expect(isReactCreateElementCall(def, noopImporter)).toBe(true);
});
it('ignores other React calls', () => {
@@ -41,7 +53,7 @@ describe('isReactCreateElementCall', () => {
var React = require("React");
React.isValidElement({});
`);
- expect(isReactCreateElementCall(def)).toBe(false);
+ expect(isReactCreateElementCall(def, noopImporter)).toBe(false);
});
it('ignores non React calls to createElement', () => {
@@ -51,7 +63,7 @@ describe('isReactCreateElementCall', () => {
render() {}
});
`);
- expect(isReactCreateElementCall(def)).toBe(false);
+ expect(isReactCreateElementCall(def, noopImporter)).toBe(false);
});
it('accepts createElement called on destructed value', () => {
@@ -59,7 +71,7 @@ describe('isReactCreateElementCall', () => {
var { createElement } = require("react");
createElement({});
`);
- expect(isReactCreateElementCall(def)).toBe(true);
+ expect(isReactCreateElementCall(def, noopImporter)).toBe(true);
});
it('accepts createElement called on destructed aliased value', () => {
@@ -67,7 +79,7 @@ describe('isReactCreateElementCall', () => {
var { createElement: foo } = require("react");
foo({});
`);
- expect(isReactCreateElementCall(def)).toBe(true);
+ expect(isReactCreateElementCall(def, noopImporter)).toBe(true);
});
it('accepts createElement called on imported value', () => {
@@ -75,7 +87,7 @@ describe('isReactCreateElementCall', () => {
import { createElement } from "react";
createElement({});
`);
- expect(isReactCreateElementCall(def)).toBe(true);
+ expect(isReactCreateElementCall(def, noopImporter)).toBe(true);
});
it('accepts createElement called on imported aliased value', () => {
@@ -83,7 +95,15 @@ describe('isReactCreateElementCall', () => {
import { createElement as foo } from "react";
foo({});
`);
- expect(isReactCreateElementCall(def)).toBe(true);
+ expect(isReactCreateElementCall(def, noopImporter)).toBe(true);
+ });
+
+ it('can resolve createElement imported from an intermediate module', () => {
+ const def = parsePath(`
+ import foo from "foo";
+ foo({});
+ `);
+ expect(isReactCreateElementCall(def, mockImporter)).toBe(true);
});
});
});
diff --git a/src/utils/__tests__/isReactForwardRefCall-test.js b/src/utils/__tests__/isReactForwardRefCall-test.js
index 2395f7cba9d..862702a0f82 100644
--- a/src/utils/__tests__/isReactForwardRefCall-test.js
+++ b/src/utils/__tests__/isReactForwardRefCall-test.js
@@ -6,7 +6,12 @@
*
*/
-import { parse } from '../../../tests/utils';
+import {
+ parse,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import isReactForwardRefCall from '../isReactForwardRefCall';
describe('isReactForwardRefCall', () => {
@@ -15,6 +20,13 @@ describe('isReactForwardRefCall', () => {
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default React.forwardRef;
+ import React from 'react';
+ `).get('declaration'),
+ });
+
describe('built in React.forwardRef', () => {
it('accepts forwardRef called on React', () => {
const def = parsePath(`
@@ -23,7 +35,7 @@ describe('isReactForwardRefCall', () => {
render() {}
});
`);
- expect(isReactForwardRefCall(def)).toBe(true);
+ expect(isReactForwardRefCall(def, noopImporter)).toBe(true);
});
it('accepts forwardRef called on aliased React', () => {
@@ -33,7 +45,7 @@ describe('isReactForwardRefCall', () => {
render() {}
});
`);
- expect(isReactForwardRefCall(def)).toBe(true);
+ expect(isReactForwardRefCall(def, noopImporter)).toBe(true);
});
it('ignores other React calls', () => {
@@ -41,7 +53,7 @@ describe('isReactForwardRefCall', () => {
var React = require("React");
React.isValidElement({});
`);
- expect(isReactForwardRefCall(def)).toBe(false);
+ expect(isReactForwardRefCall(def, noopImporter)).toBe(false);
});
it('ignores non React calls to forwardRef', () => {
@@ -51,7 +63,7 @@ describe('isReactForwardRefCall', () => {
render() {}
});
`);
- expect(isReactForwardRefCall(def)).toBe(false);
+ expect(isReactForwardRefCall(def, noopImporter)).toBe(false);
});
it('accepts forwardRef called on destructed value', () => {
@@ -59,7 +71,7 @@ describe('isReactForwardRefCall', () => {
var { forwardRef } = require("react");
forwardRef({});
`);
- expect(isReactForwardRefCall(def)).toBe(true);
+ expect(isReactForwardRefCall(def, noopImporter)).toBe(true);
});
it('accepts forwardRef called on destructed aliased value', () => {
@@ -67,7 +79,7 @@ describe('isReactForwardRefCall', () => {
var { forwardRef: foo } = require("react");
foo({});
`);
- expect(isReactForwardRefCall(def)).toBe(true);
+ expect(isReactForwardRefCall(def, noopImporter)).toBe(true);
});
it('accepts forwardRef called on imported value', () => {
@@ -75,7 +87,7 @@ describe('isReactForwardRefCall', () => {
import { forwardRef } from "react";
forwardRef({});
`);
- expect(isReactForwardRefCall(def)).toBe(true);
+ expect(isReactForwardRefCall(def, noopImporter)).toBe(true);
});
it('accepts forwardRef called on imported aliased value', () => {
@@ -83,7 +95,15 @@ describe('isReactForwardRefCall', () => {
import { forwardRef as foo } from "react";
foo({});
`);
- expect(isReactForwardRefCall(def)).toBe(true);
+ expect(isReactForwardRefCall(def, noopImporter)).toBe(true);
+ });
+
+ it('can resolve forwardRef imported from an intermediate module', () => {
+ const def = parsePath(`
+ import foo from "foo";
+ foo({});
+ `);
+ expect(isReactForwardRefCall(def, mockImporter)).toBe(true);
});
});
});
diff --git a/src/utils/__tests__/isStatelessComponent-test.js b/src/utils/__tests__/isStatelessComponent-test.js
index 10de014a609..bc69b17615a 100644
--- a/src/utils/__tests__/isStatelessComponent-test.js
+++ b/src/utils/__tests__/isStatelessComponent-test.js
@@ -6,7 +6,12 @@
*
*/
-import { parse, statement } from '../../../tests/utils';
+import {
+ parse,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import isStatelessComponent from '../isStatelessComponent';
describe('isStatelessComponent', () => {
@@ -104,7 +109,7 @@ describe('isStatelessComponent', () => {
...caseSelector,
...componentSelector,
);
- expect(isStatelessComponent(def)).toBe(true);
+ expect(isStatelessComponent(def, noopImporter)).toBe(true);
});
});
@@ -120,7 +125,7 @@ describe('isStatelessComponent', () => {
...caseSelector,
...componentSelector,
);
- expect(isStatelessComponent(def)).toBe(false);
+ expect(isStatelessComponent(def, noopImporter)).toBe(false);
});
});
});
@@ -140,7 +145,7 @@ describe('isStatelessComponent', () => {
.get('declarations', [0])
.get('init');
- expect(isStatelessComponent(def)).toBe(true);
+ expect(isStatelessComponent(def, noopImporter)).toBe(true);
});
});
@@ -166,11 +171,11 @@ describe('isStatelessComponent', () => {
const render = def.get('properties', 3);
const world = def.get('properties', 4);
- expect(isStatelessComponent(bar)).toBe(true);
- expect(isStatelessComponent(baz)).toBe(true);
- expect(isStatelessComponent(hello)).toBe(true);
- expect(isStatelessComponent(render)).toBe(false);
- expect(isStatelessComponent(world)).toBe(true);
+ expect(isStatelessComponent(bar, noopImporter)).toBe(true);
+ expect(isStatelessComponent(baz, noopImporter)).toBe(true);
+ expect(isStatelessComponent(hello, noopImporter)).toBe(true);
+ expect(isStatelessComponent(render, noopImporter)).toBe(false);
+ expect(isStatelessComponent(world, noopImporter)).toBe(true);
});
});
@@ -183,7 +188,7 @@ describe('isStatelessComponent', () => {
}
}
`);
- expect(isStatelessComponent(def)).toBe(false);
+ expect(isStatelessComponent(def, noopImporter)).toBe(false);
});
it('does not accept React.Component classes', () => {
@@ -196,7 +201,7 @@ describe('isStatelessComponent', () => {
}
`).get('body', 1);
- expect(isStatelessComponent(def)).toBe(false);
+ expect(isStatelessComponent(def, noopImporter)).toBe(false);
});
it('does not accept React.createClass calls', () => {
@@ -208,19 +213,25 @@ describe('isStatelessComponent', () => {
});
`);
- expect(isStatelessComponent(def)).toBe(false);
+ expect(isStatelessComponent(def, noopImporter)).toBe(false);
});
});
describe('resolving return values', () => {
- function test(desc, code) {
+ function test(desc, code, importer = noopImporter) {
it(desc, () => {
const def = parse(code).get('body', 1);
- expect(isStatelessComponent(def)).toBe(true);
+ expect(isStatelessComponent(def, importer)).toBe(true);
});
}
+ const mockImporter = makeMockImporter({
+ bar: statement(`
+ export default
;
+ `).get('declaration'),
+ });
+
it('does not see ifs as separate block', () => {
const def = statement(`
function Foo (props) {
@@ -230,7 +241,7 @@ describe('isStatelessComponent', () => {
}
`);
- expect(isStatelessComponent(def)).toBe(true);
+ expect(isStatelessComponent(def, noopImporter)).toBe(true);
});
it('handles recursive function calls', () => {
@@ -240,7 +251,7 @@ describe('isStatelessComponent', () => {
}
`);
- expect(isStatelessComponent(def)).toBe(false);
+ expect(isStatelessComponent(def, noopImporter)).toBe(false);
});
test(
@@ -338,5 +349,16 @@ describe('isStatelessComponent', () => {
}
`,
);
+
+ test(
+ 'resolves imported values as return',
+ `
+ import bar from 'bar';
+ function Foo (props) {
+ return bar;
+ }
+ `,
+ mockImporter,
+ );
});
});
diff --git a/src/utils/__tests__/resolveExportDeclaration-test.js b/src/utils/__tests__/resolveExportDeclaration-test.js
index 7bbd8832821..7e245485cbc 100644
--- a/src/utils/__tests__/resolveExportDeclaration-test.js
+++ b/src/utils/__tests__/resolveExportDeclaration-test.js
@@ -8,7 +8,7 @@
jest.mock('../resolveToValue');
-import { statement } from '../../../tests/utils';
+import { statement, noopImporter } from '../../../tests/utils';
import resolveToValue from '../resolveToValue';
import resolveExportDeclaration from '../resolveExportDeclaration';
@@ -21,42 +21,51 @@ describe('resolveExportDeclaration', () => {
it('resolves default exports', () => {
const exp = statement('export default 42;');
- const resolved = resolveExportDeclaration(exp);
+ const resolved = resolveExportDeclaration(exp, noopImporter);
expect(resolved).toEqual([returnValue]);
- expect(resolveToValue).toBeCalledWith(exp.get('declaration'));
+ expect(resolveToValue).toBeCalledWith(exp.get('declaration'), noopImporter);
});
it('resolves named exports', () => {
let exp = statement('export var foo = 42, bar = 21;');
- let resolved = resolveExportDeclaration(exp);
+ let resolved = resolveExportDeclaration(exp, noopImporter);
const declarations = exp.get('declaration', 'declarations');
expect(resolved).toEqual([returnValue, returnValue]);
- expect(resolveToValue).toBeCalledWith(declarations.get(0));
- expect(resolveToValue).toBeCalledWith(declarations.get(1));
+ expect(resolveToValue).toBeCalledWith(declarations.get(0), noopImporter);
+ expect(resolveToValue).toBeCalledWith(declarations.get(1), noopImporter);
exp = statement('export function foo(){}');
- resolved = resolveExportDeclaration(exp);
+ resolved = resolveExportDeclaration(exp, noopImporter);
expect(resolved).toEqual([returnValue]);
- expect(resolveToValue).toBeCalledWith(exp.get('declaration'));
+ expect(resolveToValue).toBeCalledWith(exp.get('declaration'), noopImporter);
exp = statement('export class Foo {}');
- resolved = resolveExportDeclaration(exp);
+ resolved = resolveExportDeclaration(exp, noopImporter);
expect(resolved).toEqual([returnValue]);
- expect(resolveToValue).toBeCalledWith(exp.get('declaration'));
+ expect(resolveToValue).toBeCalledWith(exp.get('declaration'), noopImporter);
});
it('resolves named exports', () => {
const exp = statement('export {foo, bar, baz}; var foo, bar, baz;');
- const resolved = resolveExportDeclaration(exp);
+ const resolved = resolveExportDeclaration(exp, noopImporter);
const specifiers = exp.get('specifiers');
expect(resolved).toEqual([returnValue, returnValue, returnValue]);
- expect(resolveToValue).toBeCalledWith(specifiers.get(0, 'local'));
- expect(resolveToValue).toBeCalledWith(specifiers.get(1, 'local'));
- expect(resolveToValue).toBeCalledWith(specifiers.get(2, 'local'));
+ expect(resolveToValue).toBeCalledWith(
+ specifiers.get(0, 'local'),
+ noopImporter,
+ );
+ expect(resolveToValue).toBeCalledWith(
+ specifiers.get(1, 'local'),
+ noopImporter,
+ );
+ expect(resolveToValue).toBeCalledWith(
+ specifiers.get(2, 'local'),
+ noopImporter,
+ );
});
});
diff --git a/src/utils/__tests__/resolveHOC-test.js b/src/utils/__tests__/resolveHOC-test.js
index d9a8589361d..f1e4421615c 100644
--- a/src/utils/__tests__/resolveHOC-test.js
+++ b/src/utils/__tests__/resolveHOC-test.js
@@ -7,23 +7,43 @@
*/
import { builders } from 'ast-types';
-import * as utils from '../../../tests/utils';
+import {
+ parse as parseSource,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import resolveHOC from '../resolveHOC';
describe('resolveHOC', () => {
function parse(src) {
- const root = utils.parse(src);
+ const root = parseSource(src);
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ component: statement(`
+ export default Component;
+ `).get('declaration'),
+
+ hoc: statement(`
+ export default hoc1(foo);
+ import foo from 'component';
+ `).get('declaration'),
+ });
+
it('resolves simple hoc', () => {
const path = parse(['hoc(Component);'].join('\n'));
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves simple hoc w/ multiple args', () => {
const path = parse(['hoc1(arg1a, arg1b)(Component);'].join('\n'));
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves nested hocs', () => {
@@ -32,7 +52,9 @@ describe('resolveHOC', () => {
hoc1(arg1a, arg2a)(Component)
);`,
);
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves really nested hocs', () => {
@@ -43,38 +65,82 @@ describe('resolveHOC', () => {
)
);`,
);
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves HOC with additional params', () => {
const path = parse(`hoc3(Component, {})`);
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves HOC as last element if first is literal', () => {
const path = parse(`hoc3(41, Component)`);
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves HOC as last element if first is array', () => {
const path = parse(`hoc3([], Component)`);
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves HOC as last element if first is object', () => {
const path = parse(`hoc3({}, Component)`);
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves HOC as last element if first is spread', () => {
const path = parse(`hoc3(...params, Component)`);
- expect(resolveHOC(path)).toEqualASTNode(builders.identifier('Component'));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
it('resolves intermediate hocs', () => {
const path = parse(
['const Component = React.memo(42);', 'hoc()(Component);'].join('\n'),
);
- expect(resolveHOC(path)).toEqualASTNode(builders.literal(42));
+ expect(resolveHOC(path, noopImporter)).toEqualASTNode(builders.literal(42));
+ });
+
+ it('can resolve an imported component passed to hoc', () => {
+ const path = parse(`
+ import foo from 'component';
+ hoc(foo);
+ `);
+ expect(resolveHOC(path, mockImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
+ });
+
+ it('can resolve an imported component passed to nested hoc', () => {
+ const path = parse(`
+ import foo from 'component';
+ hoc2(arg2b, arg2b)(
+ hoc1(arg1a, arg2a)(foo)
+ );
+ `);
+ expect(resolveHOC(path, mockImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
+ });
+
+ it('can resolve an hocs inside imported component passed to hoc', () => {
+ const path = parse(`
+ import bar from 'hoc';
+ hoc(bar);
+ `);
+ expect(resolveHOC(path, mockImporter)).toEqualASTNode(
+ builders.identifier('Component'),
+ );
});
});
diff --git a/src/utils/__tests__/resolveObjectKeysToArray-test.js b/src/utils/__tests__/resolveObjectKeysToArray-test.js
index 79c3a06412b..c359a43de9c 100644
--- a/src/utils/__tests__/resolveObjectKeysToArray-test.js
+++ b/src/utils/__tests__/resolveObjectKeysToArray-test.js
@@ -7,21 +7,45 @@
*/
import { builders } from 'ast-types';
-import * as utils from '../../../tests/utils';
+import {
+ parse as parseSource,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import resolveObjectKeysToArray from '../resolveObjectKeysToArray';
describe('resolveObjectKeysToArray', () => {
function parse(src) {
- const root = utils.parse(src);
+ const root = parseSource(src);
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default {
+ bar: "bar",
+ "foo": "foo",
+ 1: 0,
+ 2: 5,
+ [3]: 3,
+ ['baz']: "baz",
+ };
+ `).get('declaration'),
+
+ bar: statement(`
+ export default {
+ bar: 'bar',
+ };
+ `).get('declaration'),
+ });
+
it('resolves Object.keys with identifiers', () => {
const path = parse(
['var foo = { bar: 1, foo: 2 };', 'Object.keys(foo);'].join('\n'),
);
- expect(resolveObjectKeysToArray(path)).toEqualASTNode(
+ expect(resolveObjectKeysToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal('bar'),
builders.literal('foo'),
@@ -34,7 +58,7 @@ describe('resolveObjectKeysToArray', () => {
['var foo = { "bar": 1, 5: 2 };', 'Object.keys(foo);'].join('\n'),
);
- expect(resolveObjectKeysToArray(path)).toEqualASTNode(
+ expect(resolveObjectKeysToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal('bar'),
builders.literal('5'),
@@ -47,7 +71,7 @@ describe('resolveObjectKeysToArray', () => {
['var foo = { ["bar"]: 1, [5]: 2};', 'Object.keys(foo);'].join('\n'),
);
- expect(resolveObjectKeysToArray(path)).toEqualASTNode(
+ expect(resolveObjectKeysToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal('bar'),
builders.literal('5'),
@@ -64,7 +88,7 @@ describe('resolveObjectKeysToArray', () => {
].join('\n'),
);
- expect(resolveObjectKeysToArray(path)).toEqualASTNode(
+ expect(resolveObjectKeysToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal('boo'),
builders.literal('foo'),
@@ -80,7 +104,7 @@ describe('resolveObjectKeysToArray', () => {
),
);
- expect(resolveObjectKeysToArray(path)).toEqualASTNode(
+ expect(resolveObjectKeysToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal('boo'),
builders.literal('foo'),
@@ -97,7 +121,7 @@ describe('resolveObjectKeysToArray', () => {
].join('\n'),
);
- expect(resolveObjectKeysToArray(path)).toEqualASTNode(
+ expect(resolveObjectKeysToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal('boo'),
builders.literal('foo'),
@@ -115,7 +139,7 @@ describe('resolveObjectKeysToArray', () => {
].join('\n'),
);
- expect(resolveObjectKeysToArray(path)).toEqualASTNode(
+ expect(resolveObjectKeysToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal('boo'),
builders.literal('foo'),
@@ -131,7 +155,7 @@ describe('resolveObjectKeysToArray', () => {
),
);
- expect(resolveObjectKeysToArray(path)).toEqualASTNode(
+ expect(resolveObjectKeysToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([builders.literal('x')]),
);
});
@@ -141,7 +165,7 @@ describe('resolveObjectKeysToArray', () => {
['var foo = { bar: 1, foo: 2, ...bar };', 'Object.keys(foo);'].join('\n'),
);
- expect(resolveObjectKeysToArray(path)).toBeNull();
+ expect(resolveObjectKeysToArray(path, noopImporter)).toBeNull();
});
it('does not resolve Object.keys when using computed keys', () => {
@@ -149,6 +173,40 @@ describe('resolveObjectKeysToArray', () => {
['var foo = { [bar]: 1, foo: 2 };', 'Object.keys(foo);'].join('\n'),
);
- expect(resolveObjectKeysToArray(path)).toBeNull();
+ expect(resolveObjectKeysToArray(path, noopImporter)).toBeNull();
+ });
+
+ it('can resolve imported objects passed to Object.keys', () => {
+ const path = parse(`
+ import foo from 'foo';
+ Object.keys(foo);
+ `);
+
+ expect(resolveObjectKeysToArray(path, mockImporter)).toEqualASTNode(
+ builders.arrayExpression([
+ builders.literal('bar'),
+ builders.literal('foo'),
+ builders.literal(1),
+ builders.literal(2),
+ builders.literal(3),
+ builders.literal('baz'),
+ ]),
+ );
+ });
+
+ it('can resolve spreads from imported objects', () => {
+ const path = parse(`
+ import bar from 'bar';
+ var abc = { foo: 'foo', baz: 'baz', ...bar };
+ Object.keys(abc);
+ `);
+
+ expect(resolveObjectKeysToArray(path, mockImporter)).toEqualASTNode(
+ builders.arrayExpression([
+ builders.literal('foo'),
+ builders.literal('baz'),
+ builders.literal('bar'),
+ ]),
+ );
});
});
diff --git a/src/utils/__tests__/resolveObjectValuesToArray-test.js b/src/utils/__tests__/resolveObjectValuesToArray-test.js
index e14a7fa9026..9a8b90e7cba 100644
--- a/src/utils/__tests__/resolveObjectValuesToArray-test.js
+++ b/src/utils/__tests__/resolveObjectValuesToArray-test.js
@@ -7,21 +7,47 @@
*/
import { builders } from 'ast-types';
-import * as utils from '../../../tests/utils';
+import {
+ parse as parseSource,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import resolveObjectValuesToArray from '../resolveObjectValuesToArray';
describe('resolveObjectValuesToArray', () => {
function parse(src) {
- const root = utils.parse(src);
+ const root = parseSource(src);
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default {
+ 1: "bar",
+ 2: "foo",
+ 3: 0,
+ 4: 5,
+ 5: undefined,
+ 6: null,
+ [7]: 7,
+ ['foo']: "foo",
+ };
+ `).get('declaration'),
+
+ bar: statement(`
+ export default {
+ bar: 'bar',
+ };
+ `).get('declaration'),
+ });
+
it('resolves Object.values with strings', () => {
const path = parse(
['var foo = { 1: "bar", 2: "foo" };', 'Object.values(foo);'].join('\n'),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal('bar'),
builders.literal('foo'),
@@ -34,7 +60,7 @@ describe('resolveObjectValuesToArray', () => {
['var foo = { 1: 0, 2: 5 };', 'Object.values(foo);'].join('\n'),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([builders.literal(0), builders.literal(5)]),
);
});
@@ -46,7 +72,7 @@ describe('resolveObjectValuesToArray', () => {
),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal(null),
builders.literal(null),
@@ -59,7 +85,7 @@ describe('resolveObjectValuesToArray', () => {
['var foo = { ["bar"]: 1, [5]: 2};', 'Object.values(foo);'].join('\n'),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([builders.literal(2), builders.literal(1)]),
);
});
@@ -69,7 +95,7 @@ describe('resolveObjectValuesToArray', () => {
['var foo = { [()=>{}]: 1, [5]: 2};', 'Object.values(foo);'].join('\n'),
);
- expect(resolveObjectValuesToArray(path)).toBeNull();
+ expect(resolveObjectValuesToArray(path, noopImporter)).toBeNull();
});
it('resolves Object.values when using resolvable spread', () => {
@@ -81,7 +107,7 @@ describe('resolveObjectValuesToArray', () => {
].join('\n'),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal(1),
builders.literal(4),
@@ -98,7 +124,7 @@ describe('resolveObjectValuesToArray', () => {
].join('\n'),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([builders.literal(1), builders.literal(2)]),
);
});
@@ -111,7 +137,7 @@ describe('resolveObjectValuesToArray', () => {
].join('\n'),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([builders.literal(1), builders.literal(2)]),
);
});
@@ -125,7 +151,7 @@ describe('resolveObjectValuesToArray', () => {
].join('\n'),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([
builders.literal(1),
builders.literal(5),
@@ -141,7 +167,7 @@ describe('resolveObjectValuesToArray', () => {
),
);
- expect(resolveObjectValuesToArray(path)).toEqualASTNode(
+ expect(resolveObjectValuesToArray(path, noopImporter)).toEqualASTNode(
builders.arrayExpression([]),
);
});
@@ -153,6 +179,42 @@ describe('resolveObjectValuesToArray', () => {
),
);
- expect(resolveObjectValuesToArray(path)).toBeNull();
+ expect(resolveObjectValuesToArray(path, noopImporter)).toBeNull();
+ });
+
+ it('can resolve imported objects passed to Object.values', () => {
+ const path = parse(`
+ import foo from 'foo';
+ Object.values(foo);
+ `);
+
+ expect(resolveObjectValuesToArray(path, mockImporter)).toEqualASTNode(
+ builders.arrayExpression([
+ builders.literal('bar'),
+ builders.literal('foo'),
+ builders.literal(0),
+ builders.literal(5),
+ builders.literal(null),
+ builders.literal(null),
+ builders.literal(7),
+ builders.literal('foo'),
+ ]),
+ );
+ });
+
+ it('can resolve spreads from imported objects', () => {
+ const path = parse(`
+ import bar from 'bar';
+ var abc = { foo: 'foo', baz: 'baz', ...bar };
+ Object.values(abc);
+ `);
+
+ expect(resolveObjectValuesToArray(path, mockImporter)).toEqualASTNode(
+ builders.arrayExpression([
+ builders.literal('bar'),
+ builders.literal('baz'),
+ builders.literal('foo'),
+ ]),
+ );
});
});
diff --git a/src/utils/__tests__/resolveToModule-test.js b/src/utils/__tests__/resolveToModule-test.js
index 4d2c65b02d7..733ccbcb9e9 100644
--- a/src/utils/__tests__/resolveToModule-test.js
+++ b/src/utils/__tests__/resolveToModule-test.js
@@ -6,7 +6,12 @@
*
*/
-import { parse } from '../../../tests/utils';
+import {
+ parse,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
import resolveToModule from '../resolveToModule';
describe('resolveToModule', () => {
@@ -15,12 +20,24 @@ describe('resolveToModule', () => {
return root.get('body', root.node.body.length - 1, 'expression');
}
+ const mockImporter = makeMockImporter({
+ Foo: statement(`
+ export default bar;
+ import bar from 'Bar';
+ `).get('declaration'),
+
+ Bar: statement(`
+ export default baz;
+ import baz from 'Baz';
+ `).get('declaration'),
+ });
+
it('resolves identifiers', () => {
const path = parsePath(`
var foo = require("Foo");
foo;
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
});
it('resolves function calls', () => {
@@ -28,7 +45,7 @@ describe('resolveToModule', () => {
var foo = require("Foo");
foo();
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
});
it('resolves member expressions', () => {
@@ -36,7 +53,7 @@ describe('resolveToModule', () => {
var foo = require("Foo");
foo.bar().baz;
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
});
it('understands destructuring', () => {
@@ -44,7 +61,7 @@ describe('resolveToModule', () => {
var {foo} = require("Foo");
foo;
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
});
describe('ES6 import declarations', () => {
@@ -53,13 +70,13 @@ describe('resolveToModule', () => {
import foo from "Foo";
foo;
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
path = parsePath(`
import foo, {createElement} from "Foo";
foo;
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
});
it('resolves ImportSpecifier', () => {
@@ -67,7 +84,7 @@ describe('resolveToModule', () => {
import {foo, bar} from "Foo";
bar;
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
});
it('resolves aliased ImportSpecifier', () => {
@@ -75,7 +92,7 @@ describe('resolveToModule', () => {
import {foo, bar as baz} from "Foo";
baz;
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
});
it('resolves ImportNamespaceSpecifier', () => {
@@ -83,7 +100,15 @@ describe('resolveToModule', () => {
import * as foo from "Foo";
foo;
`);
- expect(resolveToModule(path)).toBe('Foo');
+ expect(resolveToModule(path, noopImporter)).toBe('Foo');
+ });
+
+ it('can resolve imports until one not expanded', () => {
+ const path = parsePath(`
+ import foo from "Foo";
+ foo;
+ `);
+ expect(resolveToModule(path, mockImporter)).toBe('Baz');
});
});
});
diff --git a/src/utils/__tests__/resolveToValue-test.js b/src/utils/__tests__/resolveToValue-test.js
index 7294eb299c3..845b0b0aec9 100644
--- a/src/utils/__tests__/resolveToValue-test.js
+++ b/src/utils/__tests__/resolveToValue-test.js
@@ -7,7 +7,7 @@
*/
import { builders } from 'ast-types';
-import { parse } from '../../../tests/utils';
+import { parse, noopImporter } from '../../../tests/utils';
import resolveToValue from '../resolveToValue';
describe('resolveToValue', () => {
@@ -18,14 +18,16 @@ describe('resolveToValue', () => {
it('resolves simple variable declarations', () => {
const path = parsePath(['var foo = 42;', 'foo;'].join('\n'));
- expect(resolveToValue(path)).toEqualASTNode(builders.literal(42));
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(
+ builders.literal(42),
+ );
});
it('resolves object destructuring', () => {
const path = parsePath(['var {foo: {bar: baz}} = bar;', 'baz;'].join('\n'));
// Node should be equal to bar.foo.bar
- expect(resolveToValue(path)).toEqualASTNode(
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(
builders.memberExpression(
builders.memberExpression(
builders.identifier('bar'),
@@ -41,19 +43,21 @@ describe('resolveToValue', () => {
['var {foo: {bar}, ...baz} = bar;', 'baz;'].join('\n'),
);
- expect(resolveToValue(path)).toEqualASTNode(path);
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(path);
});
it('returns the original path if it cannot be resolved', () => {
const path = parsePath(['function foo() {}', 'foo()'].join('\n'));
- expect(resolveToValue(path)).toEqualASTNode(path);
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(path);
});
it('resolves variable declarators to their init value', () => {
const path = parse('var foo = 42;').get('body', 0, 'declarations', 0);
- expect(resolveToValue(path)).toEqualASTNode(builders.literal(42));
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(
+ builders.literal(42),
+ );
});
it('resolves to class declarations', () => {
@@ -61,7 +65,9 @@ describe('resolveToValue', () => {
class Foo {}
Foo;
`);
- expect(resolveToValue(path).node.type).toBe('ClassDeclaration');
+ expect(resolveToValue(path, noopImporter).node.type).toBe(
+ 'ClassDeclaration',
+ );
});
it('resolves to class function declaration', () => {
@@ -69,7 +75,9 @@ describe('resolveToValue', () => {
function foo() {}
foo;
`);
- expect(resolveToValue(path).node.type).toBe('FunctionDeclaration');
+ expect(resolveToValue(path, noopImporter).node.type).toBe(
+ 'FunctionDeclaration',
+ );
});
describe('flow', () => {
@@ -78,7 +86,9 @@ describe('resolveToValue', () => {
function foo() {}
(foo: any);
`);
- expect(resolveToValue(path).node.type).toBe('FunctionDeclaration');
+ expect(resolveToValue(path, noopImporter).node.type).toBe(
+ 'FunctionDeclaration',
+ );
});
});
@@ -91,7 +101,9 @@ describe('resolveToValue', () => {
function foo() {}
(foo as any);
`);
- expect(resolveToValue(path).node.type).toBe('FunctionDeclaration');
+ expect(resolveToValue(path, noopImporter).node.type).toBe(
+ 'FunctionDeclaration',
+ );
});
it('resolves type assertions', () => {
@@ -99,7 +111,9 @@ describe('resolveToValue', () => {
function foo() {}
( foo);
`);
- expect(resolveToValue(path).node.type).toBe('FunctionDeclaration');
+ expect(resolveToValue(path, noopImporter).node.type).toBe(
+ 'FunctionDeclaration',
+ );
});
});
@@ -107,7 +121,9 @@ describe('resolveToValue', () => {
it('resolves to assigned values', () => {
const path = parsePath(['var foo;', 'foo = 42;', 'foo;'].join('\n'));
- expect(resolveToValue(path)).toEqualASTNode(builders.literal(42));
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(
+ builders.literal(42),
+ );
});
it('resolves to other assigned value if ref is in an assignment lhs', () => {
@@ -115,7 +131,7 @@ describe('resolveToValue', () => {
['var foo;', 'foo = 42;', 'foo = wrap(foo);'].join('\n'),
);
- expect(resolveToValue(path.get('left'))).toEqualASTNode(
+ expect(resolveToValue(path.get('left'), noopImporter)).toEqualASTNode(
builders.literal(42),
);
});
@@ -125,16 +141,16 @@ describe('resolveToValue', () => {
['var foo;', 'foo = 42;', 'foo = wrap(foo);'].join('\n'),
);
- expect(resolveToValue(path.get('right', 'arguments', 0))).toEqualASTNode(
- builders.literal(42),
- );
+ expect(
+ resolveToValue(path.get('right', 'arguments', 0), noopImporter),
+ ).toEqualASTNode(builders.literal(42));
});
});
describe('ImportDeclaration', () => {
it('resolves default import references to the import declaration', () => {
const path = parsePath(['import foo from "Foo"', 'foo;'].join('\n'));
- const value = resolveToValue(path);
+ const value = resolveToValue(path, noopImporter);
expect(Array.isArray(value.value)).toBe(false);
expect(value.node.type).toBe('ImportDeclaration');
@@ -142,7 +158,7 @@ describe('resolveToValue', () => {
it('resolves named import references to the import declaration', () => {
const path = parsePath(['import {foo} from "Foo"', 'foo;'].join('\n'));
- const value = resolveToValue(path);
+ const value = resolveToValue(path, noopImporter);
expect(Array.isArray(value.value)).toBe(false);
expect(value.node.type).toBe('ImportDeclaration');
@@ -152,7 +168,7 @@ describe('resolveToValue', () => {
const path = parsePath(
['import {foo as bar} from "Foo"', 'bar;'].join('\n'),
);
- const value = resolveToValue(path);
+ const value = resolveToValue(path, noopImporter);
expect(Array.isArray(value.value)).toBe(false);
expect(value.node.type).toBe('ImportDeclaration');
@@ -160,7 +176,7 @@ describe('resolveToValue', () => {
it('resolves namespace import references to the import declaration', () => {
const path = parsePath(['import * as bar from "Foo"', 'bar;'].join('\n'));
- const value = resolveToValue(path);
+ const value = resolveToValue(path, noopImporter);
expect(Array.isArray(value.value)).toBe(false);
expect(value.node.type).toBe('ImportDeclaration');
@@ -171,7 +187,9 @@ describe('resolveToValue', () => {
it("resolves a MemberExpression to it's init value", () => {
const path = parsePath(['var foo = { bar: 1 };', 'foo.bar;'].join('\n'));
- expect(resolveToValue(path)).toEqualASTNode(builders.literal(1));
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(
+ builders.literal(1),
+ );
});
it('resolves a MemberExpression in the scope chain', () => {
@@ -179,7 +197,9 @@ describe('resolveToValue', () => {
['var foo = 1;', 'var bar = { baz: foo };', 'bar.baz;'].join('\n'),
);
- expect(resolveToValue(path)).toEqualASTNode(builders.literal(1));
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(
+ builders.literal(1),
+ );
});
it('resolves a nested MemberExpression in the scope chain', () => {
@@ -191,7 +211,9 @@ describe('resolveToValue', () => {
].join('\n'),
);
- expect(resolveToValue(path)).toEqualASTNode(builders.literal(1));
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(
+ builders.literal(1),
+ );
});
it('returns the last resolvable MemberExpression', () => {
@@ -203,7 +225,7 @@ describe('resolveToValue', () => {
].join('\n'),
);
- expect(resolveToValue(path)).toEqualASTNode(
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(
builders.memberExpression(
builders.identifier('foo'),
builders.identifier('bar'),
@@ -216,7 +238,7 @@ describe('resolveToValue', () => {
['var foo = {};', 'foo.bar = 1;', 'foo.bar;'].join('\n'),
);
- expect(resolveToValue(path)).toEqualASTNode(path);
+ expect(resolveToValue(path, noopImporter)).toEqualASTNode(path);
});
});
});
diff --git a/src/utils/__tests__/setPropDescription-test.js b/src/utils/__tests__/setPropDescription-test.js
index e2012414a1d..31596a9dc38 100644
--- a/src/utils/__tests__/setPropDescription-test.js
+++ b/src/utils/__tests__/setPropDescription-test.js
@@ -8,7 +8,12 @@
jest.mock('../../Documentation');
-import { expression } from '../../../tests/utils';
+import {
+ expression,
+ statement,
+ noopImporter,
+ makeMockImporter,
+} from '../../../tests/utils';
describe('setPropDescription', () => {
let defaultDocumentation;
@@ -19,10 +24,16 @@ describe('setPropDescription', () => {
setPropDescription = require('../setPropDescription').default;
});
+ const mockImporter = makeMockImporter({
+ foo: statement(`
+ export default 'foo';
+ `).get('declaration'),
+ });
+
function getDescriptors(src, documentation = defaultDocumentation) {
const node = expression(src).get('properties', 0);
- setPropDescription(documentation, node);
+ setPropDescription(documentation, node, noopImporter);
return documentation.descriptors;
}
@@ -72,4 +83,26 @@ describe('setPropDescription', () => {
},
});
});
+
+ it('resolves computed props to imported values', () => {
+ const src = `
+ ({
+ /**
+ * my description 3
+ */
+
+ [a]: boolean,
+ });
+ import a from 'foo';
+ `;
+ const node = statement(src).get('expression', 'properties', 0);
+
+ setPropDescription(defaultDocumentation, node, mockImporter);
+
+ expect(defaultDocumentation.descriptors).toEqual({
+ foo: {
+ description: 'my description 3',
+ },
+ });
+ });
});
diff --git a/src/utils/expressionTo.js b/src/utils/expressionTo.js
index 609f8fd2478..5a5348c449c 100644
--- a/src/utils/expressionTo.js
+++ b/src/utils/expressionTo.js
@@ -11,12 +11,13 @@
import { namedTypes as t } from 'ast-types';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
/**
* Splits a MemberExpression or CallExpression into parts.
* E.g. foo.bar.baz becomes ['foo', 'bar', 'baz']
*/
-function toArray(path: NodePath): Array {
+function toArray(path: NodePath, importer: Importer): Array {
const parts = [path];
let result = [];
@@ -29,9 +30,9 @@ function toArray(path: NodePath): Array {
} else if (t.MemberExpression.check(node)) {
parts.push(path.get('object'));
if (node.computed) {
- const resolvedPath = resolveToValue(path.get('property'));
+ const resolvedPath = resolveToValue(path.get('property'), importer);
if (resolvedPath !== undefined) {
- result = result.concat(toArray(resolvedPath));
+ result = result.concat(toArray(resolvedPath, importer));
} else {
result.push('');
}
@@ -51,7 +52,9 @@ function toArray(path: NodePath): Array {
} else if (t.ObjectExpression.check(node)) {
const properties = path.get('properties').map(function(property) {
return (
- toString(property.get('key')) + ': ' + toString(property.get('value'))
+ toString(property.get('key'), importer) +
+ ': ' +
+ toString(property.get('value'), importer)
);
});
result.push('{' + properties.join(', ') + '}');
@@ -61,7 +64,9 @@ function toArray(path: NodePath): Array {
'[' +
path
.get('elements')
- .map(toString)
+ .map(function(el) {
+ return toString(el, importer);
+ })
.join(', ') +
']',
);
@@ -75,8 +80,8 @@ function toArray(path: NodePath): Array {
/**
* Creates a string representation of a member expression.
*/
-function toString(path: NodePath): string {
- return toArray(path).join('.');
+function toString(path: NodePath, importer: Importer): string {
+ return toArray(path, importer).join('.');
}
export { toString as String, toArray as Array };
diff --git a/src/utils/getClassMemberValuePath.js b/src/utils/getClassMemberValuePath.js
index 55005438709..d53d1b67161 100644
--- a/src/utils/getClassMemberValuePath.js
+++ b/src/utils/getClassMemberValuePath.js
@@ -9,10 +9,12 @@
import { namedTypes as t } from 'ast-types';
import getNameOrValue from './getNameOrValue';
+import type { Importer } from '../types';
export default function getClassMemberValuePath(
classDefinition: NodePath,
memberName: string,
+ _importer: Importer, // eslint-disable-line no-unused-vars
): ?NodePath {
// Fortunately it seems like that all members of a class body, be it
// ClassProperty or MethodDefinition, have the same structure: They have a
diff --git a/src/utils/getFlowType.js b/src/utils/getFlowType.js
index 5eb1ad7ff7c..fc0085ec59c 100644
--- a/src/utils/getFlowType.js
+++ b/src/utils/getFlowType.js
@@ -22,6 +22,7 @@ import type {
FlowObjectSignatureType,
FlowSimpleType,
FlowTypeDescriptor,
+ Importer,
} from '../types';
const flowTypes = {
@@ -57,28 +58,32 @@ const namedTypes = {
function getFlowTypeWithRequirements(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
- const type = getFlowTypeWithResolvedTypes(path, typeParams);
+ const type = getFlowTypeWithResolvedTypes(path, typeParams, importer);
type.required = !path.parentPath.node.optional;
return type;
}
-function handleKeysHelper(path: NodePath): ?FlowElementsType {
+function handleKeysHelper(
+ path: NodePath,
+ importer: Importer,
+): ?FlowElementsType {
let value = path.get('typeParameters', 'params', 0);
if (t.TypeofTypeAnnotation.check(value.node)) {
value = value.get('argument', 'id');
} else if (!t.ObjectTypeAnnotation.check(value.node)) {
value = value.get('id');
}
- const resolvedPath = resolveToValue(value);
+ const resolvedPath = resolveToValue(value, importer);
if (
resolvedPath &&
(t.ObjectExpression.check(resolvedPath.node) ||
t.ObjectTypeAnnotation.check(resolvedPath.node))
) {
- const keys = resolveObjectToNameArray(resolvedPath, true);
+ const keys = resolveObjectToNameArray(resolvedPath, importer, true);
if (keys) {
return {
@@ -95,11 +100,16 @@ function handleKeysHelper(path: NodePath): ?FlowElementsType {
function handleArrayTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowElementsType {
return {
name: 'Array',
elements: [
- getFlowTypeWithResolvedTypes(path.get('elementType'), typeParams),
+ getFlowTypeWithResolvedTypes(
+ path.get('elementType'),
+ typeParams,
+ importer,
+ ),
],
raw: printValue(path),
};
@@ -108,9 +118,10 @@ function handleArrayTypeAnnotation(
function handleGenericTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): ?FlowTypeDescriptor {
if (path.node.id.name === '$Keys' && path.node.typeParameters) {
- return handleKeysHelper(path);
+ return handleKeysHelper(path, importer);
}
let type: FlowTypeDescriptor;
@@ -130,13 +141,15 @@ function handleGenericTypeAnnotation(
}
const resolvedPath =
- (typeParams && typeParams[type.name]) || resolveToValue(path.get('id'));
+ (typeParams && typeParams[type.name]) ||
+ resolveToValue(path.get('id'), importer);
if (path.node.typeParameters && resolvedPath.node.typeParameters) {
typeParams = getTypeParameters(
resolvedPath.get('typeParameters'),
path.get('typeParameters'),
typeParams,
+ importer,
);
}
@@ -149,18 +162,22 @@ function handleGenericTypeAnnotation(
}
if (typeParams && typeParams[type.name]) {
- type = getFlowTypeWithResolvedTypes(resolvedPath, typeParams);
+ type = getFlowTypeWithResolvedTypes(resolvedPath, typeParams, importer);
}
if (resolvedPath && resolvedPath.node.right) {
- type = getFlowTypeWithResolvedTypes(resolvedPath.get('right'), typeParams);
+ type = getFlowTypeWithResolvedTypes(
+ resolvedPath.get('right'),
+ typeParams,
+ importer,
+ );
} else if (path.node.typeParameters) {
const params = path.get('typeParameters').get('params');
type = {
...type,
elements: params.map(param =>
- getFlowTypeWithResolvedTypes(param, typeParams),
+ getFlowTypeWithResolvedTypes(param, typeParams, importer),
),
raw: printValue(path),
};
@@ -172,6 +189,7 @@ function handleGenericTypeAnnotation(
function handleObjectTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
const type: FlowObjectSignatureType = {
name: 'signature',
@@ -184,13 +202,18 @@ function handleObjectTypeAnnotation(
type.signature.constructor = getFlowTypeWithResolvedTypes(
param.get('value'),
typeParams,
+ importer,
);
});
path.get('indexers').each(param => {
type.signature.properties.push({
- key: getFlowTypeWithResolvedTypes(param.get('key'), typeParams),
- value: getFlowTypeWithRequirements(param.get('value'), typeParams),
+ key: getFlowTypeWithResolvedTypes(param.get('key'), typeParams, importer),
+ value: getFlowTypeWithRequirements(
+ param.get('value'),
+ typeParams,
+ importer,
+ ),
});
});
@@ -198,8 +221,12 @@ function handleObjectTypeAnnotation(
if (t.ObjectTypeProperty.check(param.node)) {
type.signature.properties.push({
// For ObjectTypeProperties `getPropertyName` always returns string
- key: ((getPropertyName(param): any): string),
- value: getFlowTypeWithRequirements(param.get('value'), typeParams),
+ key: ((getPropertyName(param, importer): any): string),
+ value: getFlowTypeWithRequirements(
+ param.get('value'),
+ typeParams,
+ importer,
+ ),
});
}
});
@@ -218,38 +245,49 @@ function handleInterfaceDeclaration(path: NodePath): FlowSimpleType {
function handleUnionTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowElementsType {
return {
name: 'union',
raw: printValue(path),
elements: path
.get('types')
- .map(subType => getFlowTypeWithResolvedTypes(subType, typeParams)),
+ .map(subType =>
+ getFlowTypeWithResolvedTypes(subType, typeParams, importer),
+ ),
};
}
function handleIntersectionTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowElementsType {
return {
name: 'intersection',
raw: printValue(path),
elements: path
.get('types')
- .map(subType => getFlowTypeWithResolvedTypes(subType, typeParams)),
+ .map(subType =>
+ getFlowTypeWithResolvedTypes(subType, typeParams, importer),
+ ),
};
}
function handleNullableTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): ?FlowTypeDescriptor {
const typeAnnotation = getTypeAnnotation(path);
if (!typeAnnotation) return null;
- const type = getFlowTypeWithResolvedTypes(typeAnnotation, typeParams);
+ const type = getFlowTypeWithResolvedTypes(
+ typeAnnotation,
+ typeParams,
+ importer,
+ );
type.nullable = true;
return type;
@@ -258,6 +296,7 @@ function handleNullableTypeAnnotation(
function handleFunctionTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowFunctionSignatureType {
const type: FlowFunctionSignatureType = {
name: 'signature',
@@ -265,7 +304,11 @@ function handleFunctionTypeAnnotation(
raw: printValue(path),
signature: {
arguments: [],
- return: getFlowTypeWithResolvedTypes(path.get('returnType'), typeParams),
+ return: getFlowTypeWithResolvedTypes(
+ path.get('returnType'),
+ typeParams,
+ importer,
+ ),
},
};
@@ -275,7 +318,7 @@ function handleFunctionTypeAnnotation(
type.signature.arguments.push({
name: param.node.name ? param.node.name.name : '',
type: typeAnnotation
- ? getFlowTypeWithResolvedTypes(typeAnnotation, typeParams)
+ ? getFlowTypeWithResolvedTypes(typeAnnotation, typeParams, importer)
: undefined,
});
});
@@ -287,7 +330,7 @@ function handleFunctionTypeAnnotation(
type.signature.arguments.push({
name: rest.node.name ? rest.node.name.name : '',
type: typeAnnotation
- ? getFlowTypeWithResolvedTypes(typeAnnotation, typeParams)
+ ? getFlowTypeWithResolvedTypes(typeAnnotation, typeParams, importer)
: undefined,
rest: true,
});
@@ -299,6 +342,7 @@ function handleFunctionTypeAnnotation(
function handleTupleTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowElementsType {
const type: FlowElementsType = {
name: 'tuple',
@@ -307,7 +351,9 @@ function handleTupleTypeAnnotation(
};
path.get('types').each(param => {
- type.elements.push(getFlowTypeWithResolvedTypes(param, typeParams));
+ type.elements.push(
+ getFlowTypeWithResolvedTypes(param, typeParams, importer),
+ );
});
return type;
@@ -316,8 +362,13 @@ function handleTupleTypeAnnotation(
function handleTypeofTypeAnnotation(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
- return getFlowTypeWithResolvedTypes(path.get('argument'), typeParams);
+ return getFlowTypeWithResolvedTypes(
+ path.get('argument'),
+ typeParams,
+ importer,
+ );
}
let visitedTypes = {};
@@ -325,6 +376,7 @@ let visitedTypes = {};
function getFlowTypeWithResolvedTypes(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
const node = path.node;
let type: ?FlowTypeDescriptor;
@@ -350,7 +402,7 @@ function getFlowTypeWithResolvedTypes(
} else if (node.type in flowLiteralTypes) {
type = { name: 'literal', value: node.raw || `${node.value}` };
} else if (node.type in namedTypes) {
- type = namedTypes[node.type](path, typeParams);
+ type = namedTypes[node.type](path, typeParams, importer);
}
if (!type) {
@@ -375,12 +427,13 @@ function getFlowTypeWithResolvedTypes(
export default function getFlowType(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
// Empty visited types before an after run
// Before: in case the detection threw and we rerun again
// After: cleanup memory after we are done here
visitedTypes = {};
- const type = getFlowTypeWithResolvedTypes(path, typeParams);
+ const type = getFlowTypeWithResolvedTypes(path, typeParams, importer);
visitedTypes = {};
return type;
diff --git a/src/utils/getFlowTypeFromReactComponent.js b/src/utils/getFlowTypeFromReactComponent.js
index 74f98b7c062..70441638884 100644
--- a/src/utils/getFlowTypeFromReactComponent.js
+++ b/src/utils/getFlowTypeFromReactComponent.js
@@ -15,11 +15,12 @@ import isReactComponentClass from './isReactComponentClass';
import isReactForwardRefCall from './isReactForwardRefCall';
import resolveGenericTypeAnnotation from './resolveGenericTypeAnnotation';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
-function getStatelessPropsPath(componentDefinition): NodePath {
- const value = resolveToValue(componentDefinition);
- if (isReactForwardRefCall(value)) {
- const inner = resolveToValue(value.get('arguments', 0));
+function getStatelessPropsPath(componentDefinition, importer): NodePath {
+ const value = resolveToValue(componentDefinition, importer);
+ if (isReactForwardRefCall(value, importer)) {
+ const inner = resolveToValue(value.get('arguments', 0), importer);
return inner.get('params', 0);
}
return value.get('params', 0);
@@ -30,10 +31,10 @@ function getStatelessPropsPath(componentDefinition): NodePath {
* flow type for the props. If not found or not one of the supported
* component types returns null.
*/
-export default (path: NodePath): ?NodePath => {
+export default (path: NodePath, importer: Importer): ?NodePath => {
let typePath: ?NodePath = null;
- if (isReactComponentClass(path)) {
+ if (isReactComponentClass(path, importer)) {
const superTypes = path.get('superTypeParameters');
if (superTypes.value) {
@@ -44,7 +45,7 @@ export default (path: NodePath): ?NodePath => {
typePath = params.get(0);
}
} else {
- const propsMemberPath = getMemberValuePath(path, 'props');
+ const propsMemberPath = getMemberValuePath(path, 'props', importer);
if (!propsMemberPath) {
return null;
}
@@ -55,7 +56,7 @@ export default (path: NodePath): ?NodePath => {
return typePath;
}
- const propsParam = getStatelessPropsPath(path);
+ const propsParam = getStatelessPropsPath(path, importer);
if (propsParam) {
typePath = getTypeAnnotation(propsParam);
@@ -69,6 +70,7 @@ export function applyToFlowTypeProperties(
path: NodePath,
callback: (propertyPath: NodePath, typeParams: ?TypeParameters) => void,
typeParams?: ?TypeParameters,
+ importer: Importer,
) {
if (path.node.properties) {
path
@@ -80,7 +82,7 @@ export function applyToFlowTypeProperties(
.each(propertyPath => callback(propertyPath, typeParams));
} else if (path.node.type === 'InterfaceDeclaration') {
if (path.node.extends) {
- applyExtends(documentation, path, callback, typeParams);
+ applyExtends(documentation, path, callback, typeParams, importer);
}
path
@@ -88,7 +90,7 @@ export function applyToFlowTypeProperties(
.each(propertyPath => callback(propertyPath, typeParams));
} else if (path.node.type === 'TSInterfaceDeclaration') {
if (path.node.extends) {
- applyExtends(documentation, path, callback, typeParams);
+ applyExtends(documentation, path, callback, typeParams, importer);
}
path
@@ -106,27 +108,35 @@ export function applyToFlowTypeProperties(
typesPath,
callback,
typeParams,
+ importer,
),
);
} else if (path.node.type !== 'UnionTypeAnnotation') {
// The react-docgen output format does not currently allow
// for the expression of union types
- const typePath = resolveGenericTypeAnnotation(path);
+ const typePath = resolveGenericTypeAnnotation(path, importer);
if (typePath) {
- applyToFlowTypeProperties(documentation, typePath, callback, typeParams);
+ applyToFlowTypeProperties(
+ documentation,
+ typePath,
+ callback,
+ typeParams,
+ importer,
+ );
}
}
}
-function applyExtends(documentation, path, callback, typeParams) {
+function applyExtends(documentation, path, callback, typeParams, importer) {
path.get('extends').each((extendsPath: NodePath) => {
- const resolvedPath = resolveGenericTypeAnnotation(extendsPath);
+ const resolvedPath = resolveGenericTypeAnnotation(extendsPath, importer);
if (resolvedPath) {
if (resolvedPath.node.typeParameters && extendsPath.node.typeParameters) {
typeParams = getTypeParameters(
resolvedPath.get('typeParameters'),
extendsPath.get('typeParameters'),
typeParams,
+ importer,
);
}
applyToFlowTypeProperties(
@@ -134,6 +144,7 @@ function applyExtends(documentation, path, callback, typeParams) {
resolvedPath,
callback,
typeParams,
+ importer,
);
} else {
const id =
diff --git a/src/utils/getMemberExpressionValuePath.js b/src/utils/getMemberExpressionValuePath.js
index 2c210f4f810..7af6128916a 100644
--- a/src/utils/getMemberExpressionValuePath.js
+++ b/src/utils/getMemberExpressionValuePath.js
@@ -11,8 +11,9 @@ import { namedTypes as t, visit } from 'ast-types';
import getNameOrValue from './getNameOrValue';
import { String as toString } from './expressionTo';
import isReactForwardRefCall from './isReactForwardRefCall';
+import type { Importer } from '../types';
-function resolveName(path) {
+function resolveName(path, importer) {
if (t.VariableDeclaration.check(path.node)) {
const declarations = path.get('declarations');
if (declarations.value.length && declarations.value.length !== 1) {
@@ -36,7 +37,7 @@ function resolveName(path) {
t.ArrowFunctionExpression.check(path.node) ||
t.TaggedTemplateExpression.check(path.node) ||
t.CallExpression.check(path.node) ||
- isReactForwardRefCall(path)
+ isReactForwardRefCall(path, importer)
) {
let currentPath = path;
while (currentPath.parent) {
@@ -69,8 +70,9 @@ function getRoot(node) {
export default function getMemberExpressionValuePath(
variableDefinition: NodePath,
memberName: string,
+ importer: Importer,
): ?NodePath {
- const localName = resolveName(variableDefinition);
+ const localName = resolveName(variableDefinition, importer);
const program = getRoot(variableDefinition);
if (!localName) {
@@ -91,7 +93,7 @@ export default function getMemberExpressionValuePath(
(!memberPath.node.computed ||
t.Literal.check(memberPath.node.property)) &&
getNameOrValue(memberPath.get('property')) === memberName &&
- toString(memberPath.get('object')) === localName
+ toString(memberPath.get('object'), importer) === localName
) {
result = path.get('right');
return false;
diff --git a/src/utils/getMemberValuePath.js b/src/utils/getMemberValuePath.js
index 7ed9e1840dc..a6fa3e28c06 100644
--- a/src/utils/getMemberValuePath.js
+++ b/src/utils/getMemberValuePath.js
@@ -11,31 +11,32 @@ import getClassMemberValuePath from './getClassMemberValuePath';
import getMemberExpressionValuePath from './getMemberExpressionValuePath';
import getPropertyValuePath from './getPropertyValuePath';
import resolveFunctionDefinitionToReturnValue from '../utils/resolveFunctionDefinitionToReturnValue';
+import type { Importer } from '../types';
const SYNONYMS = {
getDefaultProps: 'defaultProps',
defaultProps: 'getDefaultProps',
};
-const POSTPROCESS_MEMBERS = {
- propTypes: path =>
- t.Function.check(path.node)
- ? resolveFunctionDefinitionToReturnValue(path)
- : path,
-};
+const postprocessPropTypes = (path, importer) =>
+ t.Function.check(path.node)
+ ? resolveFunctionDefinitionToReturnValue(path, importer)
+ : path;
-const LOOKUP_METHOD = {
- [t.ArrowFunctionExpression.name]: getMemberExpressionValuePath,
- [t.CallExpression.name]: getMemberExpressionValuePath,
- [t.FunctionExpression.name]: getMemberExpressionValuePath,
- [t.FunctionDeclaration.name]: getMemberExpressionValuePath,
- [t.VariableDeclaration.name]: getMemberExpressionValuePath,
- [t.ObjectExpression.name]: getPropertyValuePath,
- [t.ClassDeclaration.name]: getClassMemberValuePath,
- [t.ClassExpression.name]: getClassMemberValuePath,
-};
+const POSTPROCESS_MEMBERS = new Map([['propTypes', postprocessPropTypes]]);
+
+const LOOKUP_METHOD = new Map([
+ [t.ArrowFunctionExpression.name, getMemberExpressionValuePath],
+ [t.CallExpression.name, getMemberExpressionValuePath],
+ [t.FunctionExpression.name, getMemberExpressionValuePath],
+ [t.FunctionDeclaration.name, getMemberExpressionValuePath],
+ [t.VariableDeclaration.name, getMemberExpressionValuePath],
+ [t.ObjectExpression.name, getPropertyValuePath],
+ [t.ClassDeclaration.name, getClassMemberValuePath],
+ [t.ClassExpression.name, getClassMemberValuePath],
+]);
-function isSupportedDefinitionType({ node }) {
+export function isSupportedDefinitionType({ node }: NodePath) {
return (
t.ObjectExpression.check(node) ||
t.ClassDeclaration.check(node) ||
@@ -86,6 +87,7 @@ function isSupportedDefinitionType({ node }) {
export default function getMemberValuePath(
componentDefinition: NodePath,
memberName: string,
+ importer: Importer,
): ?NodePath {
if (!isSupportedDefinitionType(componentDefinition)) {
throw new TypeError(
@@ -99,14 +101,15 @@ export default function getMemberValuePath(
}
const lookupMethod =
- LOOKUP_METHOD[componentDefinition.node.type] ||
+ LOOKUP_METHOD.get(componentDefinition.node.type) ||
getMemberExpressionValuePath;
- let result = lookupMethod(componentDefinition, memberName);
+ let result = lookupMethod(componentDefinition, memberName, importer);
if (!result && SYNONYMS[memberName]) {
- result = lookupMethod(componentDefinition, SYNONYMS[memberName]);
+ result = lookupMethod(componentDefinition, SYNONYMS[memberName], importer);
}
- if (result && POSTPROCESS_MEMBERS[memberName]) {
- result = POSTPROCESS_MEMBERS[memberName](result);
+ const postprocessMethod = POSTPROCESS_MEMBERS.get(memberName);
+ if (result && postprocessMethod) {
+ result = postprocessMethod(result, importer);
}
return result;
diff --git a/src/utils/getMethodDocumentation.js b/src/utils/getMethodDocumentation.js
index 517003477cc..592873dfb02 100644
--- a/src/utils/getMethodDocumentation.js
+++ b/src/utils/getMethodDocumentation.js
@@ -14,7 +14,7 @@ import getTSType from './getTSType';
import getParameterName from './getParameterName';
import getPropertyName from './getPropertyName';
import getTypeAnnotation from './getTypeAnnotation';
-import type { FlowTypeDescriptor } from '../types';
+import type { FlowTypeDescriptor, Importer } from '../types';
import resolveToValue from './resolveToValue';
type MethodParameter = {
@@ -35,29 +35,28 @@ type MethodDocumentation = {
returns: ?MethodReturn,
};
-function getMethodFunctionExpression(methodPath) {
- if (t.AssignmentExpression.check(methodPath.node)) {
- return resolveToValue(methodPath.get('right'));
- }
- // Otherwise this is a method/property node
- return methodPath.get('value');
+function getMethodFunctionExpression(methodPath, importer) {
+ const exprPath = t.AssignmentExpression.check(methodPath.node)
+ ? methodPath.get('right')
+ : methodPath.get('value');
+ return resolveToValue(exprPath, importer);
}
-function getMethodParamsDoc(methodPath) {
+function getMethodParamsDoc(methodPath, importer) {
const params = [];
- const functionExpression = getMethodFunctionExpression(methodPath);
+ const functionExpression = getMethodFunctionExpression(methodPath, importer);
// Extract param flow types.
functionExpression.get('params').each(paramPath => {
let type = null;
const typePath = getTypeAnnotation(paramPath);
if (typePath && t.Flow.check(typePath.node)) {
- type = getFlowType(typePath);
+ type = getFlowType(typePath, null, importer);
if (t.GenericTypeAnnotation.check(typePath.node)) {
type.alias = typePath.node.id.name;
}
} else if (typePath) {
- type = getTSType(typePath);
+ type = getTSType(typePath, null, importer);
if (t.TSTypeReference.check(typePath.node)) {
type.alias = typePath.node.typeName.name;
}
@@ -76,15 +75,15 @@ function getMethodParamsDoc(methodPath) {
}
// Extract flow return type.
-function getMethodReturnDoc(methodPath) {
- const functionExpression = getMethodFunctionExpression(methodPath);
+function getMethodReturnDoc(methodPath, importer) {
+ const functionExpression = getMethodFunctionExpression(methodPath, importer);
if (functionExpression.node.returnType) {
const returnType = getTypeAnnotation(functionExpression.get('returnType'));
if (returnType && t.Flow.check(returnType.node)) {
- return { type: getFlowType(returnType) };
+ return { type: getFlowType(returnType, null, importer) };
} else if (returnType) {
- return { type: getTSType(returnType) };
+ return { type: getTSType(returnType, null, importer) };
}
}
@@ -119,7 +118,7 @@ function getMethodModifiers(methodPath) {
return modifiers;
}
-function getMethodName(methodPath) {
+function getMethodName(methodPath, importer) {
if (
t.AssignmentExpression.check(methodPath.node) &&
t.MemberExpression.check(methodPath.node.left)
@@ -134,7 +133,7 @@ function getMethodName(methodPath) {
}
return null;
}
- return getPropertyName(methodPath);
+ return getPropertyName(methodPath, importer);
}
function getMethodAccessibility(methodPath) {
@@ -167,19 +166,20 @@ function getMethodDocblock(methodPath) {
// or as assignment expresions of the form `Component.foo = function() {}`
export default function getMethodDocumentation(
methodPath: NodePath,
+ importer: Importer,
): ?MethodDocumentation {
if (getMethodAccessibility(methodPath) === 'private') {
return null;
}
- const name = getMethodName(methodPath);
+ const name = getMethodName(methodPath, importer);
if (!name) return null;
return {
name,
docblock: getMethodDocblock(methodPath),
modifiers: getMethodModifiers(methodPath),
- params: getMethodParamsDoc(methodPath),
- returns: getMethodReturnDoc(methodPath),
+ params: getMethodParamsDoc(methodPath, importer),
+ returns: getMethodReturnDoc(methodPath, importer),
};
}
diff --git a/src/utils/getPropType.js b/src/utils/getPropType.js
index febf24e65f4..81fcf17eee0 100644
--- a/src/utils/getPropType.js
+++ b/src/utils/getPropType.js
@@ -18,18 +18,18 @@ import printValue from './printValue';
import resolveToValue from './resolveToValue';
import resolveObjectKeysToArray from './resolveObjectKeysToArray';
import resolveObjectValuesToArray from './resolveObjectValuesToArray';
-import type { PropTypeDescriptor, PropDescriptor } from '../types';
+import type { PropTypeDescriptor, PropDescriptor, Importer } from '../types';
-function getEnumValues(path) {
+function getEnumValues(path: NodePath, importer: Importer) {
const values = [];
path.get('elements').each(function(elementPath) {
if (t.SpreadElement.check(elementPath.node)) {
- const value = resolveToValue(elementPath.get('argument'));
+ const value = resolveToValue(elementPath.get('argument'), importer);
if (t.ArrayExpression.check(value.node)) {
// if the SpreadElement resolved to an Array, add all their elements too
- return values.push(...getEnumValues(value));
+ return values.push(...getEnumValues(value, importer));
} else {
// otherwise we'll just print the SpreadElement itself
return values.push({
@@ -40,7 +40,7 @@ function getEnumValues(path) {
}
// try to resolve the array element to it's value
- const value = resolveToValue(elementPath);
+ const value = resolveToValue(elementPath, importer);
return values.push({
value: printValue(value),
computed: !t.Literal.check(value.node),
@@ -50,33 +50,34 @@ function getEnumValues(path) {
return values;
}
-function getPropTypeOneOf(argumentPath) {
+function getPropTypeOneOf(argumentPath: NodePath, importer: Importer) {
const type: PropTypeDescriptor = { name: 'enum' };
- let value = resolveToValue(argumentPath);
+ let value = resolveToValue(argumentPath, importer);
if (!t.ArrayExpression.check(value.node)) {
value =
- resolveObjectKeysToArray(value) || resolveObjectValuesToArray(value);
+ resolveObjectKeysToArray(value, importer) ||
+ resolveObjectValuesToArray(value, importer);
if (value) {
- type.value = getEnumValues(value);
+ type.value = getEnumValues(value, importer);
} else {
// could not easily resolve to an Array, let's print the original value
type.computed = true;
type.value = printValue(argumentPath);
}
} else {
- type.value = getEnumValues(value);
+ type.value = getEnumValues(value, importer);
}
return type;
}
-function getPropTypeOneOfType(argumentPath) {
+function getPropTypeOneOfType(argumentPath: NodePath, importer: Importer) {
const type: PropTypeDescriptor = { name: 'union' };
if (!t.ArrayExpression.check(argumentPath.node)) {
type.computed = true;
type.value = printValue(argumentPath);
} else {
type.value = argumentPath.get('elements').map(function(itemPath) {
- const descriptor: PropTypeDescriptor = getPropType(itemPath);
+ const descriptor: PropTypeDescriptor = getPropType(itemPath, importer);
const docs = getDocblock(itemPath);
if (docs) {
descriptor.description = docs;
@@ -87,7 +88,7 @@ function getPropTypeOneOfType(argumentPath) {
return type;
}
-function getPropTypeArrayOf(argumentPath) {
+function getPropTypeArrayOf(argumentPath: NodePath, importer: Importer) {
const type: PropTypeDescriptor = { name: 'arrayOf' };
const docs = getDocblock(argumentPath);
@@ -95,7 +96,7 @@ function getPropTypeArrayOf(argumentPath) {
type.description = docs;
}
- const subType = getPropType(argumentPath);
+ const subType = getPropType(argumentPath, importer);
if (subType.name === 'unknown') {
type.value = printValue(argumentPath);
@@ -106,7 +107,7 @@ function getPropTypeArrayOf(argumentPath) {
return type;
}
-function getPropTypeObjectOf(argumentPath) {
+function getPropTypeObjectOf(argumentPath: NodePath, importer: Importer) {
const type: PropTypeDescriptor = { name: 'objectOf' };
const docs = getDocblock(argumentPath);
@@ -114,7 +115,7 @@ function getPropTypeObjectOf(argumentPath) {
type.description = docs;
}
- const subType = getPropType(argumentPath);
+ const subType = getPropType(argumentPath, importer);
if (subType.name === 'unknown') {
type.value = printValue(argumentPath);
@@ -128,10 +129,14 @@ function getPropTypeObjectOf(argumentPath) {
/**
* Handles shape and exact prop types
*/
-function getPropTypeShapish(name, argumentPath) {
+function getPropTypeShapish(
+ name: 'shape' | 'exact',
+ argumentPath: NodePath,
+ importer: Importer,
+) {
const type: PropTypeDescriptor = { name };
if (!t.ObjectExpression.check(argumentPath.node)) {
- argumentPath = resolveToValue(argumentPath);
+ argumentPath = resolveToValue(argumentPath, importer);
}
if (t.ObjectExpression.check(argumentPath.node)) {
@@ -142,10 +147,11 @@ function getPropTypeShapish(name, argumentPath) {
return;
}
- const propertyName = getPropertyName(propertyPath);
+ const propertyName = getPropertyName(propertyPath, importer);
if (!propertyName) return;
const descriptor: PropDescriptor | PropTypeDescriptor = getPropType(
propertyPath.get('value'),
+ importer,
);
const docs = getDocblock(propertyPath);
if (docs) {
@@ -165,7 +171,8 @@ function getPropTypeShapish(name, argumentPath) {
return type;
}
-function getPropTypeInstanceOf(argumentPath) {
+// eslint-disable-next-line no-unused-vars
+function getPropTypeInstanceOf(argumentPath: NodePath, importer: Importer) {
return {
name: 'instanceOf',
value: printValue(argumentPath),
@@ -204,7 +211,10 @@ const propTypes = new Map([
*
* If there is no match, "custom" is returned.
*/
-export default function getPropType(path: NodePath): PropTypeDescriptor {
+export default function getPropType(
+ path: NodePath,
+ importer: Importer,
+): PropTypeDescriptor {
let descriptor;
getMembers(path, true).some(member => {
const node = member.path.node;
@@ -220,7 +230,7 @@ export default function getPropType(path: NodePath): PropTypeDescriptor {
return true;
} else if (propTypes.has(name) && member.argumentsPath) {
// $FlowIssue
- descriptor = propTypes.get(name)(member.argumentsPath.get(0));
+ descriptor = propTypes.get(name)(member.argumentsPath.get(0), importer);
return true;
}
}
diff --git a/src/utils/getPropertyName.js b/src/utils/getPropertyName.js
index 70aed906df2..d067cc4537b 100644
--- a/src/utils/getPropertyName.js
+++ b/src/utils/getPropertyName.js
@@ -10,6 +10,7 @@
import { namedTypes as t } from 'ast-types';
import getNameOrValue from './getNameOrValue';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
export const COMPUTED_PREFIX = '@computed#';
@@ -18,7 +19,10 @@ export const COMPUTED_PREFIX = '@computed#';
* or a literal (or dynamic, but we don't support those). This function simply
* returns the value of the literal or name of the identifier.
*/
-export default function getPropertyName(propertyPath: NodePath): ?string {
+export default function getPropertyName(
+ propertyPath: NodePath,
+ importer: Importer,
+): ?string {
if (t.ObjectTypeSpreadProperty.check(propertyPath.node)) {
return getNameOrValue(propertyPath.get('argument').get('id'), false);
} else if (propertyPath.node.computed) {
@@ -26,7 +30,7 @@ export default function getPropertyName(propertyPath: NodePath): ?string {
// Try to resolve variables and member expressions
if (t.Identifier.check(key.node) || t.MemberExpression.check(key.node)) {
- const value = resolveToValue(key).node;
+ const value = resolveToValue(key, importer).node;
if (
t.Literal.check(value) &&
diff --git a/src/utils/getPropertyValuePath.js b/src/utils/getPropertyValuePath.js
index 5df8cb77a46..9fd560850da 100644
--- a/src/utils/getPropertyValuePath.js
+++ b/src/utils/getPropertyValuePath.js
@@ -9,6 +9,7 @@
import { namedTypes as t } from 'ast-types';
import getPropertyName from './getPropertyName';
+import type { Importer } from '../types';
/**
* Given an ObjectExpression, this function returns the path of the value of
@@ -17,11 +18,14 @@ import getPropertyName from './getPropertyName';
export default function getPropertyValuePath(
path: NodePath,
propertyName: string,
+ importer: Importer,
): ?NodePath {
t.ObjectExpression.assert(path.node);
return path
.get('properties')
- .filter(propertyPath => getPropertyName(propertyPath) === propertyName)
+ .filter(
+ propertyPath => getPropertyName(propertyPath, importer) === propertyName,
+ )
.map(propertyPath => propertyPath.get('value'))[0];
}
diff --git a/src/utils/getTSType.js b/src/utils/getTSType.js
index 83f9117e56d..a6fdf5f7d13 100644
--- a/src/utils/getTSType.js
+++ b/src/utils/getTSType.js
@@ -24,6 +24,7 @@ import type {
FlowSimpleType,
FlowTypeDescriptor,
TSFunctionSignatureType,
+ Importer,
} from '../types';
const tsTypes = {
@@ -59,10 +60,13 @@ const namedTypes = {
function handleTSArrayType(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowElementsType {
return {
name: 'Array',
- elements: [getTSTypeWithResolvedTypes(path.get('elementType'), typeParams)],
+ elements: [
+ getTSTypeWithResolvedTypes(path.get('elementType'), typeParams, importer),
+ ],
raw: printValue(path),
};
}
@@ -70,6 +74,7 @@ function handleTSArrayType(
function handleTSTypeReference(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): ?FlowTypeDescriptor {
let type: FlowTypeDescriptor;
if (t.TSQualifiedName.check(path.node.typeName)) {
@@ -89,24 +94,27 @@ function handleTSTypeReference(
const resolvedPath =
(typeParams && typeParams[type.name]) ||
- resolveToValue(path.get('typeName'));
+ resolveToValue(path.get('typeName'), importer);
if (path.node.typeParameters && resolvedPath.node.typeParameters) {
typeParams = getTypeParameters(
resolvedPath.get('typeParameters'),
path.get('typeParameters'),
typeParams,
+ importer,
);
}
if (typeParams && typeParams[type.name]) {
- type = getTSTypeWithResolvedTypes(resolvedPath);
+ // Open question: Why is this `null` instead of `typeParams`
+ type = getTSTypeWithResolvedTypes(resolvedPath, null, importer);
}
if (resolvedPath && resolvedPath.node.typeAnnotation) {
type = getTSTypeWithResolvedTypes(
resolvedPath.get('typeAnnotation'),
typeParams,
+ importer,
);
} else if (path.node.typeParameters) {
const params = path.get('typeParameters').get('params');
@@ -114,7 +122,7 @@ function handleTSTypeReference(
type = {
...type,
elements: params.map(param =>
- getTSTypeWithResolvedTypes(param, typeParams),
+ getTSTypeWithResolvedTypes(param, typeParams, importer),
),
raw: printValue(path),
};
@@ -126,8 +134,9 @@ function handleTSTypeReference(
function getTSTypeWithRequirements(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
- const type = getTSTypeWithResolvedTypes(path, typeParams);
+ const type = getTSTypeWithResolvedTypes(path, typeParams, importer);
type.required = !path.parentPath.node.optional;
return type;
}
@@ -135,6 +144,7 @@ function getTSTypeWithRequirements(
function handleTSTypeLiteral(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
const type: FlowObjectSignatureType = {
name: 'signature',
@@ -148,7 +158,7 @@ function handleTSTypeLiteral(
t.TSPropertySignature.check(param.node) ||
t.TSMethodSignature.check(param.node)
) {
- const propName = getPropertyName(param);
+ const propName = getPropertyName(param, importer);
if (!propName) {
return;
}
@@ -157,10 +167,15 @@ function handleTSTypeLiteral(
value: getTSTypeWithRequirements(
param.get('typeAnnotation'),
typeParams,
+ importer,
),
});
} else if (t.TSCallSignatureDeclaration.check(param.node)) {
- type.signature.constructor = handleTSFunctionType(param, typeParams);
+ type.signature.constructor = handleTSFunctionType(
+ param,
+ typeParams,
+ importer,
+ );
} else if (t.TSIndexSignature.check(param.node)) {
type.signature.properties.push({
key: getTSTypeWithResolvedTypes(
@@ -169,10 +184,12 @@ function handleTSTypeLiteral(
.get(0)
.get('typeAnnotation'),
typeParams,
+ importer,
),
value: getTSTypeWithRequirements(
param.get('typeAnnotation'),
typeParams,
+ importer,
),
});
}
@@ -192,36 +209,44 @@ function handleTSInterfaceDeclaration(path: NodePath): FlowSimpleType {
function handleTSUnionType(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowElementsType {
return {
name: 'union',
raw: printValue(path),
elements: path
.get('types')
- .map(subType => getTSTypeWithResolvedTypes(subType, typeParams)),
+ .map(subType =>
+ getTSTypeWithResolvedTypes(subType, typeParams, importer),
+ ),
};
}
function handleTSIntersectionType(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowElementsType {
return {
name: 'intersection',
raw: printValue(path),
elements: path
.get('types')
- .map(subType => getTSTypeWithResolvedTypes(subType, typeParams)),
+ .map(subType =>
+ getTSTypeWithResolvedTypes(subType, typeParams, importer),
+ ),
};
}
function handleTSMappedType(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowObjectSignatureType {
const key = getTSTypeWithResolvedTypes(
path.get('typeParameter').get('constraint'),
typeParams,
+ importer,
);
key.required = !path.node.optional;
@@ -236,6 +261,7 @@ function handleTSMappedType(
value: getTSTypeWithResolvedTypes(
path.get('typeAnnotation'),
typeParams,
+ importer,
),
},
],
@@ -246,6 +272,7 @@ function handleTSMappedType(
function handleTSFunctionType(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): TSFunctionSignatureType {
const type: TSFunctionSignatureType = {
name: 'signature',
@@ -256,6 +283,7 @@ function handleTSFunctionType(
return: getTSTypeWithResolvedTypes(
path.get('typeAnnotation'),
typeParams,
+ importer,
),
},
};
@@ -265,7 +293,7 @@ function handleTSFunctionType(
const arg: FlowFunctionArgumentType = {
name: param.node.name || '',
type: typeAnnotation
- ? getTSTypeWithResolvedTypes(typeAnnotation, typeParams)
+ ? getTSTypeWithResolvedTypes(typeAnnotation, typeParams, importer)
: undefined,
};
@@ -288,6 +316,7 @@ function handleTSFunctionType(
function handleTSTupleType(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowElementsType {
const type: FlowElementsType = {
name: 'tuple',
@@ -296,7 +325,7 @@ function handleTSTupleType(
};
path.get('elementTypes').each(param => {
- type.elements.push(getTSTypeWithResolvedTypes(param, typeParams));
+ type.elements.push(getTSTypeWithResolvedTypes(param, typeParams, importer));
});
return type;
@@ -305,19 +334,25 @@ function handleTSTupleType(
function handleTSTypeQuery(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
- const resolvedPath = resolveToValue(path.get('exprName'));
+ const resolvedPath = resolveToValue(path.get('exprName'), importer);
if (resolvedPath && resolvedPath.node.typeAnnotation) {
return getTSTypeWithResolvedTypes(
resolvedPath.get('typeAnnotation'),
typeParams,
+ importer,
);
}
return { name: path.node.exprName.name };
}
-function handleTSTypeOperator(path: NodePath): ?FlowTypeDescriptor {
+function handleTSTypeOperator(
+ path: NodePath,
+ typeParams: ?TypeParameters,
+ importer: Importer,
+): ?FlowTypeDescriptor {
if (path.node.operator !== 'keyof') {
return null;
}
@@ -329,13 +364,13 @@ function handleTSTypeOperator(path: NodePath): ?FlowTypeDescriptor {
value = value.get('id');
}
- const resolvedPath = resolveToValue(value);
+ const resolvedPath = resolveToValue(value, importer);
if (
resolvedPath &&
(t.ObjectExpression.check(resolvedPath.node) ||
t.TSTypeLiteral.check(resolvedPath.node))
) {
- const keys = resolveObjectToNameArray(resolvedPath, true);
+ const keys = resolveObjectToNameArray(resolvedPath, importer, true);
if (keys) {
return {
@@ -350,16 +385,19 @@ function handleTSTypeOperator(path: NodePath): ?FlowTypeDescriptor {
function handleTSIndexedAccessType(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowSimpleType {
// eslint-disable-next-line no-undef
const objectType: $Shape = getTSTypeWithResolvedTypes(
path.get('objectType'),
typeParams,
+ importer,
);
// eslint-disable-next-line no-undef
const indexType: $Shape = getTSTypeWithResolvedTypes(
path.get('indexType'),
typeParams,
+ importer,
);
// We only get the signature if the objectType is a type (vs interface)
@@ -386,6 +424,7 @@ let visitedTypes = {};
function getTSTypeWithResolvedTypes(
path: NodePath,
typeParams: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
if (t.TSTypeAnnotation.check(path.node)) {
path = path.get('typeAnnotation');
@@ -418,7 +457,7 @@ function getTSTypeWithResolvedTypes(
value: node.literal.raw || `${node.literal.value}`,
};
} else if (node.type in namedTypes) {
- type = namedTypes[node.type](path, typeParams);
+ type = namedTypes[node.type](path, typeParams, importer);
}
if (!type) {
@@ -443,12 +482,13 @@ function getTSTypeWithResolvedTypes(
export default function getTSType(
path: NodePath,
typeParamMap: ?TypeParameters,
+ importer: Importer,
): FlowTypeDescriptor {
// Empty visited types before an after run
// Before: in case the detection threw and we rerun again
// After: cleanup memory after we are done here
visitedTypes = {};
- const type = getTSTypeWithResolvedTypes(path, typeParamMap);
+ const type = getTSTypeWithResolvedTypes(path, typeParamMap, importer);
visitedTypes = {};
return type;
diff --git a/src/utils/getTypeParameters.js b/src/utils/getTypeParameters.js
index 601df07c02e..96a2ff5bdce 100644
--- a/src/utils/getTypeParameters.js
+++ b/src/utils/getTypeParameters.js
@@ -8,6 +8,7 @@
*/
import resolveGenericTypeAnnotation from '../utils/resolveGenericTypeAnnotation';
+import type { Importer } from '../types';
export type TypeParameters = {
[string]: NodePath,
@@ -17,6 +18,7 @@ export default function getTypeParameters(
declaration: NodePath,
instantiation: NodePath,
inputParams: ?TypeParameters,
+ importer: Importer,
): TypeParameters {
const params = {};
const numInstantiationParams = instantiation.node.params.length;
@@ -33,7 +35,8 @@ export default function getTypeParameters(
: defaultTypePath;
if (typePath) {
- let resolvedTypePath = resolveGenericTypeAnnotation(typePath) || typePath;
+ let resolvedTypePath =
+ resolveGenericTypeAnnotation(typePath, importer) || typePath;
const typeName =
resolvedTypePath.node.typeName || resolvedTypePath.node.id;
if (typeName && inputParams && inputParams[typeName.name]) {
diff --git a/src/utils/isExportsOrModuleAssignment.js b/src/utils/isExportsOrModuleAssignment.js
index b77017118ef..1950e77788a 100644
--- a/src/utils/isExportsOrModuleAssignment.js
+++ b/src/utils/isExportsOrModuleAssignment.js
@@ -9,12 +9,16 @@
import { namedTypes as t } from 'ast-types';
import * as expressionTo from './expressionTo';
+import type { Importer } from '../types';
/**
* Returns true if the expression is of form `exports.foo = ...;` or
* `modules.exports = ...;`.
*/
-export default function isExportsOrModuleAssignment(path: NodePath): boolean {
+export default function isExportsOrModuleAssignment(
+ path: NodePath,
+ importer: Importer,
+): boolean {
if (t.ExpressionStatement.check(path.node)) {
path = path.get('expression');
}
@@ -25,7 +29,7 @@ export default function isExportsOrModuleAssignment(path: NodePath): boolean {
return false;
}
- const exprArr = expressionTo.Array(path.get('left'));
+ const exprArr = expressionTo.Array(path.get('left'), importer);
return (
(exprArr[0] === 'module' && exprArr[1] === 'exports') ||
exprArr[0] === 'exports'
diff --git a/src/utils/isReactBuiltinCall.js b/src/utils/isReactBuiltinCall.js
index 2cd492f808a..dab84ffabff 100644
--- a/src/utils/isReactBuiltinCall.js
+++ b/src/utils/isReactBuiltinCall.js
@@ -12,6 +12,7 @@ import isReactModuleName from './isReactModuleName';
import match from './match';
import resolveToModule from './resolveToModule';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
/**
* Returns true if the expression is a function call of the form
@@ -20,18 +21,19 @@ import resolveToValue from './resolveToValue';
export default function isReactBuiltinCall(
path: NodePath,
name: string,
+ importer: Importer,
): boolean {
if (t.ExpressionStatement.check(path.node)) {
path = path.get('expression');
}
if (match(path.node, { callee: { property: { name } } })) {
- const module = resolveToModule(path.get('callee', 'object'));
+ const module = resolveToModule(path.get('callee', 'object'), importer);
return Boolean(module && isReactModuleName(module));
}
if (t.CallExpression.check(path.node)) {
- const value = resolveToValue(path.get('callee'));
+ const value = resolveToValue(path.get('callee'), importer);
if (value === path.get('callee')) return false;
if (
@@ -45,7 +47,7 @@ export default function isReactBuiltinCall(
specifier => specifier.imported && specifier.imported.name === name,
))
) {
- const module = resolveToModule(value);
+ const module = resolveToModule(value, importer);
return Boolean(module && isReactModuleName(module));
}
}
diff --git a/src/utils/isReactChildrenElementCall.js b/src/utils/isReactChildrenElementCall.js
index 1ed9f6ea74e..ce5d7ef9063 100644
--- a/src/utils/isReactChildrenElementCall.js
+++ b/src/utils/isReactChildrenElementCall.js
@@ -11,12 +11,16 @@ import { namedTypes as t } from 'ast-types';
import isReactModuleName from './isReactModuleName';
import match from './match';
import resolveToModule from './resolveToModule';
+import type { Importer } from '../types';
/**
* Returns true if the expression is a function call of the form
* `React.Children.only(...)`.
*/
-export default function isReactChildrenElementCall(path: NodePath): boolean {
+export default function isReactChildrenElementCall(
+ path: NodePath,
+ importer: Importer,
+): boolean {
if (t.ExpressionStatement.check(path.node)) {
path = path.get('expression');
}
@@ -26,7 +30,7 @@ export default function isReactChildrenElementCall(path: NodePath): boolean {
}
const calleeObj = path.get('callee', 'object');
- const module = resolveToModule(calleeObj);
+ const module = resolveToModule(calleeObj, importer);
if (!match(calleeObj, { value: { property: { name: 'Children' } } })) {
return false;
diff --git a/src/utils/isReactCloneElementCall.js b/src/utils/isReactCloneElementCall.js
index eb8e06aae2a..459a48d7428 100644
--- a/src/utils/isReactCloneElementCall.js
+++ b/src/utils/isReactCloneElementCall.js
@@ -8,11 +8,15 @@
*/
import isReactBuiltinCall from './isReactBuiltinCall';
+import type { Importer } from '../types';
/**
* Returns true if the expression is a function call of the form
* `React.cloneElement(...)`.
*/
-export default function isReactCloneElementCall(path: NodePath): boolean {
- return isReactBuiltinCall(path, 'cloneElement');
+export default function isReactCloneElementCall(
+ path: NodePath,
+ importer: Importer,
+): boolean {
+ return isReactBuiltinCall(path, 'cloneElement', importer);
}
diff --git a/src/utils/isReactComponentClass.js b/src/utils/isReactComponentClass.js
index 6bf3c682a14..d7a65da7ea9 100644
--- a/src/utils/isReactComponentClass.js
+++ b/src/utils/isReactComponentClass.js
@@ -12,6 +12,7 @@ import isReactModuleName from './isReactModuleName';
import match from './match';
import resolveToModule from './resolveToModule';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
function isRenderMethod(node) {
const isProperty = node.type === 'ClassProperty';
@@ -28,7 +29,10 @@ function isRenderMethod(node) {
* Returns `true` of the path represents a class definition which either extends
* `React.Component` or has a superclass and implements a `render()` method.
*/
-export default function isReactComponentClass(path: NodePath): boolean {
+export default function isReactComponentClass(
+ path: NodePath,
+ importer: Importer,
+): boolean {
const node = path.node;
if (!t.ClassDeclaration.check(node) && !t.ClassExpression.check(node)) {
return false;
@@ -40,12 +44,12 @@ export default function isReactComponentClass(path: NodePath): boolean {
}
// React.Component or React.PureComponent
- const superClass = resolveToValue(path.get('superClass'));
+ const superClass = resolveToValue(path.get('superClass'), importer);
if (
match(superClass.node, { property: { name: 'Component' } }) ||
match(superClass.node, { property: { name: 'PureComponent' } })
) {
- const module = resolveToModule(superClass);
+ const module = resolveToModule(superClass, importer);
if (module && isReactModuleName(module)) {
return true;
}
diff --git a/src/utils/isReactComponentMethod.js b/src/utils/isReactComponentMethod.js
index d568f523a10..c17c74f1d77 100644
--- a/src/utils/isReactComponentMethod.js
+++ b/src/utils/isReactComponentMethod.js
@@ -9,6 +9,7 @@
import { namedTypes as t } from 'ast-types';
import getPropertyName from './getPropertyName';
+import type { Importer } from '../types';
const componentMethods = [
'componentDidMount',
@@ -35,7 +36,7 @@ const componentMethods = [
/**
* Returns if the method path is a Component method.
*/
-export default function(methodPath: NodePath): boolean {
+export default function(methodPath: NodePath, importer: Importer): boolean {
if (
!t.MethodDefinition.check(methodPath.node) &&
!t.Property.check(methodPath.node)
@@ -43,6 +44,6 @@ export default function(methodPath: NodePath): boolean {
return false;
}
- const name = getPropertyName(methodPath);
+ const name = getPropertyName(methodPath, importer);
return !!name && componentMethods.indexOf(name) !== -1;
}
diff --git a/src/utils/isReactCreateClassCall.js b/src/utils/isReactCreateClassCall.js
index 1bdabce18c6..bbb77f943c2 100644
--- a/src/utils/isReactCreateClassCall.js
+++ b/src/utils/isReactCreateClassCall.js
@@ -11,6 +11,7 @@ import { namedTypes as t } from 'ast-types';
import match from './match';
import resolveToModule from './resolveToModule';
import isReactBuiltinCall from './isReactBuiltinCall';
+import type { Importer } from '../types';
/**
* Returns true if the expression is a function call of the form
@@ -19,7 +20,10 @@ import isReactBuiltinCall from './isReactBuiltinCall';
* createReactClass(...);
* ```
*/
-function isReactCreateClassCallModular(path: NodePath): boolean {
+function isReactCreateClassCallModular(
+ path: NodePath,
+ importer: Importer,
+): boolean {
if (t.ExpressionStatement.check(path.node)) {
path = path.get('expression');
}
@@ -27,7 +31,7 @@ function isReactCreateClassCallModular(path: NodePath): boolean {
if (!match(path.node, { type: 'CallExpression' })) {
return false;
}
- const module = resolveToModule(path);
+ const module = resolveToModule(path, importer);
return Boolean(module && module === 'create-react-class');
}
@@ -39,9 +43,12 @@ function isReactCreateClassCallModular(path: NodePath): boolean {
* createReactClass(...);
* ```
*/
-export default function isReactCreateClassCall(path: NodePath): boolean {
+export default function isReactCreateClassCall(
+ path: NodePath,
+ importer: Importer,
+): boolean {
return (
- isReactBuiltinCall(path, 'createClass') ||
- isReactCreateClassCallModular(path)
+ isReactBuiltinCall(path, 'createClass', importer) ||
+ isReactCreateClassCallModular(path, importer)
);
}
diff --git a/src/utils/isReactCreateElementCall.js b/src/utils/isReactCreateElementCall.js
index 2b1ff9079a8..419b42867d5 100644
--- a/src/utils/isReactCreateElementCall.js
+++ b/src/utils/isReactCreateElementCall.js
@@ -8,11 +8,15 @@
*/
import isReactBuiltinCall from './isReactBuiltinCall';
+import type { Importer } from '../types';
/**
* Returns true if the expression is a function call of the form
* `React.createElement(...)`.
*/
-export default function isReactCreateElementCall(path: NodePath): boolean {
- return isReactBuiltinCall(path, 'createElement');
+export default function isReactCreateElementCall(
+ path: NodePath,
+ importer: Importer,
+): boolean {
+ return isReactBuiltinCall(path, 'createElement', importer);
}
diff --git a/src/utils/isReactForwardRefCall.js b/src/utils/isReactForwardRefCall.js
index 90d25eb78c5..e680fd56e07 100644
--- a/src/utils/isReactForwardRefCall.js
+++ b/src/utils/isReactForwardRefCall.js
@@ -7,11 +7,15 @@
* @flow
*/
import isReactBuiltinCall from './isReactBuiltinCall';
+import type { Importer } from '../types';
/**
* Returns true if the expression is a function call of the form
* `React.forwardRef(...)`.
*/
-export default function isReactForwardRefCall(path: NodePath): boolean {
- return isReactBuiltinCall(path, 'forwardRef');
+export default function isReactForwardRefCall(
+ path: NodePath,
+ importer: Importer,
+): boolean {
+ return isReactBuiltinCall(path, 'forwardRef', importer);
}
diff --git a/src/utils/isStatelessComponent.js b/src/utils/isStatelessComponent.js
index a912c6ed4ba..d0559b852ce 100644
--- a/src/utils/isStatelessComponent.js
+++ b/src/utils/isStatelessComponent.js
@@ -15,6 +15,7 @@ import isReactCreateElementCall from './isReactCreateElementCall';
import isReactCloneElementCall from './isReactCloneElementCall';
import isReactChildrenElementCall from './isReactChildrenElementCall';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
const validPossibleStatelessComponentTypes = [
'Property',
@@ -23,17 +24,20 @@ const validPossibleStatelessComponentTypes = [
'ArrowFunctionExpression',
];
-function isJSXElementOrReactCall(path) {
+function isJSXElementOrReactCall(path, importer: Importer) {
return (
path.node.type === 'JSXElement' ||
path.node.type === 'JSXFragment' ||
- (path.node.type === 'CallExpression' && isReactCreateElementCall(path)) ||
- (path.node.type === 'CallExpression' && isReactCloneElementCall(path)) ||
- (path.node.type === 'CallExpression' && isReactChildrenElementCall(path))
+ (path.node.type === 'CallExpression' &&
+ isReactCreateElementCall(path, importer)) ||
+ (path.node.type === 'CallExpression' &&
+ isReactCloneElementCall(path, importer)) ||
+ (path.node.type === 'CallExpression' &&
+ isReactChildrenElementCall(path, importer))
);
}
-function resolvesToJSXElementOrReactCall(path, seen) {
+function resolvesToJSXElementOrReactCall(path, importer: Importer, seen) {
// avoid returns with recursive function calls
if (seen.has(path)) {
return false;
@@ -42,18 +46,26 @@ function resolvesToJSXElementOrReactCall(path, seen) {
seen.add(path);
// Is the path is already a JSX element or a call to one of the React.* functions
- if (isJSXElementOrReactCall(path)) {
+ if (isJSXElementOrReactCall(path, importer)) {
return true;
}
- const resolvedPath = resolveToValue(path);
+ const resolvedPath = resolveToValue(path, importer);
// If the path points to a conditional expression, then we need to look only at
// the two possible paths
if (resolvedPath.node.type === 'ConditionalExpression') {
return (
- resolvesToJSXElementOrReactCall(resolvedPath.get('consequent'), seen) ||
- resolvesToJSXElementOrReactCall(resolvedPath.get('alternate'), seen)
+ resolvesToJSXElementOrReactCall(
+ resolvedPath.get('consequent'),
+ importer,
+ seen,
+ ) ||
+ resolvesToJSXElementOrReactCall(
+ resolvedPath.get('alternate'),
+ importer,
+ seen,
+ )
);
}
@@ -61,22 +73,29 @@ function resolvesToJSXElementOrReactCall(path, seen) {
// the two possible paths
if (resolvedPath.node.type === 'LogicalExpression') {
return (
- resolvesToJSXElementOrReactCall(resolvedPath.get('left'), seen) ||
- resolvesToJSXElementOrReactCall(resolvedPath.get('right'), seen)
+ resolvesToJSXElementOrReactCall(
+ resolvedPath.get('left'),
+ importer,
+ seen,
+ ) ||
+ resolvesToJSXElementOrReactCall(resolvedPath.get('right'), importer, seen)
);
}
// Is the resolved path is already a JSX element or a call to one of the React.* functions
// Only do this if the resolvedPath actually resolved something as otherwise we did this check already
- if (resolvedPath !== path && isJSXElementOrReactCall(resolvedPath)) {
+ if (
+ resolvedPath !== path &&
+ isJSXElementOrReactCall(resolvedPath, importer)
+ ) {
return true;
}
// If we have a call expression, lets try to follow it
if (resolvedPath.node.type === 'CallExpression') {
- let calleeValue = resolveToValue(resolvedPath.get('callee'));
+ let calleeValue = resolveToValue(resolvedPath.get('callee'), importer);
- if (returnsJSXElementOrReactCall(calleeValue, seen)) {
+ if (returnsJSXElementOrReactCall(calleeValue, importer, seen)) {
return true;
}
@@ -86,14 +105,14 @@ function resolvesToJSXElementOrReactCall(path, seen) {
if (calleeValue.node.type === 'MemberExpression') {
if (calleeValue.get('object').node.type === 'Identifier') {
- resolvedValue = resolveToValue(calleeValue.get('object'));
+ resolvedValue = resolveToValue(calleeValue.get('object'), importer);
} else if (t.MemberExpression.check(calleeValue.node)) {
do {
calleeValue = calleeValue.get('object');
namesToResolve.unshift(calleeValue.get('property'));
} while (t.MemberExpression.check(calleeValue.node));
- resolvedValue = resolveToValue(calleeValue.get('object'));
+ resolvedValue = resolveToValue(calleeValue.get('object'), importer);
}
}
@@ -105,9 +124,9 @@ function resolvesToJSXElementOrReactCall(path, seen) {
}
if (result) {
- result = getPropertyValuePath(result, nodePath.node.name);
+ result = getPropertyValuePath(result, nodePath.node.name, importer);
if (result && t.Identifier.check(result.node)) {
- return resolveToValue(result);
+ return resolveToValue(result, importer);
}
}
return result;
@@ -117,7 +136,7 @@ function resolvesToJSXElementOrReactCall(path, seen) {
if (
!resolvedMemberExpression ||
- returnsJSXElementOrReactCall(resolvedMemberExpression, seen)
+ returnsJSXElementOrReactCall(resolvedMemberExpression, importer, seen)
) {
return true;
}
@@ -127,14 +146,18 @@ function resolvesToJSXElementOrReactCall(path, seen) {
return false;
}
-function returnsJSXElementOrReactCall(path, seen = new WeakSet()) {
+function returnsJSXElementOrReactCall(
+ path,
+ importer: Importer,
+ seen = new WeakSet(),
+) {
let visited = false;
// early exit for ArrowFunctionExpressions
if (
path.node.type === 'ArrowFunctionExpression' &&
path.get('body').node.type !== 'BlockStatement' &&
- resolvesToJSXElementOrReactCall(path.get('body'), seen)
+ resolvesToJSXElementOrReactCall(path.get('body'), importer, seen)
) {
return true;
}
@@ -150,7 +173,13 @@ function returnsJSXElementOrReactCall(path, seen = new WeakSet()) {
// Only check return statements which are part of the checked function scope
if (returnPath.scope !== scope) return false;
- if (resolvesToJSXElementOrReactCall(returnPath.get('argument'), seen)) {
+ if (
+ resolvesToJSXElementOrReactCall(
+ returnPath.get('argument'),
+ importer,
+ seen,
+ )
+ ) {
visited = true;
return false;
}
@@ -165,7 +194,10 @@ function returnsJSXElementOrReactCall(path, seen = new WeakSet()) {
/**
* Returns `true` if the path represents a function which returns a JSXElement
*/
-export default function isStatelessComponent(path: NodePath): boolean {
+export default function isStatelessComponent(
+ path: NodePath,
+ importer: Importer,
+): boolean {
const node = path.node;
if (validPossibleStatelessComponentTypes.indexOf(node.type) === -1) {
@@ -174,14 +206,14 @@ export default function isStatelessComponent(path: NodePath): boolean {
if (node.type === 'Property') {
if (
- isReactCreateClassCall(path.parent) ||
- isReactComponentClass(path.parent)
+ isReactCreateClassCall(path.parent, importer) ||
+ isReactComponentClass(path.parent, importer)
) {
return false;
}
}
- if (returnsJSXElementOrReactCall(path)) {
+ if (returnsJSXElementOrReactCall(path, importer)) {
return true;
}
diff --git a/src/utils/resolveExportDeclaration.js b/src/utils/resolveExportDeclaration.js
index cc04a3ce40b..1f1df2c87ea 100644
--- a/src/utils/resolveExportDeclaration.js
+++ b/src/utils/resolveExportDeclaration.js
@@ -9,9 +9,11 @@
import { namedTypes as t } from 'ast-types';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
export default function resolveExportDeclaration(
path: NodePath,
+ importer: Importer,
): Array {
const definitions = [];
if (path.node.default) {
@@ -33,5 +35,5 @@ export default function resolveExportDeclaration(
),
);
}
- return definitions.map(definition => resolveToValue(definition));
+ return definitions.map(definition => resolveToValue(definition, importer));
}
diff --git a/src/utils/resolveFunctionDefinitionToReturnValue.js b/src/utils/resolveFunctionDefinitionToReturnValue.js
index 87e7cbdc1e2..4867fa49927 100644
--- a/src/utils/resolveFunctionDefinitionToReturnValue.js
+++ b/src/utils/resolveFunctionDefinitionToReturnValue.js
@@ -9,16 +9,18 @@
import resolveToValue from './resolveToValue';
import { traverseShallow } from './traverse';
+import type { Importer } from '../types';
export default function resolveFunctionDefinitionToReturnValue(
path: NodePath,
+ importer: Importer,
): ?NodePath {
let returnPath = null;
traverseShallow(path.get('body'), {
visitFunction: () => false,
visitReturnStatement: nodePath => {
- returnPath = resolveToValue(nodePath.get('argument'));
+ returnPath = resolveToValue(nodePath.get('argument'), importer);
return false;
},
});
diff --git a/src/utils/resolveGenericTypeAnnotation.js b/src/utils/resolveGenericTypeAnnotation.js
index 5d2c978a2ff..749fdc5bac7 100644
--- a/src/utils/resolveGenericTypeAnnotation.js
+++ b/src/utils/resolveGenericTypeAnnotation.js
@@ -11,8 +11,12 @@ import { namedTypes as t } from 'ast-types';
import isUnreachableFlowType from '../utils/isUnreachableFlowType';
import resolveToValue from '../utils/resolveToValue';
import { unwrapUtilityType } from './flowUtilityTypes';
+import type { Importer } from '../types';
-function tryResolveGenericTypeAnnotation(path: NodePath): ?NodePath {
+function tryResolveGenericTypeAnnotation(
+ path: NodePath,
+ importer: Importer,
+): ?NodePath {
let typePath = unwrapUtilityType(path);
let idPath;
@@ -25,15 +29,18 @@ function tryResolveGenericTypeAnnotation(path: NodePath): ?NodePath {
}
if (idPath) {
- typePath = resolveToValue(idPath);
+ typePath = resolveToValue(idPath, importer);
if (isUnreachableFlowType(typePath)) {
return;
}
if (t.TypeAlias.check(typePath.node)) {
- return tryResolveGenericTypeAnnotation(typePath.get('right'));
+ return tryResolveGenericTypeAnnotation(typePath.get('right'), importer);
} else if (t.TSTypeAliasDeclaration.check(typePath.node)) {
- return tryResolveGenericTypeAnnotation(typePath.get('typeAnnotation'));
+ return tryResolveGenericTypeAnnotation(
+ typePath.get('typeAnnotation'),
+ importer,
+ );
}
return typePath;
@@ -49,10 +56,11 @@ function tryResolveGenericTypeAnnotation(path: NodePath): ?NodePath {
*/
export default function resolveGenericTypeAnnotation(
path: NodePath,
+ importer: Importer,
): ?NodePath {
if (!path) return;
- const typePath = tryResolveGenericTypeAnnotation(path);
+ const typePath = tryResolveGenericTypeAnnotation(path, importer);
if (!typePath || typePath === path) return;
diff --git a/src/utils/resolveHOC.js b/src/utils/resolveHOC.js
index e8687835bec..e19a137fe4c 100644
--- a/src/utils/resolveHOC.js
+++ b/src/utils/resolveHOC.js
@@ -11,6 +11,7 @@ import { namedTypes as t } from 'ast-types';
import isReactCreateClassCall from './isReactCreateClassCall';
import isReactForwardRefCall from './isReactForwardRefCall';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
/**
* If the path is a call expression, it recursively resolves to the
@@ -18,12 +19,15 @@ import resolveToValue from './resolveToValue';
*
* Else the path itself is returned.
*/
-export default function resolveHOC(path: NodePath): NodePath {
+export default function resolveHOC(
+ path: NodePath,
+ importer: Importer,
+): NodePath {
const node = path.node;
if (
t.CallExpression.check(node) &&
- !isReactCreateClassCall(path) &&
- !isReactForwardRefCall(path)
+ !isReactCreateClassCall(path, importer) &&
+ !isReactForwardRefCall(path, importer)
) {
if (node.arguments.length) {
const inner = path.get('arguments', 0);
@@ -38,11 +42,15 @@ export default function resolveHOC(path: NodePath): NodePath {
t.SpreadElement.check(inner.node))
) {
return resolveHOC(
- resolveToValue(path.get('arguments', node.arguments.length - 1)),
+ resolveToValue(
+ path.get('arguments', node.arguments.length - 1),
+ importer,
+ ),
+ importer,
);
}
- return resolveHOC(resolveToValue(inner));
+ return resolveHOC(resolveToValue(inner, importer), importer);
}
}
diff --git a/src/utils/resolveObjectKeysToArray.js b/src/utils/resolveObjectKeysToArray.js
index 7253f7fb414..9e1577f8d54 100644
--- a/src/utils/resolveObjectKeysToArray.js
+++ b/src/utils/resolveObjectKeysToArray.js
@@ -9,6 +9,7 @@
import { ASTNode, NodePath, builders, namedTypes as t } from 'ast-types';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
function isObjectKeysCall(node: ASTNode): boolean {
return (
@@ -42,6 +43,7 @@ function isWhiteListedObjectTypeProperty(prop) {
// Resolves an ObjectExpression or an ObjectTypeAnnotation
export function resolveObjectToNameArray(
object: NodePath,
+ importer: Importer,
raw: boolean = false,
): ?Array {
if (
@@ -74,15 +76,15 @@ export function resolveObjectToNameArray(
t.SpreadElement.check(prop) ||
t.ObjectTypeSpreadProperty.check(prop)
) {
- let spreadObject = resolveToValue(propPath.get('argument'));
+ let spreadObject = resolveToValue(propPath.get('argument'), importer);
if (t.GenericTypeAnnotation.check(spreadObject.value)) {
- const typeAlias = resolveToValue(spreadObject.get('id'));
+ const typeAlias = resolveToValue(spreadObject.get('id'), importer);
if (t.ObjectTypeAnnotation.check(typeAlias.get('right').value)) {
- spreadObject = resolveToValue(typeAlias.get('right'));
+ spreadObject = resolveToValue(typeAlias.get('right'), importer);
}
}
- const spreadValues = resolveObjectToNameArray(spreadObject);
+ const spreadValues = resolveObjectToNameArray(spreadObject, importer);
if (!spreadValues) {
error = true;
return;
@@ -108,12 +110,18 @@ export function resolveObjectToNameArray(
* unresolvable spreads
* computed identifier keys
*/
-export default function resolveObjectKeysToArray(path: NodePath): ?NodePath {
+export default function resolveObjectKeysToArray(
+ path: NodePath,
+ importer: Importer,
+): ?NodePath {
const node = path.node;
if (isObjectKeysCall(node)) {
- const objectExpression = resolveToValue(path.get('arguments').get(0));
- const values = resolveObjectToNameArray(objectExpression);
+ const objectExpression = resolveToValue(
+ path.get('arguments').get(0),
+ importer,
+ );
+ const values = resolveObjectToNameArray(objectExpression, importer);
if (values) {
const nodes = values
diff --git a/src/utils/resolveObjectValuesToArray.js b/src/utils/resolveObjectValuesToArray.js
index fa9caed7dd4..dca823ff442 100644
--- a/src/utils/resolveObjectValuesToArray.js
+++ b/src/utils/resolveObjectValuesToArray.js
@@ -9,6 +9,7 @@
import { ASTNode, NodePath, builders, namedTypes as t } from 'ast-types';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
type ObjectPropMap = {
properties: Array,
@@ -47,6 +48,7 @@ function isWhiteListedObjectTypeProperty(prop) {
// Resolves an ObjectExpression or an ObjectTypeAnnotation
export function resolveObjectToPropMap(
object: NodePath,
+ importer: Importer,
raw: boolean = false,
): ?ObjectPropMap {
if (
@@ -89,15 +91,15 @@ export function resolveObjectToPropMap(
t.SpreadElement.check(prop) ||
t.ObjectTypeSpreadProperty.check(prop)
) {
- let spreadObject = resolveToValue(propPath.get('argument'));
+ let spreadObject = resolveToValue(propPath.get('argument'), importer);
if (t.GenericTypeAnnotation.check(spreadObject.value)) {
- const typeAlias = resolveToValue(spreadObject.get('id'));
+ const typeAlias = resolveToValue(spreadObject.get('id'), importer);
if (t.ObjectTypeAnnotation.check(typeAlias.get('right').value)) {
- spreadObject = resolveToValue(typeAlias.get('right'));
+ spreadObject = resolveToValue(typeAlias.get('right'), importer);
}
}
- const spreadValues = resolveObjectToPropMap(spreadObject);
+ const spreadValues = resolveObjectToPropMap(spreadObject, importer);
if (!spreadValues) {
error = true;
return;
@@ -128,12 +130,18 @@ export function resolveObjectToPropMap(
* unresolvable spreads
* computed identifier values
*/
-export default function resolveObjectValuesToArray(path: NodePath): ?NodePath {
+export default function resolveObjectValuesToArray(
+ path: NodePath,
+ importer: Importer,
+): ?NodePath {
const node = path.node;
if (isObjectValuesCall(node)) {
- const objectExpression = resolveToValue(path.get('arguments').get(0));
- const propMap = resolveObjectToPropMap(objectExpression);
+ const objectExpression = resolveToValue(
+ path.get('arguments').get(0),
+ importer,
+ );
+ const propMap = resolveObjectToPropMap(objectExpression, importer);
if (propMap) {
const nodes = propMap.properties.map(prop => {
diff --git a/src/utils/resolveToModule.js b/src/utils/resolveToModule.js
index a43aaf72304..f6393c00e24 100644
--- a/src/utils/resolveToModule.js
+++ b/src/utils/resolveToModule.js
@@ -10,30 +10,34 @@
import { namedTypes as t } from 'ast-types';
import match from './match';
import resolveToValue from './resolveToValue';
+import type { Importer } from '../types';
/**
* Given a path (e.g. call expression, member expression or identifier),
* this function tries to find the name of module from which the "root value"
* was imported.
*/
-export default function resolveToModule(path: NodePath): ?string {
+export default function resolveToModule(
+ path: NodePath,
+ importer: Importer,
+): ?string {
const node = path.node;
switch (node.type) {
case t.VariableDeclarator.name:
if (node.init) {
- return resolveToModule(path.get('init'));
+ return resolveToModule(path.get('init'), importer);
}
break;
case t.CallExpression.name:
if (match(node.callee, { type: t.Identifier.name, name: 'require' })) {
return node.arguments[0].value;
}
- return resolveToModule(path.get('callee'));
+ return resolveToModule(path.get('callee'), importer);
case t.Identifier.name:
case t.JSXIdentifier.name: {
- const valuePath = resolveToValue(path);
+ const valuePath = resolveToValue(path, importer);
if (valuePath !== path) {
- return resolveToModule(valuePath);
+ return resolveToModule(valuePath, importer);
}
break;
}
@@ -44,7 +48,7 @@ export default function resolveToModule(path: NodePath): ?string {
path = path.get('object');
}
if (path) {
- return resolveToModule(path);
+ return resolveToModule(path, importer);
}
}
diff --git a/src/utils/resolveToValue.js b/src/utils/resolveToValue.js
index 039092c08d7..8516a06609e 100644
--- a/src/utils/resolveToValue.js
+++ b/src/utils/resolveToValue.js
@@ -12,6 +12,10 @@ import getMemberExpressionRoot from './getMemberExpressionRoot';
import getPropertyValuePath from './getPropertyValuePath';
import { Array as toArray } from './expressionTo';
import { traverseShallow } from './traverse';
+import getMemberValuePath, {
+ isSupportedDefinitionType,
+} from './getMemberValuePath';
+import type { Importer } from '../types';
function buildMemberExpressionFromPattern(path: NodePath): ?NodePath {
const node = path.node;
@@ -35,13 +39,36 @@ function buildMemberExpressionFromPattern(path: NodePath): ?NodePath {
return null;
}
-function findScopePath(paths: Array, path: NodePath): ?NodePath {
+function findScopePath(
+ paths: Array,
+ path: NodePath,
+ importer: Importer,
+): ?NodePath {
if (paths.length < 1) {
return null;
}
let resultPath = paths[0];
const parentPath = resultPath.parent;
+ // Namespace imports are handled separately, at the site of a member expression access
+ if (
+ t.ImportDefaultSpecifier.check(parentPath.node) ||
+ t.ImportSpecifier.check(parentPath.node)
+ ) {
+ let exportName;
+ if (t.ImportDefaultSpecifier.check(parentPath.node)) {
+ exportName = 'default';
+ } else {
+ exportName = parentPath.node.imported.name;
+ }
+
+ const resolvedPath = importer(parentPath.parentPath, exportName);
+
+ if (resolvedPath) {
+ return resolveToValue(resolvedPath, importer);
+ }
+ }
+
if (
t.ImportDefaultSpecifier.check(parentPath.node) ||
t.ImportSpecifier.check(parentPath.node) ||
@@ -62,7 +89,7 @@ function findScopePath(paths: Array, path: NodePath): ?NodePath {
}
if (resultPath.node !== path.node) {
- return resolveToValue(resultPath);
+ return resolveToValue(resultPath, importer);
}
return null;
@@ -72,7 +99,7 @@ function findScopePath(paths: Array, path: NodePath): ?NodePath {
* Tries to find the last value assigned to `name` in the scope created by
* `scope`. We are not descending into any statements (blocks).
*/
-function findLastAssignedValue(scope, idPath) {
+function findLastAssignedValue(scope, idPath, importer) {
const results = [];
const name = idPath.node.name;
@@ -105,7 +132,7 @@ function findLastAssignedValue(scope, idPath) {
if (results.length === 0) {
return null;
}
- return resolveToValue(results.pop());
+ return resolveToValue(results.pop(), importer);
}
/**
@@ -115,26 +142,61 @@ function findLastAssignedValue(scope, idPath) {
*
* Else the path itself is returned.
*/
-export default function resolveToValue(path: NodePath): NodePath {
+export default function resolveToValue(
+ path: NodePath,
+ importer: Importer,
+): NodePath {
const node = path.node;
if (t.VariableDeclarator.check(node)) {
if (node.init) {
- return resolveToValue(path.get('init'));
+ return resolveToValue(path.get('init'), importer);
}
} else if (t.MemberExpression.check(node)) {
- const resolved = resolveToValue(getMemberExpressionRoot(path));
+ const root = getMemberExpressionRoot(path);
+ const resolved = resolveToValue(root, importer);
if (t.ObjectExpression.check(resolved.node)) {
let propertyPath = resolved;
- for (const propertyName of toArray(path).slice(1)) {
+ for (const propertyName of toArray(path, importer).slice(1)) {
if (propertyPath && t.ObjectExpression.check(propertyPath.node)) {
- propertyPath = getPropertyValuePath(propertyPath, propertyName);
+ propertyPath = getPropertyValuePath(
+ propertyPath,
+ propertyName,
+ importer,
+ );
}
if (!propertyPath) {
return path;
}
- propertyPath = resolveToValue(propertyPath);
+ propertyPath = resolveToValue(propertyPath, importer);
}
return propertyPath;
+ } else if (isSupportedDefinitionType(resolved)) {
+ const memberPath = getMemberValuePath(
+ resolved,
+ path.node.property.name,
+ importer,
+ );
+ if (memberPath) {
+ return resolveToValue(memberPath, importer);
+ }
+ } else if (t.ImportDeclaration.check(resolved.node)) {
+ // Handle references to namespace imports, e.g. import * as foo from 'bar'.
+ // Try to find a specifier that matches the root of the member expression, and
+ // find the export that matches the property name.
+ for (const specifier of resolved.node.specifiers) {
+ if (
+ t.ImportNamespaceSpecifier.check(specifier) &&
+ specifier.local.name === root.node.name
+ ) {
+ const resolvedPath = importer(
+ resolved,
+ root.parentPath.node.property.name,
+ );
+ if (resolvedPath) {
+ return resolveToValue(resolvedPath, importer);
+ }
+ }
+ }
}
} else if (
t.ImportDefaultSpecifier.check(node) ||
@@ -145,14 +207,14 @@ export default function resolveToValue(path: NodePath): NodePath {
return path.parentPath.parentPath;
} else if (t.AssignmentExpression.check(node)) {
if (node.operator === '=') {
- return resolveToValue(path.get('right'));
+ return resolveToValue(path.get('right'), importer);
}
} else if (
t.TypeCastExpression.check(node) ||
t.TSAsExpression.check(node) ||
t.TSTypeAssertion.check(node)
) {
- return resolveToValue(path.get('expression'));
+ return resolveToValue(path.get('expression'), importer);
} else if (t.Identifier.check(node)) {
if (
(t.ClassDeclaration.check(path.parentPath.node) ||
@@ -169,16 +231,16 @@ export default function resolveToValue(path: NodePath): NodePath {
// The variable may be assigned a different value after initialization.
// We are first trying to find all assignments to the variable in the
// block where it is defined (i.e. we are not traversing into statements)
- resolvedPath = findLastAssignedValue(scope, path);
+ resolvedPath = findLastAssignedValue(scope, path, importer);
if (!resolvedPath) {
const bindings = scope.getBindings()[node.name];
- resolvedPath = findScopePath(bindings, path);
+ resolvedPath = findScopePath(bindings, path, importer);
}
} else {
scope = path.scope.lookupType(node.name);
if (scope) {
const typesInScope = scope.getTypes()[node.name];
- resolvedPath = findScopePath(typesInScope, path);
+ resolvedPath = findScopePath(typesInScope, path, importer);
}
}
return resolvedPath || path;
diff --git a/src/utils/setPropDescription.js b/src/utils/setPropDescription.js
index 5e39bfd98d0..79b90fcc412 100644
--- a/src/utils/setPropDescription.js
+++ b/src/utils/setPropDescription.js
@@ -10,9 +10,14 @@
import type Documentation from '../Documentation';
import getPropertyName from './getPropertyName';
import { getDocblock } from './docblock';
+import type { Importer } from '../types';
-export default (documentation: Documentation, propertyPath: NodePath) => {
- const propName = getPropertyName(propertyPath);
+export default (
+ documentation: Documentation,
+ propertyPath: NodePath,
+ importer: Importer,
+) => {
+ const propName = getPropertyName(propertyPath, importer);
if (!propName) return;
const propDescriptor = documentation.getPropDescriptor(propName);
diff --git a/tests/utils.js b/tests/utils.js
index 9bcd1a4bb7f..9b4119c27f9 100644
--- a/tests/utils.js
+++ b/tests/utils.js
@@ -56,3 +56,20 @@ export const MODULE_TEMPLATE = [
'var Component = React.createClass(%s);',
'module.exports = Component',
].join('\n');
+
+/**
+ * Importer that doesn't resolve any values
+ */
+export function noopImporter() {
+ return null;
+}
+
+/**
+ * Builds an importer where the keys are import paths and the values are AST nodes
+ */
+export function makeMockImporter(mocks = {}) {
+ return path => {
+ const source = path.node.source.value;
+ return mocks[source];
+ };
+}
diff --git a/yarn.lock b/yarn.lock
index fcd5e8b2f9a..3da21e69371 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3967,6 +3967,13 @@ resolve@^1.12.0, resolve@^1.3.2, resolve@^1.8.1:
dependencies:
path-parse "^1.0.6"
+resolve@^1.17.0:
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
+ integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
+ dependencies:
+ path-parse "^1.0.6"
+
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"