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

Commit f6f63ae

Browse files
authored
feat: write multiple outputs per file (#211)
* feat: write multiple outputs per file * feat: reimplemented simple types * feat: readd all simple and complex types * feat: feature complete * chore: do not cover deprecated code * chore: add missing dependency to rimraf * refactor: use meow instead of minimist * refactor: use get-stdin * fix: fix smaller changes to previous behaviour
1 parent 597c266 commit f6f63ae

25 files changed

+848
-412
lines changed

Diff for: .vscode/settings.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"typescript.tsdk": "./node_modules/typescript/lib/",
3+
"files.exclude": {
4+
"**/.git": true,
5+
"**/.svn": true,
6+
"**/.hg": true,
7+
"**/.DS_Store": true
8+
},
9+
"search.exclude": {
10+
"**/dist": true,
11+
"**/coverage": true,
12+
"**/node_modules": true,
13+
"**/bower_components": true
14+
}
15+
}

Diff for: cli.js

+20-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,28 @@
11
#!/usr/bin/env node
22
var react2dts = require('./dist/src/index');
3-
var minimist = require('minimist');
3+
var meow = require('meow');
44

5-
var options = minimist(process.argv.slice(2), {
6-
string: ['name', 'module-name'],
7-
boolean: ['top-level-module'],
5+
const cli = meow(`
6+
Usage
7+
$ react2dts [--module-name <name> | --top-level-module]
8+
9+
react2dts reads from stdin to process a file.
10+
11+
Options
12+
--module-name, --name name of the module to create
13+
--top-level-module if the created module should live in top-level
14+
15+
Examples
16+
$ cat <some/react/component.jsx> |react2dts --module-name module-name
17+
18+
$ cat <some/react/component.jsx> |react2dts --top-level-module
19+
`, {
820
alias: {
921
'module-name': 'name'
1022
}
1123
});
24+
if (Object.keys(cli.flags).length === 0) {
25+
cli.showHelp(1);
26+
}
1227

13-
react2dts.cli(options);
28+
react2dts.cli(cli.flags);

Diff for: package.json

+7-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"scripts": {
1515
"start": "npm test",
16-
"clean": "rm -f index.js index.js.map tests/*-test.js tests/*-test.js.map",
16+
"clean": "rimraf dist",
1717
"prebuild": "npm run clean",
1818
"build": "tsc --sourceMap",
1919
"build:inline": "tsc --inlineSourceMap",
@@ -51,6 +51,7 @@
5151
"mocha": "3.1.2",
5252
"nyc": "8.4.0",
5353
"react": "15.3.2",
54+
"rimraf": "2.5.4",
5455
"source-map-support": "0.4.6",
5556
"standard-version": "3.0.0",
5657
"tslint": "3.15.1",
@@ -60,7 +61,8 @@
6061
"astq": "1.8.0",
6162
"babylon": "6.13.1",
6263
"dts-dom": "0.1.11",
63-
"minimist": "1.2.0"
64+
"get-stdin": "5.0.1",
65+
"meow": "3.7.0"
6466
},
6567
"config": {
6668
"commitizen": {
@@ -72,7 +74,9 @@
7274
"node_modules",
7375
"coverage",
7476
"dist/tests",
75-
"tests"
77+
"tests",
78+
"dist/src/deprecated.js",
79+
"dist/src/analyzer.js"
7680
]
7781
}
7882
}

Diff for: src/analyzer.ts

+14-20
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1-
import * as dom from 'dts-dom';
21
import * as astqts from 'astq';
32
const ASTQ: typeof astqts.ASTQ = astqts as any;
43

5-
import { InstanceOfResolver, IPropTypes, IProp, IASTNode } from './index';
4+
import { InstanceOfResolver } from './index';
5+
import { IPropTypes, IProp } from './deprecated';
6+
7+
interface IASTNode {
8+
type: string;
9+
loc: Object;
10+
[name: string]: any;
11+
value?: any;
12+
key?: any;
13+
expression?: any;
14+
id?: any;
15+
body?: any;
16+
}
617

718
const defaultInstanceOfResolver: InstanceOfResolver = (_name: string): undefined => undefined;
819

@@ -30,7 +41,6 @@ function getOptionalDocumentation(propertyNode: any): string {
3041
export function getTypeFromPropType(node: IASTNode, instanceOfResolver = defaultInstanceOfResolver): IProp {
3142
const result: IProp = {
3243
type: 'any',
33-
type2: 'any',
3444
optional: true
3545
};
3646
if (isNode(node)) {
@@ -39,54 +49,41 @@ export function getTypeFromPropType(node: IASTNode, instanceOfResolver = default
3949
switch (type.name) {
4050
case 'any':
4151
result.type = 'any';
42-
result.type2 = 'any';
4352
break;
4453
case 'array':
4554
result.type = (type.arrayType || 'any') + '[]';
46-
result.type2 = dom.create.array(type.arrayType2 || 'any');
4755
break;
4856
case 'bool':
4957
result.type = 'boolean';
50-
result.type2 = 'boolean';
5158
break;
5259
case 'func':
5360
result.type = '(...args: any[]) => any';
54-
result.type2 = dom.create.functionType([
55-
dom.create.parameter('args', dom.create.array('any'), dom.ParameterFlags.Rest)], 'any');
5661
break;
5762
case 'number':
5863
result.type = 'number';
59-
result.type2 = 'number';
6064
break;
6165
case 'object':
6266
result.type = 'Object';
63-
result.type2 = dom.create.namedTypeReference('Object');
6467
break;
6568
case 'string':
6669
result.type = 'string';
67-
result.type2 = 'string';
6870
break;
6971
case 'node':
7072
result.type = 'React.ReactNode';
71-
result.type2 = dom.create.namedTypeReference('React.ReactNode');
7273
break;
7374
case 'element':
7475
result.type = 'React.ReactElement<any>';
75-
result.type2 = dom.create.namedTypeReference('React.ReactElement<any>');
7676
break;
7777
case 'union':
7878
result.type = type.types.map((unionType: string) => unionType).join('|');
79-
result.type2 = dom.create.union(type.types2);
8079
break;
8180
case 'instanceOf':
8281
if (type.importPath) {
8382
result.type = 'typeof ' + type.type;
84-
result.type2 = dom.create.typeof(type.type2);
8583
(result as any).importType = type.type;
8684
(result as any).importPath = type.importPath;
8785
} else {
8886
result.type = 'any';
89-
result.type2 = 'any';
9087
}
9188
break;
9289
}
@@ -109,23 +106,20 @@ function getReactPropTypeFromExpression(node: any, instanceOfResolver: InstanceO
109106
return {
110107
name: 'instanceOf',
111108
type: node.arguments[0].name,
112-
type2: dom.create.namedTypeReference(node.arguments[0].name),
113109
importPath: instanceOfResolver(node.arguments[0].name)
114110
};
115111
case 'arrayOf':
116112
const arrayType = getTypeFromPropType(node.arguments[0], instanceOfResolver);
117113
return {
118114
name: 'array',
119115
arrayType: arrayType.type,
120-
arrayType2: arrayType.type2
121116
};
122117
case 'oneOfType':
123118
const unionTypes = node.arguments[0].elements.map((element: IASTNode) =>
124119
getTypeFromPropType(element, instanceOfResolver));
125120
return {
126121
name: 'union',
127-
types: unionTypes.map((type: any) => type.type),
128-
types2: unionTypes.map((type: any) => type.type2)
122+
types: unionTypes.map((type: any) => type.type)
129123
};
130124
}
131125
}

Diff for: src/deprecated.ts

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import * as astqts from 'astq';
2+
const ASTQ: typeof astqts.ASTQ = astqts as any;
3+
import { IOptions, InstanceOfResolver } from './index';
4+
import { Generator } from './generator';
5+
import { parsePropTypes } from './analyzer';
6+
7+
export enum ExportType {
8+
default,
9+
named
10+
}
11+
12+
export interface IProp {
13+
type: string;
14+
optional: boolean;
15+
importType?: string;
16+
importPath?: string;
17+
documentation?: string;
18+
}
19+
20+
export interface IPropTypes {
21+
[name: string]: IProp;
22+
}
23+
24+
export function generateTypings(moduleName: string|null, ast: any, options: IOptions): string {
25+
const parsingResult = parseAst(ast, options.instanceOfResolver);
26+
return deprecatedGenerator(options.generator as Generator, moduleName, parsingResult);
27+
}
28+
29+
function deprecatedGenerator(generator: Generator, moduleName: string|null,
30+
{exportType, classname, propTypes}: IParsingResult): string {
31+
const componentName = classname || 'Anonymous';
32+
const generateTypings = () => {
33+
generator.import('* as React', 'react');
34+
if (propTypes) {
35+
Object.keys(propTypes).forEach(propName => {
36+
const prop = propTypes[propName];
37+
if (prop.importType && prop.importPath) {
38+
generator.import(prop.importType, prop.importPath);
39+
}
40+
});
41+
}
42+
generator.nl();
43+
generator.props(componentName, propTypes);
44+
generator.nl();
45+
generator.exportDeclaration(exportType, () => {
46+
generator.class(componentName, !!propTypes);
47+
});
48+
};
49+
50+
if (moduleName === null) {
51+
generateTypings();
52+
} else {
53+
generator.declareModule(moduleName, generateTypings);
54+
}
55+
return generator.toString();
56+
}
57+
58+
/**
59+
* @internal
60+
*/
61+
export interface IParsingResult {
62+
exportType: ExportType;
63+
classname: string|undefined;
64+
functionname: string|undefined;
65+
propTypes: IPropTypes;
66+
}
67+
68+
function parseAst(ast: any, instanceOfResolver?: InstanceOfResolver): IParsingResult {
69+
let exportType: ExportType|undefined;
70+
let functionname: string|undefined;
71+
let propTypes: IPropTypes|undefined;
72+
73+
let classname = getClassName(ast);
74+
if (classname) {
75+
propTypes = getEs7StyleClassPropTypes(ast, classname, instanceOfResolver);
76+
exportType = getClassExportType(ast, classname);
77+
}
78+
if (!propTypes) {
79+
const componentName = getComponentNameByPropTypeAssignment(ast);
80+
if (componentName) {
81+
const astq = new ASTQ();
82+
const exportTypeNodes = astq.query(ast, `
83+
//ExportNamedDeclaration // VariableDeclarator[
84+
/:id Identifier[@name=='${componentName}'] &&
85+
/:init ArrowFunctionExpression // JSXElement
86+
],
87+
//ExportNamedDeclaration // FunctionDeclaration[/:id Identifier[@name == '${componentName}']] // JSXElement,
88+
//ExportDefaultDeclaration // AssignmentExpression[/:left Identifier[@name == '${componentName}']]
89+
// ArrowFunctionExpression // JSXElement,
90+
//ExportDefaultDeclaration // FunctionDeclaration[/:id Identifier[@name == '${componentName}']] // JSXElement
91+
`);
92+
if (exportTypeNodes.length > 0) {
93+
functionname = componentName;
94+
exportType = ExportType.named;
95+
}
96+
propTypes = getPropTypesFromAssignment(ast, componentName, instanceOfResolver);
97+
}
98+
if (!exportType) {
99+
const astq = new ASTQ();
100+
const commonJsExports = astq.query(ast, `
101+
// AssignmentExpression[
102+
/:left MemberExpression[
103+
/:object Identifier[@name == 'exports'] &&
104+
/:property Identifier[@name == 'default']
105+
] &&
106+
/:right Identifier[@name == '${componentName}']
107+
]
108+
`);
109+
if (commonJsExports.length > 0) {
110+
classname = componentName;
111+
exportType = ExportType.default;
112+
}
113+
}
114+
}
115+
116+
if (exportType === undefined) {
117+
throw new Error('No exported component found');
118+
}
119+
return {
120+
exportType,
121+
classname,
122+
functionname,
123+
propTypes: propTypes || {}
124+
};
125+
}
126+
127+
function getClassName(ast: any): string|undefined {
128+
const astq = new ASTQ();
129+
const classDeclarationNodes = astq.query(ast, `
130+
//ClassDeclaration[
131+
/:id Identifier[@name]
132+
]
133+
`);
134+
if (classDeclarationNodes.length > 0) {
135+
return classDeclarationNodes[0].id.name;
136+
}
137+
return undefined;
138+
}
139+
140+
function getEs7StyleClassPropTypes(ast: any, classname: string,
141+
instanceOfResolver?: InstanceOfResolver): IPropTypes|undefined {
142+
const astq = new ASTQ();
143+
const propTypesNodes = astq.query(ast, `
144+
//ClassDeclaration[/:id Identifier[@name == '${classname}']]
145+
//ClassProperty[/:key Identifier[@name == 'propTypes']]
146+
`);
147+
if (propTypesNodes.length > 0) {
148+
return parsePropTypes(propTypesNodes[0].value, instanceOfResolver);
149+
}
150+
return undefined;
151+
}
152+
153+
function getClassExportType(ast: any, classname: string): ExportType|undefined {
154+
const astq = new ASTQ();
155+
const exportTypeNodes = astq.query(ast, `
156+
//ExportNamedDeclaration [
157+
/ClassDeclaration [ /:id Identifier[@name=='${classname}'] ]
158+
],
159+
//ExportDefaultDeclaration [
160+
/ClassDeclaration [ /:id Identifier[@name=='${classname}'] ]
161+
]
162+
`);
163+
if (exportTypeNodes.length > 0) {
164+
return exportTypeNodes[0].type === 'ExportDefaultDeclaration' ? ExportType.default : ExportType.named;
165+
}
166+
return undefined;
167+
}
168+
169+
function getComponentNameByPropTypeAssignment(ast: any): string|undefined {
170+
const astq = new ASTQ();
171+
const componentNames = astq.query(ast, `
172+
//AssignmentExpression
173+
/:left MemberExpression[
174+
/:object Identifier &&
175+
/:property Identifier[@name == 'propTypes']
176+
]
177+
`);
178+
if (componentNames.length > 0) {
179+
return componentNames[0].object.name;
180+
}
181+
return undefined;
182+
}
183+
184+
function getPropTypesFromAssignment(ast: any, componentName: string,
185+
instanceOfResolver?: InstanceOfResolver): IPropTypes|undefined {
186+
const astq = new ASTQ();
187+
const propTypesNodes = astq.query(ast, `
188+
//AssignmentExpression[
189+
/:left MemberExpression[
190+
/:object Identifier[@name == '${componentName}'] &&
191+
/:property Identifier[@name == 'propTypes']
192+
]
193+
] /:right *
194+
`);
195+
if (propTypesNodes.length > 0) {
196+
return parsePropTypes(propTypesNodes[0], instanceOfResolver);
197+
}
198+
return undefined;
199+
}

0 commit comments

Comments
 (0)