From f29603aa6c7a52966a93554a3e3bebf079bccc4b Mon Sep 17 00:00:00 2001 From: Pavel Vasek Date: Sun, 18 Jun 2017 20:40:22 +0200 Subject: [PATCH] Props defined in different file are recognized correctly - the info about types from different files should be included as well - this should fix #16 --- src/__tests__/data/ExternalPropsComponent.tsx | 8 ++ .../data/ExternalPropsComponentProps.ts | 5 + src/__tests__/data/transformAST.tsx | 12 ++- src/__tests__/data/transformAST_external.ts | 9 ++ src/__tests__/getFileDocumentation.spec.ts | 13 +++ src/__tests__/transformAST.spec.ts | 13 ++- src/printUtils.ts | 3 + src/transformAST.ts | 95 ++++++++++++------- 8 files changed, 121 insertions(+), 37 deletions(-) create mode 100644 src/__tests__/data/ExternalPropsComponent.tsx create mode 100644 src/__tests__/data/ExternalPropsComponentProps.ts create mode 100644 src/__tests__/data/transformAST_external.ts diff --git a/src/__tests__/data/ExternalPropsComponent.tsx b/src/__tests__/data/ExternalPropsComponent.tsx new file mode 100644 index 00000000..80459624 --- /dev/null +++ b/src/__tests__/data/ExternalPropsComponent.tsx @@ -0,0 +1,8 @@ +import * as React from 'react'; +import { ExternalPropsComponentProps } from './ExternalPropsComponentProps'; + +export class ExternalPropsComponent extends React.Component { + render() { + return
ExternalPropsComponent
+ } +} \ No newline at end of file diff --git a/src/__tests__/data/ExternalPropsComponentProps.ts b/src/__tests__/data/ExternalPropsComponentProps.ts new file mode 100644 index 00000000..11972315 --- /dev/null +++ b/src/__tests__/data/ExternalPropsComponentProps.ts @@ -0,0 +1,5 @@ +/** ExternalPropsComponentProps props */ +export interface ExternalPropsComponentProps { + /** prop1 comment */ + prop1: string; +} \ No newline at end of file diff --git a/src/__tests__/data/transformAST.tsx b/src/__tests__/data/transformAST.tsx index a1b0ffcc..109ffea8 100644 --- a/src/__tests__/data/transformAST.tsx +++ b/src/__tests__/data/transformAST.tsx @@ -1,5 +1,6 @@ -import { externalHoc } from './transformAST_hoc'; import * as React from 'react'; +import { externalHoc } from './transformAST_hoc'; +import { ExternalInterface } from './transformAST_external'; const unexportedVar = 10; export const exportedVar = 10; @@ -78,4 +79,11 @@ export type ExportedType1 = React.HTMLAttributes & { prop1: "value1" | "value2"; /** the second property */ prop2: number; - }; \ No newline at end of file + }; + + /** ExportedClassWithExternalDependency comment */ +export class ExportedClassWithExternalDependency extends OurBaseClass { + method1(): string { + return ""; + } +} \ No newline at end of file diff --git a/src/__tests__/data/transformAST_external.ts b/src/__tests__/data/transformAST_external.ts new file mode 100644 index 00000000..2d9e23e8 --- /dev/null +++ b/src/__tests__/data/transformAST_external.ts @@ -0,0 +1,9 @@ +export interface ExternalInterfaceBase { + prop1ExternalInterfaceBase: string; +} + +/** ExternalInterface comment */ +export interface ExternalInterface extends ExternalInterfaceBase { + /** prop1 comment */ + prop1OnExternalInterface: string; +} \ No newline at end of file diff --git a/src/__tests__/getFileDocumentation.spec.ts b/src/__tests__/getFileDocumentation.spec.ts index 2cc7452a..79e37cd5 100644 --- a/src/__tests__/getFileDocumentation.spec.ts +++ b/src/__tests__/getFileDocumentation.spec.ts @@ -279,4 +279,17 @@ describe('getFileDocumentation', () => { assert.equal(p1.members[1].name, 'isFlippedY'); assert.equal(p1.members[1].comment, 'whether the image is flipped vertically'); }); + + it('Should recognize props defined in different file', function() { + const fileName = path.join(__dirname, '../../src/__tests__/data/ExternalPropsComponent.tsx'); // it's running in ./temp + const result = getFileDocumentation(fileName); + assert.ok(result.components); + assert.equal(1, result.components.length); + const r1 = result.components[0]; + assert.equal(r1.name, 'ExternalPropsComponent') + assert.isNotNull(r1.propInterface); + const p1 = r1.propInterface; + assert.equal(p1.name, 'ExternalPropsComponentProps'); + // assert.equal(p1.comment, 'ExternalPropsComponentProps props'); + }); }); \ No newline at end of file diff --git a/src/__tests__/transformAST.spec.ts b/src/__tests__/transformAST.spec.ts index 3cfd6cab..cded3adc 100644 --- a/src/__tests__/transformAST.spec.ts +++ b/src/__tests__/transformAST.spec.ts @@ -87,7 +87,7 @@ describe('transformAST', () => { it('should provide data about interfaces', () => { const result = target.interfaces; - assert.equal(result.length, 3); + assert.equal(result.length, 4); const r1 = result[0]; assert.equal(r1.name, 'UnexportedInterface'); assert.equal(r1.exported, false); @@ -144,11 +144,14 @@ describe('transformAST', () => { 'values': [], }, ]); + const r4 = result[3]; + assert.equal(r4.name, 'ExternalInterface'); + assert.equal(r4.exported, true); }); it('should provide data about classes', () => { const result = target.classes; - assert.equal(result.length, 3); + assert.equal(result.length, 4); const r1 = result[1]; assert.equal(r1.name, 'UnexportedClass'); assert.equal(r1.exported, false); @@ -160,6 +163,12 @@ describe('transformAST', () => { assert.equal(r2.exported, true); assert.equal(r2.comment, 'ExportedClass comment'); assert.deepEqual(r2.methods, [{name: 'method1'}, {name: 'method2'}]); + + const r4 = result[3]; + assert.equal(r4.name, 'ExportedClassWithExternalDependency'); + assert.equal(r4.exported, true); + assert.equal(r4.comment, 'ExportedClassWithExternalDependency comment'); + assert.deepEqual(r4.baseType.typeArguments.length, 2); }); it('should provide data about types', () => { diff --git a/src/printUtils.ts b/src/printUtils.ts index 0edbb353..34311faf 100644 --- a/src/printUtils.ts +++ b/src/printUtils.ts @@ -22,6 +22,9 @@ export function flagsToText(kind: ts.TypeFlags) { export function symbolFlagsToText(kind: ts.SymbolFlags) { return (ts).SymbolFlags[kind]; } +export function nodeFlagsToText(kind: ts.NodeFlags) { + return (ts).NodeFlags[kind]; +} /** True if this is visible outside this file, false otherwise */ function isNodeExported(node: ts.Node): boolean { diff --git a/src/transformAST.ts b/src/transformAST.ts index 8bcab3a1..f1883054 100644 --- a/src/transformAST.ts +++ b/src/transformAST.ts @@ -9,7 +9,7 @@ import { PropertyEntry, BaseClassEntry } from './model'; -import { simplePrint, syntaxKindToName, flagsToText, symbolFlagsToText } from "./printUtils"; +import { simplePrint, syntaxKindToName, flagsToText, symbolFlagsToText, nodeFlagsToText } from "./printUtils"; /** * Checks if the node is exported. @@ -50,6 +50,13 @@ function getMethods(checker: ts.TypeChecker, type: ts.Type, classDeclaratinNode: } function getProperties(checker: ts.TypeChecker, type: ts.Type, parent: ts.Node): PropertyEntry[] { + const baseTypes = type.getBaseTypes() || []; + const inheritedProperties = baseTypes + .reduce((acc, bt) => [ + ...acc, + ...bt.getProperties().map(p => p.getName()) + ], [] as string[]); + return type.getProperties() .filter(i => i.valueDeclaration) .map(i => { @@ -58,13 +65,13 @@ function getProperties(checker: ts.TypeChecker, type: ts.Type, parent: ts.Node): } const symbol = checker.getSymbolAtLocation(i.valueDeclaration.name); - const prop = i.valueDeclaration as ts.PropertySignature; + const prop = i.valueDeclaration as ts.PropertySignature; const typeInfo = getType(prop); + const propertyName = i.getName(); return { - name: i.getName(), - isOwn: i.valueDeclaration.parent === parent, - // text: i.valueDeclaration.getText(), + name: propertyName, + isOwn: inheritedProperties.indexOf(propertyName) === -1, type: typeInfo.type, values: typeInfo.values || [], isRequired: !prop.questionToken, @@ -159,6 +166,33 @@ export function transformAST(sourceFile: ts.SourceFile, checker: ts.TypeChecker) }; }); + const types: InterfaceEntry[] = nodes + .filter(i => i.kind === ts.SyntaxKind.TypeAliasDeclaration) + .map(i => i as ts.TypeAliasDeclaration) + .map(i => { + const type = checker.getTypeAtLocation(i.name) as ts.IntersectionType; + const properties: PropertyEntry[] = []; + type.types.forEach(t => { + const props = (t as any).properties; + let ownProperties: string[] = []; + if (props) { + ownProperties = props + .map((p: ts.Symbol) => p.getName()); + } + properties.push(...getProperties(checker, t, i)); + + properties + .forEach(p => p.isOwn = ownProperties.indexOf(p.name) > -1); + }); + const symbol = checker.getSymbolAtLocation(i.name); + return { + name: i.name.getText(), + properties, + exported: isNodeExported(i), + comment: !symbol ? "" : ts.displayPartsToString(symbol.getDocumentationComment()) + }; + }); + const classes: ClassEntry[] = nodes .filter(i => i.kind === ts.SyntaxKind.ClassDeclaration) .map(i => i as ts.ClassDeclaration) @@ -176,6 +210,29 @@ export function transformAST(sourceFile: ts.SourceFile, checker: ts.TypeChecker) const typeArguments = navigate(i, ts.SyntaxKind.HeritageClause, ts.SyntaxKind.ExpressionWithTypeArguments) as ts.ExpressionWithTypeArguments; + + + + if (typeArguments && typeArguments.typeArguments) { + typeArguments.typeArguments.forEach(ta => { + const taType = checker.getTypeAtLocation(ta); + if (taType && taType.symbol) { + const taTypeName = taType.symbol.getName(); + // check if the type is defined in another file + if (interfaces.every(int => int.name !== taTypeName) + && taTypeName !== '__type') { + // in that case we need to include the interface explicitly + interfaces.push({ + name: taType.symbol.name, + comment: ts.displayPartsToString(taType.symbol.getDocumentationComment()).trim(), + exported: true, // it has to be exported in order to be used, + properties: getProperties(checker, taType, null), + }); + } + } + }) + } + baseType = { name: t.symbol ? t.symbol.getName() : 'unknown', typeArguments: typeArguments && typeArguments.typeArguments ? @@ -192,34 +249,6 @@ export function transformAST(sourceFile: ts.SourceFile, checker: ts.TypeChecker) }; }); - const types: InterfaceEntry[] = nodes - .filter(i => i.kind === ts.SyntaxKind.TypeAliasDeclaration) - .map(i => i as ts.TypeAliasDeclaration) - .map(i => { - const type = checker.getTypeAtLocation(i.name) as ts.IntersectionType; - const properties: PropertyEntry[] = []; - type.types.forEach(t => { - const props = (t as any).properties; - let ownProperties: string[] = []; - if (props) { - ownProperties = props - .map((p: ts.Symbol) => p.getName()); - } - properties.push(...getProperties(checker, t, i)); - - properties - .filter(p => ownProperties.indexOf(p.name) > -1) - .forEach(p => p.isOwn = true); - }); - const symbol = checker.getSymbolAtLocation(i.name); - return { - name: i.name.getText(), - properties, - exported: isNodeExported(i), - comment: !symbol ? "" : ts.displayPartsToString(symbol.getDocumentationComment()) - }; - }); - return { classes, interfaces,