diff --git a/.changeset/tough-crews-share.md b/.changeset/tough-crews-share.md new file mode 100644 index 00000000..ce62441a --- /dev/null +++ b/.changeset/tough-crews-share.md @@ -0,0 +1,5 @@ +--- +'extract-react-types': patch +--- + +Adds support for React.FC and FC diff --git a/packages/extract-react-types/__snapshots__/converters-typescript.test.js.snap b/packages/extract-react-types/__snapshots__/converters-typescript.test.js.snap index dfa37ff0..506554b8 100644 --- a/packages/extract-react-types/__snapshots__/converters-typescript.test.js.snap +++ b/packages/extract-react-types/__snapshots__/converters-typescript.test.js.snap @@ -28,6 +28,69 @@ Object { } `; +exports[`TypeScript: FC 1`] = ` +Object { + "kind": "generic", + "name": Object { + "kind": "id", + "name": "Component", + "type": null, + }, + "value": Object { + "kind": "object", + "members": Array [ + Object { + "key": Object { + "kind": "id", + "name": "children", + }, + "kind": "property", + "optional": false, + "value": Object { + "kind": "string", + }, + }, + ], + "referenceIdName": "Props", + }, +} +`; + +exports[`TypeScript: FC with empty type argument 1`] = ` +Object { + "kind": "any", + "name": Object { + "kind": "id", + "name": "Component", + "type": null, + }, +} +`; + +exports[`TypeScript: FC with inline type argument 1`] = ` +Object { + "kind": "object", + "members": Array [ + Object { + "key": Object { + "kind": "id", + "name": "children", + }, + "kind": "property", + "optional": false, + "value": Object { + "kind": "string", + }, + }, + ], + "name": Object { + "kind": "id", + "name": "Component", + "type": null, + }, +} +`; + exports[`TypeScript: React.ComponentType 1`] = ` Object { "kind": "generic", diff --git a/packages/extract-react-types/converters-typescript.test.js b/packages/extract-react-types/converters-typescript.test.js index 956ecd7f..c22aa77c 100644 --- a/packages/extract-react-types/converters-typescript.test.js +++ b/packages/extract-react-types/converters-typescript.test.js @@ -65,7 +65,7 @@ const TESTS = [ ` }, { - name: 'React.FC', + name: 'FC', typeSystem: 'typescript', code: ` type Props = { @@ -78,7 +78,7 @@ const TESTS = [ ` }, { - name: 'React.FC with empty type argument', + name: 'FC with empty type argument', typeSystem: 'typescript', code: ` type Props = { @@ -91,7 +91,7 @@ const TESTS = [ ` }, { - name: 'React.FC with inline type argument', + name: 'FC with inline type argument', typeSystem: 'typescript', code: ` const Component: FC<{ @@ -101,6 +101,47 @@ const TESTS = [ export default Component; ` }, + { + name: 'React.FC', + typeSystem: 'typescript', + code: ` + import React from 'react'; + + type Props = { + children: string; + }; + + const Component: React.FC = (props) => null; + + export default Component; + ` + }, + { + name: 'React.FC with empty type argument', + typeSystem: 'typescript', + code: ` + import React from 'react'; + + type Props = { + children: string; + }; + + const Component: React.FC = (props) => null; + + export default Component; + ` + }, + { + name: 'React.FC with inline type argument', + typeSystem: 'typescript', + code: ` + const Component: React.FC<{ + children: string; + }> = (props) => null; + + export default Component; + ` + }, { name: 'Direct type assignment', typeSystem: 'typescript', diff --git a/packages/extract-react-types/src/converter.js b/packages/extract-react-types/src/converter.js index 27dff0ae..9a9d3899 100644 --- a/packages/extract-react-types/src/converter.js +++ b/packages/extract-react-types/src/converter.js @@ -15,7 +15,7 @@ import { hasDestructuredDefaultExport, matchExported } from './export-manager'; - +import { hasTypeAnnotation } from './utils'; import * as K from './kinds'; const converters = {}; @@ -1277,13 +1277,8 @@ export function convertComponentExports(componentExports, context) { ) { let propType; - // check for a component typed with the `FC` annotation - if ( - path.parentPath.node.id && - path.parentPath.node.id.typeAnnotation && - path.parentPath.node.id.typeAnnotation.typeAnnotation.typeName.name === 'FC' && - path.parentPath.node.id.typeAnnotation.typeAnnotation.typeParameters - ) { + // check for a component typed with the `React.FC` or `FC` type annotation + if (hasTypeAnnotation(path.parentPath, 'React', 'FC')) { propType = path.parentPath.get('id.typeAnnotation.typeAnnotation.typeParameters.params.0'); } else { // we have a normal function, assume the props are the first parameter diff --git a/packages/extract-react-types/src/utils.js b/packages/extract-react-types/src/utils.js new file mode 100644 index 00000000..2b500dd7 --- /dev/null +++ b/packages/extract-react-types/src/utils.js @@ -0,0 +1,20 @@ +/* eslint-disable import/prefer-default-export */ + +export function hasTypeAnnotation(path, left, right) { + if (!path.isVariableDeclarator() || !path.node.id.typeAnnotation) { + return false; + } + + const { typeName, typeParameters } = path.node.id.typeAnnotation.typeAnnotation; + + if (!typeParameters) return false; + + if ( + (typeName.left && typeName.left.name === left && typeName.right.name === right) || + ((right && typeName.name === right) || typeName.name === left) + ) { + return true; + } + + return false; +}