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

Commit 38603a0

Browse files
renrizzoloKnisterPeter
authored andcommitted
feat: create definitions for components exported as object with component properties
1 parent 46ff9a5 commit 38603a0

6 files changed

+234
-14
lines changed

Diff for: src/typings.ts

+138-14
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,13 @@ export function createTypings(
7171
}
7272
});
7373
}
74+
const alreadyDefined: string[] = [];
75+
7476
componentNames.forEach((componentName) => {
7577
const exportType = getComponentExportType(ast, componentName);
7678
const propTypes = getPropTypes(ast, componentName);
7779
if (exportType) {
80+
alreadyDefined.push(componentName);
7881
createExportedTypes(
7982
m,
8083
ast,
@@ -88,6 +91,59 @@ export function createTypings(
8891
}
8992
});
9093

94+
// top level object variables
95+
const componentObject = getComponentNamesByObject(ast, componentNames);
96+
97+
componentObject.forEach(({ name, properties = {} }) => {
98+
const obj = dom.create.objectType([]);
99+
let hasType;
100+
101+
Object.keys(properties).forEach((k) => {
102+
const { key, value } = properties[k];
103+
componentNames.forEach((componentName) => {
104+
// if a property matches an existing component
105+
// add it to the object definition
106+
if (value.type === 'Identifier' && value.name === componentName) {
107+
const exportType = getComponentExportType(ast, componentName);
108+
const propTypes = getPropTypes(ast, value.name);
109+
// if it was exported individually, it will already have been typed earlier
110+
if (!alreadyDefined.includes(componentName)) {
111+
createExportedTypes(
112+
m,
113+
ast,
114+
value.name,
115+
reactComponentName,
116+
propTypes,
117+
importedPropTypes,
118+
exportType,
119+
options
120+
);
121+
}
122+
123+
if (propTypes) {
124+
hasType = true;
125+
const type1 = dom.create.namedTypeReference(value.name);
126+
const typeBase = dom.create.typeof(type1);
127+
const b = dom.create.property(key.name, typeBase);
128+
obj.members.push(b);
129+
}
130+
}
131+
});
132+
});
133+
if (hasType) {
134+
const exportType = getComponentExportType(ast, name);
135+
136+
const objConst = dom.create.const(name, obj);
137+
m.members.push(objConst);
138+
139+
if (exportType === dom.DeclarationFlags.ExportDefault) {
140+
m.members.push(dom.create.exportDefault(name));
141+
} else {
142+
objConst.flags = exportType;
143+
}
144+
}
145+
});
146+
91147
if (moduleName === null) {
92148
return m.members.map((member) => dom.emit(member)).join('');
93149
} else {
@@ -102,7 +158,7 @@ function createExportedTypes(
102158
reactComponentName: string | undefined,
103159
propTypes: any,
104160
importedPropTypes: ImportedPropTypes,
105-
exportType: dom.DeclarationFlags,
161+
exportType: dom.DeclarationFlags | undefined,
106162
options: IOptions
107163
): void {
108164
const classComponent = isClassComponent(
@@ -123,13 +179,19 @@ function createExportedTypes(
123179
}
124180

125181
if (classComponent) {
126-
createExportedClassComponent(
127-
m,
128-
componentName,
129-
reactComponentName,
130-
exportType,
131-
interf
132-
);
182+
if (!exportType) {
183+
createClassComponent(m, componentName, reactComponentName, interf);
184+
} else {
185+
createExportedClassComponent(
186+
m,
187+
componentName,
188+
reactComponentName,
189+
exportType,
190+
interf
191+
);
192+
}
193+
} else if (!exportType) {
194+
createFunctionalComponent(m, componentName, propTypes, interf);
133195
} else {
134196
createExportedFunctionalComponent(
135197
m,
@@ -141,18 +203,16 @@ function createExportedTypes(
141203
}
142204
}
143205

144-
function createExportedClassComponent(
206+
function createClassComponent(
145207
m: dom.ModuleDeclaration,
146208
componentName: string,
147209
reactComponentName: string | undefined,
148-
exportType: dom.DeclarationFlags,
149210
interf: dom.InterfaceDeclaration
150-
): void {
211+
): dom.ClassDeclaration {
151212
const classDecl = dom.create.class(componentName);
152213
classDecl.baseType = dom.create.interface(
153214
`React.${reactComponentName || 'Component'}<${interf.name}, any>`
154215
);
155-
classDecl.flags = exportType;
156216
classDecl.members.push(
157217
dom.create.method(
158218
'render',
@@ -161,21 +221,53 @@ function createExportedClassComponent(
161221
)
162222
);
163223
m.members.push(classDecl);
224+
return classDecl;
164225
}
165226

166-
function createExportedFunctionalComponent(
227+
function createExportedClassComponent(
167228
m: dom.ModuleDeclaration,
168229
componentName: string,
169-
propTypes: any,
230+
reactComponentName: string | undefined,
170231
exportType: dom.DeclarationFlags,
171232
interf: dom.InterfaceDeclaration
172233
): void {
234+
const classDecl = createClassComponent(
235+
m,
236+
componentName,
237+
reactComponentName,
238+
interf
239+
);
240+
classDecl.flags = exportType;
241+
}
242+
243+
function createFunctionalComponent(
244+
m: dom.ModuleDeclaration,
245+
componentName: string,
246+
propTypes: any,
247+
interf: dom.InterfaceDeclaration
248+
): dom.ConstDeclaration {
173249
const typeDecl = dom.create.namedTypeReference(
174250
`React.FC${propTypes ? `<${interf.name}>` : ''}`
175251
);
176252
const constDecl = dom.create.const(componentName, typeDecl);
177253
m.members.push(constDecl);
178254

255+
return constDecl;
256+
}
257+
258+
function createExportedFunctionalComponent(
259+
m: dom.ModuleDeclaration,
260+
componentName: string,
261+
propTypes: any,
262+
exportType: dom.DeclarationFlags,
263+
interf: dom.InterfaceDeclaration
264+
): void {
265+
const constDecl = createFunctionalComponent(
266+
m,
267+
componentName,
268+
propTypes,
269+
interf
270+
);
179271
if (exportType === dom.DeclarationFlags.ExportDefault) {
180272
m.members.push(dom.create.exportDefault(componentName));
181273
} else {
@@ -488,6 +580,38 @@ function getComponentNamesByJsxInBody(ast: AstQuery): string[] {
488580
return [];
489581
}
490582

583+
function getComponentNamesByObject(
584+
ast: AstQuery,
585+
componentNames: string[]
586+
): { name: string; properties: object | undefined }[] {
587+
const res = ast.query(`
588+
/:program *
589+
/ VariableDeclaration
590+
/ VariableDeclarator[
591+
/:init ObjectExpression
592+
// ObjectProperty
593+
],
594+
/:program *
595+
/ ExportNamedDeclaration
596+
// VariableDeclarator[
597+
/:init ObjectExpression
598+
// ObjectProperty
599+
]
600+
`);
601+
if (res.length > 0) {
602+
return (
603+
res
604+
// only interested in components that exist
605+
.filter((match) => !componentNames.includes(match))
606+
.map((match) => ({
607+
name: match.id ? match.id.name : '',
608+
properties: match.init?.properties,
609+
}))
610+
);
611+
}
612+
return [];
613+
}
614+
491615
function getPropTypes(ast: AstQuery, componentName: string): any | undefined {
492616
const propTypes =
493617
getPropTypesFromAssignment(ast, componentName) ||

Diff for: tests/multiple-components-object-default.d.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
declare module 'component' {
2+
import * as React from 'react';
3+
4+
export interface Component2Props {
5+
optionalString?: string;
6+
}
7+
8+
export const Component2: React.FC<Component2Props>;
9+
10+
export interface ComponentProps {
11+
optionalAny?: any;
12+
}
13+
14+
const Component: React.FC<ComponentProps>;
15+
16+
const Composed: {
17+
Component: typeof Component;
18+
Asdf: typeof Component2;
19+
};
20+
21+
export default Composed;
22+
23+
}
24+

Diff for: tests/multiple-components-object-default.jsx

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import * as React from 'react';
2+
3+
const Component = ({ optionalAny }) => <div />;
4+
Component.propTypes = {
5+
optionalAny: React.PropTypes.any,
6+
};
7+
8+
export const Component2 = () => <div />;
9+
10+
Component2.propTypes = {
11+
optionalString: React.PropTypes.string,
12+
};
13+
14+
const Composed = {
15+
Component,
16+
Asdf: Component2,
17+
};
18+
19+
export default Composed;

Diff for: tests/multiple-components-object.d.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
declare module 'component' {
2+
import * as React from 'react';
3+
4+
export interface ComponentProps {
5+
optionalAny?: any;
6+
}
7+
8+
const Component: React.FC<ComponentProps>;
9+
10+
export interface Component2Props {
11+
optionalString?: string;
12+
}
13+
14+
const Component2: React.FC<Component2Props>;
15+
16+
export const Composed: {
17+
Component: typeof Component;
18+
Component2: typeof Component2;
19+
};
20+
}

Diff for: tests/multiple-components-object.jsx

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as React from "react";
2+
3+
const Component = ({ optionalAny }) => <div />;
4+
Component.propTypes = {
5+
optionalAny: React.PropTypes.any,
6+
};
7+
8+
const Component2 = () => <div />;
9+
10+
Component2.propTypes = {
11+
optionalString: React.PropTypes.string,
12+
};
13+
14+
export const Composed = {
15+
Component,
16+
Component2,
17+
};

Diff for: tests/parsing-test.ts

+16
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,22 @@ test('Parsing should create definition from class extending Component', (t) => {
111111
'import-react-component.d.ts'
112112
);
113113
});
114+
test('Parsing should create definition from component exported as an object of components', (t) => {
115+
compare(
116+
t,
117+
'component',
118+
'multiple-components-object.jsx',
119+
'multiple-components-object.d.ts'
120+
);
121+
});
122+
test("Parsing should create definition from default export that's an object of components", (t) => {
123+
compare(
124+
t,
125+
'component',
126+
'multiple-components-object-default.jsx',
127+
'multiple-components-object-default.d.ts'
128+
);
129+
});
114130
test('Parsing should create definition from class import PropTypes and instanceOf dependency', (t) => {
115131
compare(
116132
t,

0 commit comments

Comments
 (0)