Skip to content

Commit

Permalink
Allow SFC and FunctionComponent definitions to be indirected (#1707)
Browse files Browse the repository at this point in the history
  • Loading branch information
chandlerprall authored Mar 8, 2019
1 parent d4be8c0 commit 6f39bb6
Show file tree
Hide file tree
Showing 2 changed files with 75 additions and 34 deletions.
85 changes: 51 additions & 34 deletions scripts/babel/proptypes-from-ts-props/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
},
},
};
Expand Down
24 changes: 24 additions & 0 deletions scripts/babel/proptypes-from-ts-props/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2047,6 +2047,30 @@ const FooComponent = () => {
return <div>Hello World</div>;
};
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 (<div>Hello World</div>);
}`,
babelOptions
);

expect(result.code).toBe(`import React from 'react';
import PropTypes from "prop-types";
const FooComponent = () => {
return <div>Hello World</div>;
};
FooComponent.propTypes = {
foo: PropTypes.string.isRequired,
bar: PropTypes.number
Expand Down

0 comments on commit 6f39bb6

Please sign in to comment.