Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use react-docgen-typescript #31

Merged
merged 2 commits into from
Jan 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,23 @@ Run CLI `connect` command using the plugin.
zeplin connect -p @zeplin/cli-connect-react-plugin
```

Zeplin CLI React Plugin uses [react-docgen](https://github.com/reactjs/react-docgen) to analyze and collect information from React components. For more details about the supported formats, see `react-docgen` [guidelines](https://github.com/reactjs/react-docgen#guidelines-for-default-resolvers-and-handlers).
Zeplin CLI React Plugin uses [react-docgen](https://github.com/reactjs/react-docgen) and [react-docgen-typescript](https://github.com/styleguidist/react-docgen-typescript) to analyze and collect information from React components. For more details about the supported formats, see `react-docgen` [guidelines](https://github.com/reactjs/react-docgen#guidelines-for-default-resolvers-and-handlers) and `react-docgen-typescript` [examples](https://github.com/styleguidist/react-docgen-typescript#example).

You can choose to use either `react-docgen` or `react-docgen-typescript` for TypeScript in your plugin configurations.

```jsonc
{
...
"plugins" : [{
"name": "@zeplin/cli-connect-react-plugin",
"config": {
"tsDocgen": "react-docgen-typescript", // Uses react-docgen by default
"tsConfigPath": "/path/to/tsconfig.json" // Defaults to ./tsconfig.json
}
}],
...
}
```

## About Connected Components

Expand Down
42 changes: 38 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@types/jest": "^25.2.1",
"@types/node": "^13.13.5",
"@types/pug": "^2.0.4",
"@types/react": "^17.0.0",
"@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0",
"@zeplin/cli": "^1.0.4",
Expand All @@ -42,8 +43,9 @@
"typescript": "^3.9.7"
},
"dependencies": {
"fs-extra": "^9.0.0",
"fs-extra": "^9.0.1",
"pug": "^2.0.4",
"react-docgen": "^5.3.1"
"react-docgen": "^5.3.1",
"react-docgen-typescript": "^1.20.5"
}
}
87 changes: 78 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,48 @@
import { ConnectPlugin, ComponentConfig, ComponentData, PrismLang } from "@zeplin/cli";
import {
ConnectPlugin, ComponentConfig, ComponentData, PrismLang, PluginContext
} from "@zeplin/cli";
import path from "path";
import pug from "pug";
import { readFile } from "fs-extra";
import { parse, PreparedComponentDoc } from "react-docgen";
import { readFile, pathExists } from "fs-extra";
import { ComponentDoc, parse, PreparedComponentDoc, Props } from "react-docgen";
import * as docgen from "react-docgen-typescript";

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

export default class implements ConnectPlugin {
supportedFileExtensions = [".js", ".jsx", ".ts", ".tsx"];
tsExtensions = [".ts", ".tsx"];
config: ReactPluginConfig = {};

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

// eslint-disable-next-line require-await
async init(pluginContext: PluginContext): Promise<void> {
this.config = pluginContext.config as unknown as ReactPluginConfig;
}

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

const rawReactDocs = parse(file, null, null, {
filename: path.resolve(context.path),
babelrc: false
});
const file = await readFile(filePath);

let rawReactDocs: docgen.ComponentDoc | ComponentDoc;
let propsFilter: (props: docgen.Props | Props, name: string) => boolean;

if (this.config.tsDocgen === "react-docgen-typescript" && this.tsExtensions.includes(path.extname(filePath))) {
({ rawReactDocs, propsFilter } = await this.parseUsingReactDocgenTypescript(filePath));
} else {
({ rawReactDocs, propsFilter } = this.parseUsingReactDocgen(file, filePath));
}

const rawProps = rawReactDocs.props || {};

const props = Object.keys(rawProps)
.filter(name => name !== "children")
.filter(name => rawProps[name].type || rawProps[name].tsType || rawProps[name].flowType)
.filter(name => propsFilter(rawProps, name))
.map(name => ({ name, value: rawProps[name] }));

const hasChildren = !!rawProps.children;
Expand Down Expand Up @@ -52,4 +72,53 @@ export default class implements ConnectPlugin {
private generateSnippet(preparedComponentDoc: PreparedComponentDoc): string {
return this.template(preparedComponentDoc);
}

private parseUsingReactDocgen(file: Buffer, filePath: string): {
rawReactDocs: ComponentDoc;
propsFilter: (props: docgen.Props | Props, name: string) => boolean;
} {
const rawReactDocs = parse(file, null, null, {
filename: filePath,
babelrc: false
});

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

return {
rawReactDocs,
propsFilter
};
}

private async parseUsingReactDocgenTypescript(filePath: string): Promise <{
rawReactDocs: docgen.ComponentDoc;
propsFilter: (props: docgen.Props | Props, name: string) => boolean;
}> {
const tsConfigPath = path.resolve(this.config.tsConfigPath || "./tsconfig.json");

const parserOpts: docgen.ParserOptions = {
shouldExtractLiteralValuesFromEnum: true,
shouldRemoveUndefinedFromOptional: true,
propFilter: {
skipPropsWithoutDoc: false
}
};

let parser;

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

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

return {
rawReactDocs,
propsFilter
};
}
}
88 changes: 84 additions & 4 deletions test/__snapshots__/typescript.test.ts.snap
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Connected Components React Plugin - TypeScript TSComponent.tsx snippet creation 1`] = `
exports[`Connected Components React Plugin - TypeScript Using react-docgen TSComponent.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
"snippet": "<MyComponent />",
}
`;

exports[`Connected Components React Plugin - TypeScript TSComponentWithChildren.tsx snippet creation 1`] = `
exports[`Connected Components React Plugin - TypeScript Using react-docgen TSComponentWithChildren.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
Expand All @@ -18,7 +18,7 @@ Object {
}
`;

exports[`Connected Components React Plugin - TypeScript TSComponentWithChildrenAndProps.tsx snippet creation 1`] = `
exports[`Connected Components React Plugin - TypeScript Using react-docgen TSComponentWithChildrenAndProps.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
Expand Down Expand Up @@ -47,7 +47,18 @@ Object {
}
`;

exports[`Connected Components React Plugin - TypeScript TSComponentWithProps.tsx snippet creation 1`] = `
exports[`Connected Components React Plugin - TypeScript Using react-docgen TSComponentWithImport.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
"snippet": "<MyComponent
message={string}>
{children}
</MyComponent>",
}
`;

exports[`Connected Components React Plugin - TypeScript Using react-docgen TSComponentWithProps.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
Expand All @@ -73,3 +84,72 @@ Object {
optional={OptionalType} />",
}
`;

exports[`Connected Components React Plugin - TypeScript Using react-docgen-typescript TSComponent.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
"snippet": "<TSComponent />",
}
`;

exports[`Connected Components React Plugin - TypeScript Using react-docgen-typescript TSComponentWithChildren.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
"snippet": "<TSComponentWithChildren />",
}
`;

exports[`Connected Components React Plugin - TypeScript Using react-docgen-typescript TSComponentWithChildrenAndProps.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
"snippet": "<TSComponentWithChildrenAndProps
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&lt;HTMLButtonElement, MouseEvent&gt;) =&gt; void}
hele={(event: MouseEvent&lt;HTMLButtonElement, MouseEvent&gt;) =&gt; void}
onChange={(id: number) =&gt; void}
optional={OptionalType} />",
}
`;

exports[`Connected Components React Plugin - TypeScript Using react-docgen-typescript TSComponentWithImport.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
"snippet": "<TSComponentWithImport
other={string}
message={string} />",
}
`;

exports[`Connected Components React Plugin - TypeScript Using react-docgen-typescript TSComponentWithProps.tsx snippet creation 1`] = `
Object {
"description": "General component description.",
"lang": "tsx",
"snippet": "<TSComponentWithProps
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&lt;HTMLButtonElement, MouseEvent&gt;) =&gt; void}
onChange={(id: number) =&gt; void}
optional={OptionalType} />",
}
`;
1 change: 1 addition & 0 deletions test/samples/typescript/TSComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export default class MyComponent extends React.Component<{}, {}> {

render() {
// ...
return;
}
}
1 change: 1 addition & 0 deletions test/samples/typescript/TSComponentWithChildren.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ export default class MyComponent extends React.Component<Props, {}> {

render() {
// ...
return;
}
}
3 changes: 2 additions & 1 deletion test/samples/typescript/TSComponentWithChildrenAndProps.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { Component } from 'react';
import React from 'react';

interface OptionalType {};

Expand Down Expand Up @@ -34,5 +34,6 @@ export default class MyComponent extends React.Component<Props, {}> {

render() {
// ...
return;
}
}
Loading