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

Commit

Permalink
feat: create definitions for components exported as object with compo…
Browse files Browse the repository at this point in the history
…nent properties
  • Loading branch information
renrizzolo authored and KnisterPeter committed Nov 12, 2021
1 parent 46ff9a5 commit 38603a0
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 14 deletions.
152 changes: 138 additions & 14 deletions src/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,13 @@ export function createTypings(
}
});
}
const alreadyDefined: string[] = [];

componentNames.forEach((componentName) => {
const exportType = getComponentExportType(ast, componentName);
const propTypes = getPropTypes(ast, componentName);
if (exportType) {
alreadyDefined.push(componentName);
createExportedTypes(
m,
ast,
Expand All @@ -88,6 +91,59 @@ export function createTypings(
}
});

// top level object variables
const componentObject = getComponentNamesByObject(ast, componentNames);

componentObject.forEach(({ name, properties = {} }) => {
const obj = dom.create.objectType([]);
let hasType;

Object.keys(properties).forEach((k) => {
const { key, value } = properties[k];
componentNames.forEach((componentName) => {
// if a property matches an existing component
// add it to the object definition
if (value.type === 'Identifier' && value.name === componentName) {
const exportType = getComponentExportType(ast, componentName);
const propTypes = getPropTypes(ast, value.name);
// if it was exported individually, it will already have been typed earlier
if (!alreadyDefined.includes(componentName)) {
createExportedTypes(
m,
ast,
value.name,
reactComponentName,
propTypes,
importedPropTypes,
exportType,
options
);
}

if (propTypes) {
hasType = true;
const type1 = dom.create.namedTypeReference(value.name);
const typeBase = dom.create.typeof(type1);
const b = dom.create.property(key.name, typeBase);
obj.members.push(b);
}
}
});
});
if (hasType) {
const exportType = getComponentExportType(ast, name);

const objConst = dom.create.const(name, obj);
m.members.push(objConst);

if (exportType === dom.DeclarationFlags.ExportDefault) {
m.members.push(dom.create.exportDefault(name));
} else {
objConst.flags = exportType;
}
}
});

if (moduleName === null) {
return m.members.map((member) => dom.emit(member)).join('');
} else {
Expand All @@ -102,7 +158,7 @@ function createExportedTypes(
reactComponentName: string | undefined,
propTypes: any,
importedPropTypes: ImportedPropTypes,
exportType: dom.DeclarationFlags,
exportType: dom.DeclarationFlags | undefined,
options: IOptions
): void {
const classComponent = isClassComponent(
Expand All @@ -123,13 +179,19 @@ function createExportedTypes(
}

if (classComponent) {
createExportedClassComponent(
m,
componentName,
reactComponentName,
exportType,
interf
);
if (!exportType) {
createClassComponent(m, componentName, reactComponentName, interf);
} else {
createExportedClassComponent(
m,
componentName,
reactComponentName,
exportType,
interf
);
}
} else if (!exportType) {
createFunctionalComponent(m, componentName, propTypes, interf);
} else {
createExportedFunctionalComponent(
m,
Expand All @@ -141,18 +203,16 @@ function createExportedTypes(
}
}

function createExportedClassComponent(
function createClassComponent(
m: dom.ModuleDeclaration,
componentName: string,
reactComponentName: string | undefined,
exportType: dom.DeclarationFlags,
interf: dom.InterfaceDeclaration
): void {
): dom.ClassDeclaration {
const classDecl = dom.create.class(componentName);
classDecl.baseType = dom.create.interface(
`React.${reactComponentName || 'Component'}<${interf.name}, any>`
);
classDecl.flags = exportType;
classDecl.members.push(
dom.create.method(
'render',
Expand All @@ -161,21 +221,53 @@ function createExportedClassComponent(
)
);
m.members.push(classDecl);
return classDecl;
}

function createExportedFunctionalComponent(
function createExportedClassComponent(
m: dom.ModuleDeclaration,
componentName: string,
propTypes: any,
reactComponentName: string | undefined,
exportType: dom.DeclarationFlags,
interf: dom.InterfaceDeclaration
): void {
const classDecl = createClassComponent(
m,
componentName,
reactComponentName,
interf
);
classDecl.flags = exportType;
}

function createFunctionalComponent(
m: dom.ModuleDeclaration,
componentName: string,
propTypes: any,
interf: dom.InterfaceDeclaration
): dom.ConstDeclaration {
const typeDecl = dom.create.namedTypeReference(
`React.FC${propTypes ? `<${interf.name}>` : ''}`
);
const constDecl = dom.create.const(componentName, typeDecl);
m.members.push(constDecl);

return constDecl;
}

function createExportedFunctionalComponent(
m: dom.ModuleDeclaration,
componentName: string,
propTypes: any,
exportType: dom.DeclarationFlags,
interf: dom.InterfaceDeclaration
): void {
const constDecl = createFunctionalComponent(
m,
componentName,
propTypes,
interf
);
if (exportType === dom.DeclarationFlags.ExportDefault) {
m.members.push(dom.create.exportDefault(componentName));
} else {
Expand Down Expand Up @@ -488,6 +580,38 @@ function getComponentNamesByJsxInBody(ast: AstQuery): string[] {
return [];
}

function getComponentNamesByObject(
ast: AstQuery,
componentNames: string[]
): { name: string; properties: object | undefined }[] {
const res = ast.query(`
/:program *
/ VariableDeclaration
/ VariableDeclarator[
/:init ObjectExpression
// ObjectProperty
],
/:program *
/ ExportNamedDeclaration
// VariableDeclarator[
/:init ObjectExpression
// ObjectProperty
]
`);
if (res.length > 0) {
return (
res
// only interested in components that exist
.filter((match) => !componentNames.includes(match))
.map((match) => ({
name: match.id ? match.id.name : '',
properties: match.init?.properties,
}))
);
}
return [];
}

function getPropTypes(ast: AstQuery, componentName: string): any | undefined {
const propTypes =
getPropTypesFromAssignment(ast, componentName) ||
Expand Down
24 changes: 24 additions & 0 deletions tests/multiple-components-object-default.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
declare module 'component' {
import * as React from 'react';

export interface Component2Props {
optionalString?: string;
}

export const Component2: React.FC<Component2Props>;

export interface ComponentProps {
optionalAny?: any;
}

const Component: React.FC<ComponentProps>;

const Composed: {
Component: typeof Component;
Asdf: typeof Component2;
};

export default Composed;

}

19 changes: 19 additions & 0 deletions tests/multiple-components-object-default.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import * as React from 'react';

const Component = ({ optionalAny }) => <div />;
Component.propTypes = {
optionalAny: React.PropTypes.any,
};

export const Component2 = () => <div />;

Component2.propTypes = {
optionalString: React.PropTypes.string,
};

const Composed = {
Component,
Asdf: Component2,
};

export default Composed;
20 changes: 20 additions & 0 deletions tests/multiple-components-object.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
declare module 'component' {
import * as React from 'react';

export interface ComponentProps {
optionalAny?: any;
}

const Component: React.FC<ComponentProps>;

export interface Component2Props {
optionalString?: string;
}

const Component2: React.FC<Component2Props>;

export const Composed: {
Component: typeof Component;
Component2: typeof Component2;
};
}
17 changes: 17 additions & 0 deletions tests/multiple-components-object.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as React from "react";

const Component = ({ optionalAny }) => <div />;
Component.propTypes = {
optionalAny: React.PropTypes.any,
};

const Component2 = () => <div />;

Component2.propTypes = {
optionalString: React.PropTypes.string,
};

export const Composed = {
Component,
Component2,
};
16 changes: 16 additions & 0 deletions tests/parsing-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ test('Parsing should create definition from class extending Component', (t) => {
'import-react-component.d.ts'
);
});
test('Parsing should create definition from component exported as an object of components', (t) => {
compare(
t,
'component',
'multiple-components-object.jsx',
'multiple-components-object.d.ts'
);
});
test("Parsing should create definition from default export that's an object of components", (t) => {
compare(
t,
'component',
'multiple-components-object-default.jsx',
'multiple-components-object-default.d.ts'
);
});
test('Parsing should create definition from class import PropTypes and instanceOf dependency', (t) => {
compare(
t,
Expand Down

0 comments on commit 38603a0

Please sign in to comment.