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

Commit c548b7b

Browse files
committed
feat: Added instanceOf proptypes
1 parent 44914b7 commit c548b7b

7 files changed

+182
-62
lines changed

Diff for: README.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ import * as react2dts from 'react-to-typescript-definitions';
3737
// react2dts.generateFromFile('<module-name>', '<path/to/react-component>');
3838
react2dts.generateFromFile('component', path.join(__dirname, 'component.jsx'));
3939

40-
// react2dts.generate('<module-name>', '<code of the component>');
41-
react2dts.generate('component', 'component-code');
40+
// react2dts.generateFromSource('<module-name>', '<code of the component>');
41+
react2dts.generateFromSource('component', 'component-code');
42+
43+
// react2dts.generateFromAst('<module-name>', babylonAstObject);
44+
react2dts.generateFromAst('component', babylonAstObject);
4245
```

Diff for: index.ts

+101-42
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
11
import * as fs from 'fs';
22
import * as babylon from 'babylon';
33

4+
type InstanceOfResolver = (name: string) => string;
5+
6+
export interface IOptions {
7+
/**
8+
* Resolves type names to import paths.
9+
*
10+
* @return Path to given name if resolveable, undefined otherwise
11+
*/
12+
instanceOfResolver?: InstanceOfResolver;
13+
/**
14+
* The writer generating .d.ts code with.
15+
*/
16+
writer?: Writer;
17+
}
18+
419
interface IASTNode {
520
type: string;
621
loc: Object;
722
[name: string]: any;
823
}
924

10-
interface IProp {
25+
export interface IProp {
1126
type: string;
1227
optional: boolean;
28+
importType?: string;
29+
importPath?: string;
1330
}
1431

15-
interface IPropTypes {
32+
export interface IPropTypes {
1633
[name: string]: IProp;
1734
}
1835

@@ -32,15 +49,15 @@ export function cli(options: any): void {
3249
console.error('Failed to specify --name parameter');
3350
process.exit(1);
3451
}
35-
process.stdout.write(generate(options.name, stdinCode.join('')));
52+
process.stdout.write(generateFromSource(options.name, stdinCode.join('')));
3653
});
3754
}
3855

39-
export function generateFromFile(name: string, path: string): string {
40-
return generate(name, fs.readFileSync(path).toString());
56+
export function generateFromFile(name: string, path: string, options?: IOptions): string {
57+
return generateFromSource(name, fs.readFileSync(path).toString(), options);
4158
}
4259

43-
export function generate(name: string, code: string): string {
60+
export function generateFromSource(name: string, code: string, options: IOptions = {}): string {
4461
const ast: any = babylon.parse(code, {
4562
sourceType: 'module',
4663
plugins: [
@@ -59,42 +76,69 @@ export function generate(name: string, code: string): string {
5976
'functionSent'
6077
]
6178
});
62-
const writer: Writer = new Writer();
79+
return generateFromAst(name, ast, options);
80+
}
81+
82+
const defaultInstanceOfResolver: InstanceOfResolver = (name: string): string => undefined;
83+
84+
export function generateFromAst(name: string, ast: any, options: IOptions = {}): string {
85+
const {classname, propTypes}: IParsingResult = parseAst(ast, options);
86+
const writer: Writer = options.writer || new Writer();
6387
writer.declareModule(name, () => {
6488
writer.import('* as React', 'react');
89+
if (propTypes) {
90+
Object.keys(propTypes).forEach((propName: string) => {
91+
const prop: IProp = propTypes[propName];
92+
if (prop.importPath) {
93+
writer.import(prop.importType, prop.importPath);
94+
}
95+
});
96+
}
6597
writer.nl();
66-
walk(ast.program, {
67-
'ExportDefaultDeclaration': (node: any) => {
68-
let classname: string;
69-
let propTypes: IPropTypes = undefined;
70-
walk(node, {
71-
'ClassDeclaration': (node: any) => {
72-
classname = node.id.name;
73-
walk(node.body, {
74-
'ClassProperty': (node: any) => {
75-
if (node.key.name == 'propTypes') {
76-
propTypes = {};
77-
walk(node.value, {
78-
'ObjectProperty': (node: any) => {
79-
propTypes[node.key.name] = getTypeFromPropType(node.value);
80-
}
81-
});
82-
}
83-
}
84-
});
85-
writer.props(classname, propTypes);
86-
writer.nl();
87-
}
88-
});
89-
writer.exportDefault(() => {
90-
writer.class(classname, !!propTypes);
91-
});
92-
}
98+
writer.props(classname, propTypes);
99+
writer.nl();
100+
writer.exportDefault(() => {
101+
writer.class(classname, !!propTypes);
93102
});
94103
});
95104
return writer.toString();
96105
}
97106

107+
interface IParsingResult {
108+
classname: string;
109+
propTypes: IPropTypes;
110+
}
111+
112+
function parseAst(ast: any, options: IOptions): IParsingResult {
113+
let classname: string;
114+
let propTypes: IPropTypes = undefined;
115+
walk(ast.program, {
116+
'ExportDefaultDeclaration': (node: IASTNode) => {
117+
walk(node, {
118+
'ClassDeclaration': (node: any) => {
119+
classname = node.id.name;
120+
walk(node.body, {
121+
'ClassProperty': (node: any) => {
122+
if (node.key.name == 'propTypes') {
123+
propTypes = {};
124+
walk(node.value, {
125+
'ObjectProperty': (node: any) => {
126+
propTypes[node.key.name] = getTypeFromPropType(node.value, options.instanceOfResolver);
127+
}
128+
});
129+
}
130+
}
131+
});
132+
}
133+
});
134+
}
135+
});
136+
return {
137+
classname,
138+
propTypes
139+
};
140+
}
141+
98142
function walk(node: IASTNode, handlers: any): void {
99143
if (isNode(node)) {
100144
if (typeof handlers[node.type] == 'function') {
@@ -117,45 +161,51 @@ function isNode(obj: IASTNode): boolean {
117161
return obj && typeof obj.type != 'undefined' && typeof obj.loc != 'undefined';
118162
}
119163

120-
function getReactPropTypeFromExpression(node: any): any {
164+
function getReactPropTypeFromExpression(node: any, instanceOfResolver: InstanceOfResolver): any {
121165
if (node.type == 'MemberExpression' && node.object.type == 'MemberExpression'
122166
&& node.object.object.name == 'React' && node.object.property.name == 'PropTypes') {
123167
return node.property;
124168
} else if (node.type == 'CallExpression') {
125-
const callType: any = getReactPropTypeFromExpression(node.callee);
169+
const callType: any = getReactPropTypeFromExpression(node.callee, instanceOfResolver);
126170
switch (callType.name) {
171+
case 'instanceOf':
172+
return {
173+
name: 'instanceOf',
174+
type: node.arguments[0].name,
175+
importPath: instanceOfResolver(node.arguments[0].name)
176+
};
127177
case 'arrayOf':
128178
return {
129179
name: 'array',
130-
arrayType: getTypeFromPropType(node.arguments[0]).type
180+
arrayType: getTypeFromPropType(node.arguments[0], instanceOfResolver).type
131181
};
132182
case 'oneOfType':
133183
return {
134184
name: 'union',
135185
types: node.arguments[0].elements.map((element: IASTNode) => {
136-
return getTypeFromPropType(element).type;
186+
return getTypeFromPropType(element, instanceOfResolver).type;
137187
})
138188
};
139189
}
140190
}
141191
return 'undefined';
142192
}
143193

144-
function isRequiredPropType(node: any): any {
194+
function isRequiredPropType(node: any, instanceOfResolver: InstanceOfResolver): any {
145195
const isRequired: boolean = node.type == 'MemberExpression' && node.property.name == 'isRequired';
146196
return {
147197
isRequired,
148-
type: getReactPropTypeFromExpression(isRequired ? node.object : node)
198+
type: getReactPropTypeFromExpression(isRequired ? node.object : node, instanceOfResolver)
149199
};
150200
}
151201

152-
export function getTypeFromPropType(node: IASTNode): IProp {
202+
export function getTypeFromPropType(node: IASTNode, instanceOfResolver: InstanceOfResolver = defaultInstanceOfResolver): IProp {
153203
const result: any = {
154204
type: 'any',
155205
optional: true
156206
};
157207
if (isNode(node)) {
158-
const {isRequired, type}: any = isRequiredPropType(node);
208+
const {isRequired, type}: any = isRequiredPropType(node, instanceOfResolver);
159209
result.optional = !isRequired;
160210
switch (type.name) {
161211
case 'any':
@@ -188,6 +238,15 @@ export function getTypeFromPropType(node: IASTNode): IProp {
188238
case 'union':
189239
result.type = type.types.map((unionType: string) => unionType).join('|');
190240
break;
241+
case 'instanceOf':
242+
if (type.importPath) {
243+
result.type = 'typeof ' + type.type;
244+
result.importType = type.type;
245+
result.importPath = type.importPath;
246+
} else {
247+
result.type = 'any';
248+
}
249+
break;
191250
}
192251
}
193252
return result;

0 commit comments

Comments
 (0)