Skip to content
This repository was archived by the owner on Nov 27, 2023. It is now read-only.

Commit 4e85be2

Browse files
authored
feat: resolve references in oneOf propTypes (#236)
ISSUES CLOSED: #232
1 parent ce32e8e commit 4e85be2

6 files changed

+112
-69
lines changed

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"index.d.ts"
1313
],
1414
"scripts": {
15-
"linter": "tslint --project ./tsconfig.json --type-check",
15+
"linter": "tslint --project ./tsconfig.json",
1616
"start": "npm test",
1717
"clean": "rimraf dist",
1818
"prebuild": "npm run clean",
@@ -36,7 +36,7 @@
3636
},
3737
"license": "MIT",
3838
"devDependencies": {
39-
"@knisterpeter/standard-tslint": "1.0.2",
39+
"@knisterpeter/standard-tslint": "1.0.3",
4040
"@types/chalk": "0.4.31",
4141
"@types/diff": "0.0.31",
4242
"@types/node": "6.0.46",

Diff for: src/types.ts

+33-23
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import * as ASTQ from 'astq';
21
import * as dom from 'dts-dom';
3-
import { propTypeQueryExpression } from './typings';
2+
import { propTypeQueryExpression, AstQuery } from './typings';
43

54
export interface TypeDeclaration {
65
type: any;
@@ -14,13 +13,13 @@ function getTypeDeclaration(type: any, optional: boolean): TypeDeclaration {
1413
};
1514
}
1615

17-
export function get(astq: ASTQ, propertyAst: any, propTypesName: string|undefined): TypeDeclaration {
16+
export function get(ast: AstQuery, propertyAst: any, propTypesName: string|undefined): TypeDeclaration {
1817
try {
19-
const simpleType = getSimpleType(astq, propertyAst, propTypesName);
18+
const simpleType = getSimpleType(ast, propertyAst, propTypesName);
2019
if (simpleType) {
2120
return simpleType;
2221
}
23-
const complexType = getComplexType(astq, propertyAst, propTypesName);
22+
const complexType = getComplexType(ast, propertyAst, propTypesName);
2423
if (complexType) {
2524
return complexType;
2625
}
@@ -35,8 +34,9 @@ export function get(astq: ASTQ, propertyAst: any, propTypesName: string|undefine
3534
}
3635

3736
// tslint:disable:next-line cyclomatic-complexity
38-
function getSimpleType(astq: ASTQ, propertyAst: any, propTypesName: string|undefined): TypeDeclaration|undefined {
39-
const [required, simpleTypeName] = getSimpleTypeName(astq, propertyAst, propTypesName);
37+
function getSimpleType(ast: AstQuery, propertyAst: any,
38+
propTypesName: string|undefined): TypeDeclaration|undefined {
39+
const [required, simpleTypeName] = getSimpleTypeName(ast, propertyAst, propTypesName);
4040
switch (simpleTypeName) {
4141
case 'any':
4242
return getTypeDeclaration('any', !required);
@@ -66,27 +66,28 @@ function getSimpleType(astq: ASTQ, propertyAst: any, propTypesName: string|undef
6666
return undefined;
6767
}
6868

69-
function getComplexType(astq: ASTQ, propertyAst: any, propTypesName: string|undefined): TypeDeclaration|undefined {
70-
const [required, complexTypeName, typeAst] = getComplexTypeName(astq, propertyAst, propTypesName);
69+
function getComplexType(ast: AstQuery, propertyAst: any,
70+
propTypesName: string|undefined): TypeDeclaration|undefined {
71+
const [required, complexTypeName, typeAst] = getComplexTypeName(ast, propertyAst, propTypesName);
7172
switch (complexTypeName) {
7273
case 'instanceOf':
7374
return getTypeDeclaration(dom.create.typeof(
7475
dom.create.namedTypeReference(typeAst.arguments[0].name)), !required);
7576
case 'oneOfType':
7677
const typeDecls = typeAst.arguments[0].elements
77-
.map((ast: any) => get(astq, ast, propTypesName)) as TypeDeclaration[];
78+
.map((subtree: any) => get(ast, subtree, propTypesName)) as TypeDeclaration[];
7879
return getTypeDeclaration(dom.create.union(typeDecls.map(type => type.type)), !required);
7980
case 'arrayOf':
80-
const typeDecl = get(astq, typeAst.arguments[0], propTypesName);
81+
const typeDecl = get(ast, typeAst.arguments[0], propTypesName);
8182
return getTypeDeclaration(dom.create.array(typeDecl.type), !required);
8283
case 'oneOf':
8384
// tslint:disable:next-line comment-format
8485
// FIXME: This should better be a real enum
85-
const enumEntries = getEnumValues(typeAst.arguments[0].elements);
86+
const enumEntries = getEnumValues(ast, typeAst.arguments[0]);
8687
return getTypeDeclaration(dom.create.union(enumEntries as dom.Type[]), !required);
8788
case 'shape':
8889
const entries = typeAst.arguments[0].properties.map((entry: any) => {
89-
const typeDecl = get(astq, entry.value, propTypesName);
90+
const typeDecl = get(ast, entry.value, propTypesName);
9091
return dom.create.property(entry.key.name, typeDecl.type,
9192
typeDecl.optional ? dom.DeclarationFlags.Optional : dom.DeclarationFlags.None);
9293
});
@@ -95,8 +96,8 @@ function getComplexType(astq: ASTQ, propertyAst: any, propTypesName: string|unde
9596
return undefined;
9697
}
9798

98-
function isRequired(astq: ASTQ, propertyAst: any): [boolean, any] {
99-
const required = astq.query(propertyAst, `
99+
function isRequired(ast: AstQuery, propertyAst: any): [boolean, any] {
100+
const required = ast.querySubtree(propertyAst, `
100101
MemberExpression /:property Identifier[@name == 'isRequired']
101102
`);
102103
if (required.length > 0) {
@@ -105,10 +106,10 @@ function isRequired(astq: ASTQ, propertyAst: any): [boolean, any] {
105106
return [false, propertyAst];
106107
}
107108

108-
function getSimpleTypeName(astq: ASTQ, propertyAst: any,
109+
function getSimpleTypeName(ast: AstQuery, propertyAst: any,
109110
propTypesName: string|undefined): [boolean, string|undefined] {
110-
const [required, typeAst] = isRequired(astq, propertyAst);
111-
const res = astq.query(typeAst, `
111+
const [required, typeAst] = isRequired(ast, propertyAst);
112+
const res = ast.querySubtree(typeAst, `
112113
MemberExpression[
113114
(${propTypeQueryExpression(propTypesName)})
114115
&&
@@ -118,18 +119,27 @@ function getSimpleTypeName(astq: ASTQ, propertyAst: any,
118119
return [required, res.length > 0 ? res[0].property.name : undefined];
119120
}
120121

121-
function getComplexTypeName(astq: ASTQ, propertyAst: any,
122+
function getComplexTypeName(ast: AstQuery, propertyAst: any,
122123
propTypesName: string|undefined): [boolean, string|undefined, any] {
123-
const [required, typeAst] = isRequired(astq, propertyAst);
124+
const [required, typeAst] = isRequired(ast, propertyAst);
124125
if (typeAst.type === 'CallExpression') {
125-
const [, simpleTypeName] = getSimpleTypeName(astq, typeAst.callee, propTypesName);
126+
const [, simpleTypeName] = getSimpleTypeName(ast, typeAst.callee, propTypesName);
126127
return [required, simpleTypeName, typeAst];
127128
}
128129
return [required, undefined, typeAst];
129130
}
130131

131-
function getEnumValues(oneOfTypes: any[]): any[] {
132-
return oneOfTypes.map((element: any) => {
132+
function getEnumValues(ast: AstQuery, oneOfTypes: any): any[] {
133+
if (oneOfTypes.type === 'Identifier') {
134+
const res = ast.query(`
135+
//VariableDeclarator[
136+
/:id Identifier[@name == '${oneOfTypes.name}']
137+
]
138+
/:init *
139+
`);
140+
oneOfTypes = res[0];
141+
}
142+
return (oneOfTypes.elements as any[]).map((element: any) => {
133143
// tslint:disable:next-line comment-format
134144
// FIXME: This are not named references!
135145
if (element.type === 'StringLiteral') {

Diff for: src/typings.ts

+57-44
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,34 @@ import * as dom from 'dts-dom';
33
import { InstanceOfResolver } from './index';
44
import * as types from './types';
55

6-
export function createTypings(moduleName: string|null, ast: any,
6+
export interface AstQuery {
7+
query(query: string): any[];
8+
querySubtree(subtree: any, query: string): any[];
9+
}
10+
11+
export function createTypings(moduleName: string|null, programAst: any,
712
instanceOfResolver: InstanceOfResolver | undefined): string {
813
const astq = new ASTQ();
9-
const reactComponentName = getReactComponentName(astq, ast);
10-
const propTypesName = getPropTypesName(astq, ast);
11-
const importedTypes = getInstanceOfPropTypes(astq, ast, propTypesName);
12-
const importStatements = getImportStatements(astq, ast, importedTypes, instanceOfResolver);
14+
const ast = {
15+
query(query: string): any[] {
16+
return astq.query(programAst, query);
17+
},
18+
querySubtree(subtree: any, query: string): any[] {
19+
return astq.query(subtree, query);
20+
}
21+
};
22+
const reactComponentName = getReactComponentName(ast);
23+
const propTypesName = getPropTypesName(ast);
24+
const importedTypes = getInstanceOfPropTypes(ast, propTypesName);
25+
const importStatements = getImportStatements(ast, importedTypes, instanceOfResolver);
1326
const componentNames = getUniqueNames([
14-
...getComponentNamesByPropTypeAssignment(astq, ast),
15-
...getComponentNamesByStaticPropTypeAttribute(astq, ast),
16-
...getComponentNamesByJsxInBody(astq, ast)
27+
...getComponentNamesByPropTypeAssignment(ast),
28+
...getComponentNamesByStaticPropTypeAttribute(ast),
29+
...getComponentNamesByJsxInBody(ast)
1730
]);
1831

1932
const m = dom.create.module(moduleName || 'moduleName');
20-
if (hasReactClass(astq, ast, reactComponentName)) {
33+
if (hasReactClass(ast, reactComponentName)) {
2134
m.members.push(dom.create.importAll('React', 'react'));
2235
}
2336
if (importStatements.length > 0) {
@@ -30,11 +43,11 @@ export function createTypings(moduleName: string|null, ast: any,
3043
});
3144
}
3245
componentNames.forEach(componentName => {
33-
const exportType = getComponentExportType(astq, ast, componentName);
34-
const propTypes = getPropTypesFromAssignment(astq, ast, componentName) ||
35-
getPropTypesFromStaticAttribute(astq, ast, componentName);
46+
const exportType = getComponentExportType(ast, componentName);
47+
const propTypes = getPropTypesFromAssignment(ast, componentName) ||
48+
getPropTypesFromStaticAttribute(ast, componentName);
3649
if (exportType) {
37-
createExportedTypes(m, astq, ast, componentName, reactComponentName, propTypes, propTypesName, exportType);
50+
createExportedTypes(m, ast, componentName, reactComponentName, propTypes, propTypesName, exportType);
3851
}
3952
});
4053

@@ -47,15 +60,15 @@ export function createTypings(moduleName: string|null, ast: any,
4760
}
4861
};
4962

50-
function createExportedTypes(m: dom.ModuleDeclaration, astq: ASTQ, ast: any, componentName: string,
63+
function createExportedTypes(m: dom.ModuleDeclaration, ast: AstQuery, componentName: string,
5164
reactComponentName: string|undefined, propTypes: any, propTypesName: string|undefined,
5265
exportType: dom.DeclarationFlags): void {
53-
const classComponent = isClassComponent(astq, ast, componentName, reactComponentName);
66+
const classComponent = isClassComponent(ast, componentName, reactComponentName);
5467

5568
const interf = dom.create.interface(`${componentName}Props`);
5669
interf.flags = dom.DeclarationFlags.Export;
5770
if (propTypes) {
58-
createPropTypeTypings(interf, astq, propTypes, propTypesName);
71+
createPropTypeTypings(interf, ast, propTypes, propTypesName);
5972
}
6073
if (propTypes || classComponent) {
6174
m.members.push(interf);
@@ -74,13 +87,13 @@ function createExportedTypes(m: dom.ModuleDeclaration, astq: ASTQ, ast: any, com
7487
}
7588
}
7689

77-
function createPropTypeTypings(interf: dom.InterfaceDeclaration, astq: ASTQ, propTypes: any,
90+
function createPropTypeTypings(interf: dom.InterfaceDeclaration, ast: AstQuery, propTypes: any,
7891
propTypesName: string|undefined): void {
79-
const res = astq.query(propTypes, `
92+
const res = ast.querySubtree(propTypes, `
8093
/ ObjectProperty
8194
`);
8295
res.forEach(propertyAst => {
83-
const typeDecl = types.get(astq, propertyAst.value, propTypesName);
96+
const typeDecl = types.get(ast, propertyAst.value, propTypesName);
8497
const property = dom.create.property(propertyAst.key.name || propertyAst.key.value, typeDecl.type,
8598
typeDecl.optional ? dom.DeclarationFlags.Optional : 0);
8699
if (propertyAst.leadingComments && propertyAst.leadingComments[0].type === 'CommentBlock') {
@@ -122,8 +135,8 @@ export function propTypeQueryExpression(propTypesName: string|undefined): string
122135
`;
123136
}
124137

125-
function getReactComponentName(astq: ASTQ, ast: any): string|undefined {
126-
const res = astq.query(ast, `
138+
function getReactComponentName(ast: AstQuery): string|undefined {
139+
const res = ast.query(`
127140
// ImportDeclaration[
128141
/:source StringLiteral[@value == 'react']
129142
]
@@ -138,8 +151,8 @@ function getReactComponentName(astq: ASTQ, ast: any): string|undefined {
138151
return undefined;
139152
}
140153

141-
function getPropTypesName(astq: ASTQ, ast: any): string|undefined {
142-
const res = astq.query(ast, `
154+
function getPropTypesName(ast: AstQuery): string|undefined {
155+
const res = ast.query(`
143156
// ImportDeclaration[
144157
/:source StringLiteral[@value == 'react']
145158
]
@@ -154,8 +167,8 @@ function getPropTypesName(astq: ASTQ, ast: any): string|undefined {
154167
return undefined;
155168
}
156169

157-
function hasReactClass(astq: ASTQ, ast: any, reactComponentName: string|undefined): boolean {
158-
const res = astq.query(ast, `
170+
function hasReactClass(ast: AstQuery, reactComponentName: string|undefined): boolean {
171+
const res = ast.query(`
159172
// ClassDeclaration[
160173
'${reactComponentName}' == 'undefined'
161174
?
@@ -184,8 +197,8 @@ function hasReactClass(astq: ASTQ, ast: any, reactComponentName: string|undefine
184197
return res.length > 0;
185198
}
186199

187-
function getInstanceOfPropTypes(astq: ASTQ, ast: any, propTypesName: string|undefined): string[] {
188-
const res = astq.query(ast, `
200+
function getInstanceOfPropTypes(ast: AstQuery, propTypesName: string|undefined): string[] {
201+
const res = ast.query(`
189202
// CallExpression[
190203
/:callee MemberExpression[
191204
(${propTypeQueryExpression(propTypesName)})
@@ -203,10 +216,10 @@ interface ImportStatement {
203216
local: string;
204217
path: string;
205218
}
206-
function getImportStatements(astq: ASTQ, ast: any, typeNames: string[],
219+
function getImportStatements(ast: AstQuery, typeNames: string[],
207220
instanceOfResolver: InstanceOfResolver | undefined): ImportStatement[] {
208221
return typeNames.map(name => {
209-
const res = astq.query(ast, `
222+
const res = ast.query(`
210223
// ImportDeclaration[
211224
/:specifiers * /:local Identifier[@name == '${name}']
212225
]
@@ -233,8 +246,8 @@ function getImportStatements(astq: ASTQ, ast: any, typeNames: string[],
233246
.filter(importStatement => Boolean(importStatement.path));
234247
}
235248

236-
function getComponentNamesByPropTypeAssignment(astq: ASTQ, ast: any): string[] {
237-
const res = astq.query(ast, `
249+
function getComponentNamesByPropTypeAssignment(ast: AstQuery): string[] {
250+
const res = ast.query(`
238251
//AssignmentExpression
239252
/:left MemberExpression[
240253
/:object Identifier &&
@@ -247,8 +260,8 @@ function getComponentNamesByPropTypeAssignment(astq: ASTQ, ast: any): string[] {
247260
return [];
248261
}
249262

250-
function getComponentNamesByStaticPropTypeAttribute(astq: ASTQ, ast: any): string[] {
251-
const res = astq.query(ast, `
263+
function getComponentNamesByStaticPropTypeAttribute(ast: AstQuery): string[] {
264+
const res = ast.query(`
252265
//ClassDeclaration[
253266
/:body * //ClassProperty /:key Identifier[@name == 'propTypes']
254267
]
@@ -259,8 +272,8 @@ function getComponentNamesByStaticPropTypeAttribute(astq: ASTQ, ast: any): strin
259272
return [];
260273
}
261274

262-
function getComponentNamesByJsxInBody(astq: ASTQ, ast: any): string[] {
263-
const res = astq.query(ast, `
275+
function getComponentNamesByJsxInBody(ast: AstQuery): string[] {
276+
const res = ast.query(`
264277
// ClassDeclaration[
265278
/:body * //JSXElement
266279
],
@@ -278,8 +291,8 @@ function getComponentNamesByJsxInBody(astq: ASTQ, ast: any): string[] {
278291
return [];
279292
}
280293

281-
function getPropTypesFromAssignment(astq: ASTQ, ast: any, componentName: string): any|undefined {
282-
const res = astq.query(ast, `
294+
function getPropTypesFromAssignment(ast: AstQuery, componentName: string): any|undefined {
295+
const res = ast.query(`
283296
//AssignmentExpression[
284297
/:left MemberExpression[
285298
/:object Identifier[@name == '${componentName}'] &&
@@ -293,8 +306,8 @@ function getPropTypesFromAssignment(astq: ASTQ, ast: any, componentName: string)
293306
return undefined;
294307
}
295308

296-
function getPropTypesFromStaticAttribute(astq: ASTQ, ast: any, componentName: string): any|undefined {
297-
const res = astq.query(ast, `
309+
function getPropTypesFromStaticAttribute(ast: AstQuery, componentName: string): any|undefined {
310+
const res = ast.query(`
298311
//ClassDeclaration[
299312
/:id Identifier[@name == '${componentName}']
300313
]
@@ -310,8 +323,8 @@ function getPropTypesFromStaticAttribute(astq: ASTQ, ast: any, componentName: st
310323
return undefined;
311324
}
312325

313-
function getComponentExportType(astq: ASTQ, ast: any, componentName: string): dom.DeclarationFlags|undefined {
314-
let res = astq.query(ast, `
326+
function getComponentExportType(ast: AstQuery, componentName: string): dom.DeclarationFlags|undefined {
327+
let res = ast.query(`
315328
// ExportDefaultDeclaration[
316329
// ClassDeclaration
317330
/:id Identifier[@name == '${componentName}']
@@ -337,7 +350,7 @@ function getComponentExportType(astq: ASTQ, ast: any, componentName: string): do
337350
if (res.length > 0) {
338351
return dom.DeclarationFlags.ExportDefault;
339352
}
340-
res = astq.query(ast, `
353+
res = ast.query(`
341354
// ExportNamedDeclaration[
342355
// ClassDeclaration
343356
/:id Identifier[@name == '${componentName}']
@@ -356,9 +369,9 @@ function getComponentExportType(astq: ASTQ, ast: any, componentName: string): do
356369
return undefined;
357370
}
358371

359-
function isClassComponent(astq: ASTQ, ast: any, componentName: string,
372+
function isClassComponent(ast: AstQuery, componentName: string,
360373
reactComponentName: string|undefined): boolean {
361-
const res = astq.query(ast, `
374+
const res = ast.query(`
362375
// ClassDeclaration
363376
/:id Identifier[@name == '${componentName}']
364377
,

Diff for: tests/parsing-test.ts

+3
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,6 @@ test('Parsing should create definition from class import PropTypes and instanceO
6767
test('Parsing should create definition from file without propTypes', t => {
6868
compare(t, 'component', 'component-without-proptyes.jsx', 'component-without-proptyes.d.ts');
6969
});
70+
test('Parsing should create definition from file with references in propTypes', t => {
71+
compare(t, 'component', 'references-in-proptypes.jsx', 'references-in-proptypes.d.ts');
72+
});

0 commit comments

Comments
 (0)