Skip to content

Commit

Permalink
Add multi export support
Browse files Browse the repository at this point in the history
  • Loading branch information
yuqu committed Jan 23, 2021
1 parent 8384275 commit dfb151d
Show file tree
Hide file tree
Showing 14 changed files with 494 additions and 57 deletions.
104 changes: 63 additions & 41 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ import {
import updateNotifier from "update-notifier";
import { name as packageName, version as packageVersion } from "../package.json";

let reactTsDocgen: {
withCustomConfig: typeof withCustomConfig;
withDefaultConfig: typeof withDefaultConfig;
};

interface ReactPluginConfig {
tsDocgen: "react-docgen" | "react-docgen-typescript";
tsConfigPath: string;
reactDocgenResolver?: string;
}

const defaultReactDocgenResolver = "findAllExportedComponentDefinitions";
const availableReactDocgenResolvers = [
"findAllExportedComponentDefinitions",
"findExportedComponentDefinition",
"findAllComponentDefinitions"
];

updateNotifier({
pkg: {
name: packageName,
Expand All @@ -42,21 +45,34 @@ export default class implements ConnectPlugin {
tsDocgen: "react-docgen",
tsConfigPath: "./tsconfig.json"
};
reactTsDocgen: {
withCustomConfig: typeof withCustomConfig;
withDefaultConfig: typeof withDefaultConfig;
} | null = null;
resolver = require(`react-docgen/dist/resolver/${defaultReactDocgenResolver}`).default

template = pug.compileFile(path.join(__dirname, "template/snippet.pug"));

// eslint-disable-next-line require-await
async init(pluginContext: PluginContext): Promise<void> {
Object.assign(this.config, pluginContext.config);
this.logger = pluginContext.logger;
if (this.config.reactDocgenResolver) {
const { reactDocgenResolver } = this.config;
if (availableReactDocgenResolvers.includes("availableReactDocgenResolvers") &&
reactDocgenResolver !== "findAllExportedComponentDefinitions") {
this.logger.debug(`Setting react-docgen resolver to ${reactDocgenResolver}`);
this.resolver = require(`react-docgen/dist/resolver/${reactDocgenResolver}`).default;
}
}
}

async process(context: ComponentConfig): Promise<ComponentData> {
const filePath = path.resolve(context.path);

const file = await readFile(filePath);

let rawReactDocs: TSComponentDoc | ComponentDoc;
let rawReactDocs: TSComponentDoc[] | ComponentDoc[];
let propsFilter: (props: TSProps | Props, name: string) => boolean;

if (this.config.tsDocgen === "react-docgen-typescript" && this.tsExtensions.includes(path.extname(filePath))) {
Expand All @@ -67,35 +83,41 @@ export default class implements ConnectPlugin {
({ rawReactDocs, propsFilter } = this.parseUsingReactDocgen(file, filePath));
}

const rawProps = rawReactDocs.props || {};

const props = Object.keys(rawProps)
.filter(name => name !== "children")
.filter(name => propsFilter(rawProps, name))
.map(name => {
const prop = rawProps[name];
if (prop.type) {
// Required to remove \" from typescript literal types
prop.type.name = prop.type.name.replace(/"/g, "'");
if ("raw" in prop.type && prop.type.raw) {
prop.type.raw = prop.type.raw.replace(/"/g, "'");
}
}

return { name, value: prop };
const snippets: string[] = (rawReactDocs as Array<TSComponentDoc | ComponentDoc>)
.map(rrd => {
const rawProps = rrd.props || {};

const props = Object.keys(rawProps)
.filter(name => name !== "children")
.filter(name => propsFilter(rawProps, name))
.map(name => {
const prop = rawProps[name];
if (prop.type) {
// Required to remove \" from typescript literal types
prop.type.name = prop.type.name.replace(/"/g, "'");
if ("raw" in prop.type && prop.type.raw) {
prop.type.raw = prop.type.raw.replace(/"/g, "'");
}
}

return { name, value: prop };
});

const hasChildren = !!rawProps.children;

const snippet = this.generateSnippet({
description: rrd.description,
componentName: rrd.displayName,
props,
hasChildren
});

return snippet;
});

const hasChildren = !!rawProps.children;
const snippet = snippets.join("\n\n");

const snippet = this.generateSnippet({
description: rawReactDocs.description,
componentName: rawReactDocs.displayName,
props,
hasChildren
});

// TODO maybe generate a markdown propTable as description?
const { description } = rawReactDocs;
const [{ description }] = rawReactDocs;
const lang = this.tsExtensions.includes(path.extname(context.path))
? PrismLang.ReactTSX
: PrismLang.ReactJSX;
Expand All @@ -114,10 +136,10 @@ export default class implements ConnectPlugin {
}

private parseUsingReactDocgen(file: Buffer, filePath: string): {
rawReactDocs: ComponentDoc;
rawReactDocs: ComponentDoc[];
propsFilter: (props: TSProps | Props, name: string) => boolean;
} {
const rawReactDocs = parse(file, null, null, {
const rawReactDocs = parse(file, this.resolver, null, {
filename: filePath,
babelrc: false
});
Expand All @@ -126,13 +148,13 @@ export default class implements ConnectPlugin {
!!(props[name].type || props[name].tsType || props[name].flowType);

return {
rawReactDocs,
rawReactDocs: Array.isArray(rawReactDocs) ? rawReactDocs : [rawReactDocs],
propsFilter
};
}

private async parseUsingReactDocgenTypescript(filePath: string): Promise<{
rawReactDocs: TSComponentDoc;
rawReactDocs: TSComponentDoc[];
propsFilter: (props: TSProps | Props, name: string) => boolean;
}> {
const tsConfigPath = path.resolve(this.config.tsConfigPath);
Expand All @@ -147,17 +169,17 @@ export default class implements ConnectPlugin {

let parser;

if (!reactTsDocgen) {
if (!this.reactTsDocgen) {
this.logger?.debug("Importing react-docgen-typescript package");
reactTsDocgen = await import("react-docgen-typescript");
this.reactTsDocgen = await import("react-docgen-typescript");
}

if (await pathExists(tsConfigPath)) {
parser = reactTsDocgen.withCustomConfig(tsConfigPath, parserOpts);
parser = this.reactTsDocgen.withCustomConfig(tsConfigPath, parserOpts);
} else {
parser = reactTsDocgen.withDefaultConfig(parserOpts);
parser = this.reactTsDocgen.withDefaultConfig(parserOpts);
}
const [rawReactDocs] = parser.parse(filePath);
const rawReactDocs = parser.parse(filePath);

const propsFilter = (props: TSProps | Props, name: string): boolean => !!props[name].type;

Expand Down
4 changes: 2 additions & 2 deletions src/types/react-docgen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ declare module "react-docgen" {

// Just minimal type definition of the method to use it
export function parse(src: string | Buffer,
resolver?: null,
resolver?: string | unknown,
handlers?: null,
options?: { filename: string; babelrc: boolean }): ComponentDoc;
options?: { filename: string; babelrc: boolean }): ComponentDoc | ComponentDoc[];
}
19 changes: 19 additions & 0 deletions test/__snapshots__/flow.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,22 @@ Object {
obj={{ subvalue: boolean }} />",
}
`;

exports[`Connected Components React Plugin - Flow MultiExportFlowComponentWithProps.jsx snippet creation 1`] = `
Object {
"description": "Component 1 description. Only this one should be shown",
"lang": "jsx",
"snippet": "<MyComponent
primitive={number}
literalsAndUnion={'string' | 'otherstring' | number}
arr={Array<any>}
func={(value: string) => void}
noParameterName={string => void}
obj={{ subvalue: boolean }} />
<MyOtherComponent
primitive={number}
literalsAndUnion={'string' | 'otherstring' | number}
arr={Array<any>} />",
}
`;
17 changes: 16 additions & 1 deletion test/__snapshots__/functional.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Connected Components React Plugin - Functional ComponentWithChildren.jsx snippet creation 1`] = `
exports[`Connected Components React Plugin - Functional FunctionalComponentWithChildren.jsx snippet creation 1`] = `
Object {
"description": "",
"lang": "jsx",
Expand All @@ -17,3 +17,18 @@ Object {
"snippet": "<MyComponent />",
}
`;

exports[`Connected Components React Plugin - Functional MultiExportFunctionalComponentWithChildren.jsx snippet creation 1`] = `
Object {
"description": "Component 1 description. Only this one should be shown",
"lang": "jsx",
"snippet": "<MyComponent1>
{children}
</MyComponent1>
<MyComponent2
someParameter={PropTypes.string.isRequired}>
{children}
</MyComponent2>",
}
`;
71 changes: 71 additions & 0 deletions test/__snapshots__/propType.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,38 @@ Object {
}
`;

exports[`Connected Components React Plugin - PropTypes ComponentWithChildrenAndProps.jsx snippet creation with single export resolver 1`] = `
Object {
"description": "Component description.",
"lang": "jsx",
"snippet": "<MyComponent
optionalArray={array}
optionalBool={bool}
optionalFunc={func}
optionalNumber={number}
optionalObject={object}
optionalString={string}
optionalSymbol={symbol}
optionalNode={node}
optionalElement={element}
optionalElementType={elementType}
optionalFoo={instanceOf(Foo)}
optionalEnum={enum}
optionalUnion={union[string|number|instanceOf(Foo)]}
optionalArrayOf={arrayOf[number]}
optionalObjectOf={objectOf[number]}
optionalObjectWithShape={shape}
optionalObjectWithStrictShape={exact}
requiredFunc={func}
requiredAny={any}
customProp={() => {}}
customArrayProp={arrayOf[custom]}
customObjectOfProp={objectOf[custom]}>
{children}
</MyComponent>",
}
`;

exports[`Connected Components React Plugin - PropTypes ComponentWithMemoization.jsx snippet creation 1`] = `
Object {
"description": "Component description.",
Expand Down Expand Up @@ -111,3 +143,42 @@ Object {
customObjectOfProp={objectOf[custom]} />",
}
`;

exports[`Connected Components React Plugin - PropTypes MultiExportComponentWithProps.jsx snippet creation 1`] = `
Object {
"description": "Component 1 description. Only this one should be shown",
"lang": "jsx",
"snippet": "<MyComponent1
optionalArray={array}
optionalBool={bool}
optionalFunc={func}
optionalNumber={number}
optionalObject={object}
optionalString={string}
optionalSymbol={symbol}
optionalNode={node}
optionalElement={element}
optionalElementType={elementType}
optionalFoo={instanceOf(Foo)}
optionalEnum={enum}
optionalUnion={union[string|number|instanceOf(Foo)]}
optionalArrayOf={arrayOf[number]}
optionalObjectOf={objectOf[number]}
optionalObjectWithShape={shape}
optionalObjectWithStrictShape={exact}
requiredFunc={func}
requiredAny={any}
customProp={() => {}}
customArrayProp={arrayOf[custom]}
customObjectOfProp={objectOf[custom]} />
<MyComponent2
optionalArray={array}
optionalBool={bool}
optionalFunc={func}
optionalNumber={number}
optionalObject={object}
optionalString={string}
optionalSymbol={symbol} />",
}
`;
30 changes: 30 additions & 0 deletions test/__snapshots__/typescript.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,36 @@ Object {
}
`;

exports[`Connected Components React Plugin - TypeScript Using react-docgen-typescript MultiExportTSComponentWithProps.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
"snippet": "<MyComponent
message={string}
count={number}
disabled={boolean}
names={string[]}
status={'waiting' | 'success'}
obj={object}
obj2={{}}
obj3={{ id: string; title: string; }}
objArr={{ id: string; title: string; }[]}
onSomething={Function}
onClick={(event: MouseEvent<HTMLButtonElement, MouseEvent>) => void}
onChange={(id: number) => void}
optional={OptionalType} />
<MyOtherComponent
message={string}
count={number}
disabled={boolean}
names={string[]}
status={'waiting' | 'success'}
obj={object}
obj2={{}} />",
}
`;

exports[`Connected Components React Plugin - TypeScript Using react-docgen-typescript TSComponent.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
Expand Down
13 changes: 13 additions & 0 deletions test/flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,17 @@ describe("Connected Components React Plugin - Flow", () => {

expect(componentCode).toMatchSnapshot();
});

test("MultiExportFlowComponentWithProps.jsx snippet creation", async () => {
const processor = new Plugin();

const componentCode = await processor.process(
{
path: "test/samples/flow/MultiExportFlowComponentWithProps.jsx",
zeplinNames: []
}
);

expect(componentCode).toMatchSnapshot();
});
});
Loading

0 comments on commit dfb151d

Please sign in to comment.