From 6f39bb6cc8ff67b92869990c0b3c3962dd4578c6 Mon Sep 17 00:00:00 2001 From: Chandler Prall Date: Fri, 8 Mar 2019 15:07:49 -0700 Subject: [PATCH] Allow SFC and FunctionComponent definitions to be indirected (#1707) --- .../babel/proptypes-from-ts-props/index.js | 85 +++++++++++-------- .../proptypes-from-ts-props/index.test.js | 24 ++++++ 2 files changed, 75 insertions(+), 34 deletions(-) diff --git a/scripts/babel/proptypes-from-ts-props/index.js b/scripts/babel/proptypes-from-ts-props/index.js index 0bece040087..cfe16793a0c 100644 --- a/scripts/babel/proptypes-from-ts-props/index.js +++ b/scripts/babel/proptypes-from-ts-props/index.js @@ -1159,46 +1159,63 @@ module.exports = function propTypesFromTypeScript({ types }) { // only process typescript files if (path.extname(state.file.opts.filename) !== '.ts' && path.extname(state.file.opts.filename) !== '.tsx') return; - const variableDeclarator = nodePath.node; - const { id } = variableDeclarator; - const idTypeAnnotation = id.typeAnnotation; - - if (idTypeAnnotation) { - let fileCodeNeedsUpdating = false; - - if (idTypeAnnotation.typeAnnotation.type === 'TSTypeReference') { - if (idTypeAnnotation.typeAnnotation.typeName.type === 'TSQualifiedName') { - const { left, right } = idTypeAnnotation.typeAnnotation.typeName; - - if (left.name === 'React') { - const rightName = right.name; - if (rightName === 'SFC' || rightName === 'FunctionComponent') { - processComponentDeclaration(idTypeAnnotation.typeAnnotation.typeParameters.params[0], nodePath, state); - fileCodeNeedsUpdating = true; - } else { - // throw new Error(`Cannot process annotation id React.${right.name}`); + const resolveVariableDeclarator = variableDeclarator => { + const { id } = variableDeclarator; + const idTypeAnnotation = id.typeAnnotation; + + if (idTypeAnnotation) { + let fileCodeNeedsUpdating = false; + + if (idTypeAnnotation.typeAnnotation.type === 'TSTypeReference') { + if (idTypeAnnotation.typeAnnotation.typeName.type === 'TSQualifiedName') { + const { left, right } = idTypeAnnotation.typeAnnotation.typeName; + + if (left.name === 'React') { + const rightName = right.name; + if (rightName === 'SFC' || rightName === 'FunctionComponent') { + processComponentDeclaration(idTypeAnnotation.typeAnnotation.typeParameters.params[0], nodePath, state); + fileCodeNeedsUpdating = true; + } else { + // throw new Error(`Cannot process annotation id React.${right.name}`); + } } - } - } else if (idTypeAnnotation.typeAnnotation.typeName.type === 'Identifier') { - const typeName = idTypeAnnotation.typeAnnotation.typeName.name; - if (typeName === 'SFC' || typeName === 'FunctionComponent') { - if (state.get('importsFromReact').has(typeName)) { - processComponentDeclaration(idTypeAnnotation.typeAnnotation.typeParameters.params[0], nodePath, state); - fileCodeNeedsUpdating = true; + } else if (idTypeAnnotation.typeAnnotation.typeName.type === 'Identifier') { + const typeName = idTypeAnnotation.typeAnnotation.typeName.name; + if (typeName === 'SFC' || typeName === 'FunctionComponent') { + if (state.get('importsFromReact').has(typeName)) { + processComponentDeclaration(idTypeAnnotation.typeAnnotation.typeParameters.params[0], nodePath, state); + fileCodeNeedsUpdating = true; + } + } else { + // reprocess this variable declaration but use the identifier lookup + const nextTypeDefinition = state.get('typeDefinitions')[typeName]; + const types = state.get('types'); + if (nextTypeDefinition && types.isTSType(nextTypeDefinition)) { + const newId = types.cloneDeep(id); + newId.typeAnnotation = types.TSTypeAnnotation(nextTypeDefinition); + const newNode = types.VariableDeclarator( + newId, + variableDeclarator.init + ); + resolveVariableDeclarator(newNode); + } } + } else { + throw new Error('Cannot process annotation type of', idTypeAnnotation.typeAnnotation.id.type); } - } else { - throw new Error('Cannot process annotation type of', idTypeAnnotation.typeAnnotation.id.type); } - } - if (fileCodeNeedsUpdating) { - // babel-plugin-react-docgen passes `this.file.code` to react-docgen - // instead of using the modified AST; to expose our changes to react-docgen - // they need to be rendered to a string - this.file.code = stripTypeScript(this.file.opts.filename, this.file.ast); + if (fileCodeNeedsUpdating) { + // babel-plugin-react-docgen passes `this.file.code` to react-docgen + // instead of using the modified AST; to expose our changes to react-docgen + // they need to be rendered to a string + this.file.code = stripTypeScript(this.file.opts.filename, this.file.ast); + } } - } + }; + + // kick off the recursive search for a React component in this node + resolveVariableDeclarator(nodePath.node); }, }, }; diff --git a/scripts/babel/proptypes-from-ts-props/index.test.js b/scripts/babel/proptypes-from-ts-props/index.test.js index 08958f2a429..307d5ee694e 100644 --- a/scripts/babel/proptypes-from-ts-props/index.test.js +++ b/scripts/babel/proptypes-from-ts-props/index.test.js @@ -2047,6 +2047,30 @@ const FooComponent = () => { return
Hello World
; }; +FooComponent.propTypes = { + foo: PropTypes.string.isRequired, + bar: PropTypes.number +};`); + }); + + it('annotates indirected FunctionComponent components', () => { + const result = transform( + ` +import React, { FunctionComponent } from 'react'; +type FooType = FunctionComponent<{foo: string, bar?: number}>; +const FooComponent: FooType = () => { + return (
Hello World
); +}`, + babelOptions + ); + + expect(result.code).toBe(`import React from 'react'; +import PropTypes from "prop-types"; + +const FooComponent = () => { + return
Hello World
; +}; + FooComponent.propTypes = { foo: PropTypes.string.isRequired, bar: PropTypes.number