diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.constants.ts b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.constants.ts new file mode 100644 index 00000000..21ab24c7 --- /dev/null +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.constants.ts @@ -0,0 +1,13 @@ +const PROPERTY1_DEFAULT = 'hello'; +const PROPERTY2_DEFAULT = 'goodbye'; +const PROPERTY3_DEFAULT = 10; +const PROPERTY4_DEFAULT = 'this is a string'; +const PROPERTY5_DEFAULT = true; + +export { + PROPERTY1_DEFAULT, + PROPERTY2_DEFAULT, + PROPERTY3_DEFAULT, + PROPERTY4_DEFAULT, + PROPERTY5_DEFAULT +}; diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx new file mode 100644 index 00000000..ee3f7416 --- /dev/null +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredProps.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; + +const PROPERTY1_DEFAULT = 'hello'; +const PROPERTY2_DEFAULT = 'goodbye'; +const PROPERTY3_DEFAULT = 10; +const PROPERTY4_DEFAULT = 'this is a string'; +const PROPERTY5_DEFAULT = true; + +type Property1Type = 'hello' | 'world'; + +type Props = { + /** prop1 description */ + prop1?: Property1Type; + /** prop2 description */ + prop2?: 'goodbye' | 'farewell'; + /** prop3 description */ + prop3?: number; + /** prop4 description */ + prop4?: string; + /** prop5 description */ + prop5?: boolean; +}; + +/** FunctionalComponentWithDesctructuredProps description */ +const FunctionalComponentWithDesctructuredProps: React.FC = ({ + prop1 = PROPERTY1_DEFAULT, + prop2 = PROPERTY2_DEFAULT, + prop3 = PROPERTY3_DEFAULT, + prop4 = PROPERTY4_DEFAULT, + prop5 = PROPERTY5_DEFAULT +}) =>
; + +export default FunctionalComponentWithDesctructuredProps; diff --git a/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx b/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx new file mode 100644 index 00000000..8b200ab9 --- /dev/null +++ b/src/__tests__/data/FunctionalComponentWithDesctructuredPropsAndImportedConstants.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; + +import { + PROPERTY1_DEFAULT, + PROPERTY2_DEFAULT, + PROPERTY3_DEFAULT, + PROPERTY4_DEFAULT, + PROPERTY5_DEFAULT +} from './FunctionalComponentWithDesctructuredProps.constants'; + +type Property1Type = 'hello' | 'world'; + +type Props = { + /** prop1 description */ + prop1?: Property1Type; + /** prop2 description */ + prop2?: 'goodbye' | 'farewell'; + /** prop3 description */ + prop3?: number; + /** prop4 description */ + prop4?: string; + /** prop5 description */ + prop5?: boolean; +}; + +/** FunctionalComponentWithDesctructuredPropsAndImportedConstants description */ +const FunctionalComponentWithDesctructuredPropsAndImportedConstants: React.FC = ({ + prop1 = PROPERTY1_DEFAULT, + prop2 = PROPERTY2_DEFAULT, + prop3 = PROPERTY3_DEFAULT, + prop4 = PROPERTY4_DEFAULT, + prop5 = PROPERTY5_DEFAULT +}) =>
; + +export default FunctionalComponentWithDesctructuredPropsAndImportedConstants; diff --git a/src/__tests__/parser.ts b/src/__tests__/parser.ts index 6828ab31..0042e3fb 100644 --- a/src/__tests__/parser.ts +++ b/src/__tests__/parser.ts @@ -723,6 +723,70 @@ describe('parser', () => { }); }); + it('should parse functional component defined as const with default value assignments in immediately destructured props', () => { + check('FunctionalComponentWithDesctructuredProps', { + FunctionalComponentWithDesctructuredProps: { + prop1: { + type: 'Property1Type', + required: false, + defaultValue: 'hello' + }, + prop2: { + type: '"goodbye" | "farewell"', + required: false, + defaultValue: 'goodbye' + }, + prop3: { + type: 'number', + required: false, + defaultValue: 10 + }, + prop4: { + type: 'string', + required: false, + defaultValue: 'this is a string' + }, + prop5: { + type: 'boolean', + required: false, + defaultValue: true + } + } + }); + }); + + it('should parse functional component defined as const with default value (imported from a separate file) assignments in immediately destructured props', () => { + check('FunctionalComponentWithDesctructuredPropsAndImportedConstants', { + FunctionalComponentWithDesctructuredPropsAndImportedConstants: { + prop1: { + type: 'Property1Type', + required: false, + defaultValue: 'hello' + }, + prop2: { + type: '"goodbye" | "farewell"', + required: false, + defaultValue: 'goodbye' + }, + prop3: { + type: 'number', + required: false, + defaultValue: 10 + }, + prop4: { + type: 'string', + required: false, + defaultValue: 'this is a string' + }, + prop5: { + type: 'boolean', + required: false, + defaultValue: true + } + } + }); + }); + it('should parse functional component component defined as const', () => { check('FunctionDeclarationVisibleName', { 'Awesome Jumbotron': { diff --git a/src/parser.ts b/src/parser.ts index 3c94af6b..45483efb 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -902,6 +902,33 @@ export class Parser { }, {}); } + public getLiteralValueFromImportSpecifier( + property: ts.ImportSpecifier + ): string | boolean | number | null | undefined { + if (ts.isImportSpecifier(property)) { + const symbol = this.checker.getSymbolAtLocation(property.name); + + if (!symbol) { + return null; + } + + const aliasedSymbol = this.checker.getAliasedSymbol(symbol); + if ( + aliasedSymbol && + aliasedSymbol.declarations && + aliasedSymbol.declarations.length + ) { + return this.getLiteralValueFromPropertyAssignment( + aliasedSymbol.declarations[0] as ts.BindingElement + ); + } + + return null; + } + + return null; + } + public getLiteralValueFromPropertyAssignment( property: ts.PropertyAssignment | ts.BindingElement ): string | boolean | number | null | undefined { @@ -943,10 +970,27 @@ export class Parser { case ts.SyntaxKind.NullKeyword: return this.savePropValueAsString ? 'null' : null; case ts.SyntaxKind.Identifier: - // can potentially find other identifiers in the source and map those in the future - return (initializer as ts.Identifier).text === 'undefined' - ? 'undefined' - : null; + if ((initializer as ts.Identifier).text === 'undefined') { + return 'undefined'; + } + + const symbol = this.checker.getSymbolAtLocation( + initializer as ts.Identifier + ); + + if (symbol && symbol.declarations && symbol.declarations.length) { + if (ts.isImportSpecifier(symbol.declarations[0])) { + return this.getLiteralValueFromImportSpecifier( + symbol.declarations[0] as ts.ImportSpecifier + ); + } + + return this.getLiteralValueFromPropertyAssignment( + symbol.declarations[0] as ts.BindingElement + ); + } + + return null; case ts.SyntaxKind.PropertyAccessExpression: { const symbol = this.checker.getSymbolAtLocation( initializer as ts.PropertyAccessExpression