Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/__tests__/data/ExternalPropsComponent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as React from 'react';
import { ExternalPropsComponentProps } from './ExternalPropsComponentProps';

export class ExternalPropsComponent extends React.Component<ExternalPropsComponentProps, {}> {
render() {
return <div>ExternalPropsComponent</div>
}
}
5 changes: 5 additions & 0 deletions src/__tests__/data/ExternalPropsComponentProps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/** ExternalPropsComponentProps props */
export interface ExternalPropsComponentProps {
/** prop1 comment */
prop1: string;
}
12 changes: 10 additions & 2 deletions src/__tests__/data/transformAST.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -78,4 +79,11 @@ export type ExportedType1 = React.HTMLAttributes<HTMLImageElement> & {
prop1: "value1" | "value2";
/** the second property */
prop2: number;
};
};

/** ExportedClassWithExternalDependency comment */
export class ExportedClassWithExternalDependency extends OurBaseClass<ExternalInterface, {}> {
method1(): string {
return "";
}
}
9 changes: 9 additions & 0 deletions src/__tests__/data/transformAST_external.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface ExternalInterfaceBase {
prop1ExternalInterfaceBase: string;
}

/** ExternalInterface comment */
export interface ExternalInterface extends ExternalInterfaceBase {
/** prop1 comment */
prop1OnExternalInterface: string;
}
13 changes: 13 additions & 0 deletions src/__tests__/getFileDocumentation.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
});
13 changes: 11 additions & 2 deletions src/__tests__/transformAST.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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', () => {
Expand Down
3 changes: 3 additions & 0 deletions src/printUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export function flagsToText(kind: ts.TypeFlags) {
export function symbolFlagsToText(kind: ts.SymbolFlags) {
return (<any>ts).SymbolFlags[kind];
}
export function nodeFlagsToText(kind: ts.NodeFlags) {
return (<any>ts).NodeFlags[kind];
}

/** True if this is visible outside this file, false otherwise */
function isNodeExported(node: ts.Node): boolean {
Expand Down
95 changes: 62 additions & 33 deletions src/transformAST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 => {
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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 ?
Expand All @@ -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,
Expand Down