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

Refactor resolvers and add new ChainResolver #714

Merged
merged 1 commit into from
Dec 29, 2022
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
8 changes: 8 additions & 0 deletions .changeset/friendly-timers-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@react-docgen/cli': minor
---

`--resolver` option can now be used multiple times.

If used multiple times the resolvers will be chained in the defined order and
all components from all resolvers will be used.
14 changes: 14 additions & 0 deletions .changeset/hungry-crabs-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'react-docgen': minor
---

Add new ChainResolver to allow multiple resolvers to be chained.

```ts
import { builtinResolvers } from 'react-docgen';

const { ChainResolver } = builtinResolvers;
const resolver = new ChainResolver([resolver1, resolver2], {
chainingLogic: ChainResolver.Logic.ALL, // or ChainResolver.Logic.FIRST_FOUND,
});
```
23 changes: 23 additions & 0 deletions .changeset/quiet-spiders-grab.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'react-docgen': minor
---

Allow resolvers to be classes in addition to functions.

```ts
import type { ResolverClass, ResolverFunction } from 'react-dcogen';

// This was the only option until now
const functionResolver: ResolverFunction = (file: FileState) => {
//needs to return array of found components
};

// This is the new class resolver
class MyResolver implements ResolverClass {
resolve(file: FileState) {
//needs to return array of found components
}
}

const classResolver = new MyResolver();
```
6 changes: 6 additions & 0 deletions .changeset/shy-papayas-attack.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@react-docgen/cli': major
---

Renamed `--handlers` option to`--handler`. This unifies all options to be
singular.
31 changes: 31 additions & 0 deletions .changeset/thin-tables-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
---
'react-docgen': major
---

Renamed and migrated built-in resolvers to classes.

- `findAllComponentDefinitions` was renamed to `FindAllDefinitionsResolver` and
is now a class.

```diff
-const resolver = builtinResolvers.findAllComponentDefinitions
+const resolver = new builtinResolvers.FindAllDefinitionsResolver()
```

- `findAllExportedComponentDefinitions` was renamed to
`FindExportedDefinitionsResolver` and is now a class.

```diff
-const resolver = builtinResolvers.findAllExportedComponentDefinitions
+const resolver = new builtinResolvers.FindExportedDefinitionsResolver()
```

- `findExportedComponentDefinition` was removed.
Use`FindExportedDefinitionsResolver` with the `limit` option instead.

> This is still the default resolver.

```diff
-const resolver = builtinResolvers.findExportedComponentDefinition
+const resolver = new builtinResolvers.FindExportedDefinitionsResolver({ limit: 1 })
```
1 change: 1 addition & 0 deletions packages/react-docgen-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"scripts": {
"build": "rimraf dist/ && tsc",
"test": "vitest run",
"watch": "rimraf dist/ && tsc --watch"
},
"keywords": [
Expand Down
28 changes: 17 additions & 11 deletions packages/react-docgen-cli/src/commands/parse/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import outputError from './output/outputError.js';
import { resolve } from 'path';
import slash from 'slash';
import type { Documentation } from 'react-docgen';
import { ResolverConfigs } from './options/loadResolvers.js';

const debug = debugFactory('react-docgen:cli');

Expand All @@ -20,12 +21,14 @@ const defaultIgnoreGlobs = [
];

const defaultHandlers = Object.keys(builtinHandlers);
const defaultResolvers = ['find-exported-component'];

function collect(value: string, previous: string[]) {
if (
!previous ||
previous === defaultIgnoreGlobs ||
previous === defaultHandlers
previous === defaultHandlers ||
previous === defaultResolvers
) {
previous = [];
}
Expand All @@ -38,12 +41,12 @@ function collect(value: string, previous: string[]) {
interface CLIOptions {
defaultIgnores: boolean;
failOnWarning: boolean;
handlers?: string[];
handler?: string[];
ignore: string[];
importer?: string;
out?: string;
pretty: boolean;
resolver?: string;
resolver?: string[];
}

program
Expand Down Expand Up @@ -73,18 +76,21 @@ program
false,
)
.option(
'--resolver <resolver>',
'Built-in resolver name (findAllComponentDefinitions, findAllExportedComponentDefinitions, findExportedComponentDefinition) or path to a module that exports a resolver.',
'findExportedComponentDefinition',
'--resolver <resolvers>',
`Built-in resolver config (${Object.values(ResolverConfigs).join(
', ',
)}), package name or path to a module that exports a resolver. Can also be used multiple times. When used, no default handlers will be added.`,
collect,
defaultResolvers,
)
.option(
'--importer <importer>',
'Built-in importer name (fsImport, ignoreImporter) or path to a module that exports an importer.',
'Built-in importer name (fsImport, ignoreImporter), package name or path to a module that exports an importer.',
'fsImporter',
)
.option(
'--handlers <handlers>',
'Comma separated list of handlers to use. Can also be used multiple times. When used no default handlers will be added.',
'--handler <handlers>',
'Comma separated list of handlers to use. Can also be used multiple times. When used, no default handlers will be added.',
collect,
defaultHandlers,
)
Expand All @@ -93,7 +99,7 @@ program
const {
defaultIgnores,
failOnWarning,
handlers,
handler,
ignore,
importer,
out: output,
Expand All @@ -111,7 +117,7 @@ program
}

const options = await loadOptions({
handlers,
handler,
importer,
resolver,
});
Expand Down
30 changes: 11 additions & 19 deletions packages/react-docgen-cli/src/commands/parse/options/loadOptions.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
import type { Handler, Importer, Resolver } from 'react-docgen';
import {
builtinHandlers,
builtinImporters,
builtinResolvers,
} from 'react-docgen';
import { builtinHandlers, builtinImporters } from 'react-docgen';
import loadReactDocgenPlugin from './loadReactDocgenPlugin.js';
import loadResolvers from './loadResolvers.js';

export default async function loadOptions(input: {
handlers: string[] | undefined;
handler: string[] | undefined;
importer: string | undefined;
resolver: string | undefined;
resolver: string[] | undefined;
}): Promise<{
handlers: Handler[] | undefined;
importer: Importer | undefined;
resolver: Resolver | undefined;
}> {
const resolver =
input.resolver && input.resolver.length !== 0
? await loadReactDocgenPlugin<Resolver>(
input.resolver,
'resolver',
builtinResolvers,
)
: undefined;

const importer =
input.importer && input.importer.length !== 0
? await loadReactDocgenPlugin<Importer>(
Expand All @@ -33,9 +21,9 @@ export default async function loadOptions(input: {
)
: undefined;

const handlers = input.handlers
const handlers = input.handler
? await Promise.all(
input.handlers.map(async handler => {
input.handler.map(async handler => {
return await loadReactDocgenPlugin<Handler>(
handler,
'handler',
Expand All @@ -45,5 +33,9 @@ export default async function loadOptions(input: {
)
: undefined;

return { handlers, importer, resolver };
return {
handlers,
importer,
resolver: await loadResolvers(input.resolver),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ import importFile from '../../../utils/importFile.js';
export default async function loadReactDocgenPlugin<T>(
input: string,
name: string,
builtins: Record<string, T>,
builtins?: Record<string, T>,
): Promise<T> {
if (builtins[input]) {
if (builtins?.[input]) {
return builtins[input];
}

const path = resolve(process.cwd(), input);
// Maybe it is local path or a package
const importer: T | undefined =
const plugin: T | undefined =
(await importFile(path)) ?? (await importFile(input));

if (importer) {
return importer;
if (plugin) {
return plugin;
}

throw new Error(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Resolver } from 'react-docgen';
import { builtinResolvers } from 'react-docgen';
import loadReactDocgenPlugin from './loadReactDocgenPlugin.js';

const { ChainResolver } = builtinResolvers;

export enum ResolverConfigs {
FindAll = 'find-all-components',
FindAllExported = 'find-all-exported-components',
FindExported = 'find-exported-component',
}

async function loadResolver(input: string): Promise<Resolver> {
if (input === ResolverConfigs.FindAll) {
return new builtinResolvers.FindAllDefinitionsResolver();
} else if (input === ResolverConfigs.FindAllExported) {
return new builtinResolvers.FindExportedDefinitionsResolver();
} else if (input === ResolverConfigs.FindExported) {
return new builtinResolvers.FindExportedDefinitionsResolver({
limit: 1,
});
}

const loadedResolver = await loadReactDocgenPlugin<Resolver>(
input,
'resolver',
);

// Check if it is a class constructor
// If it is we do not know how to construct the resolver so error instead
if (
typeof loadedResolver === 'function' &&
loadedResolver.toString().startsWith('class ')
) {
throw new Error(
`The provided resolver '${input}' is not a function or a class instance but instead a class.` +
' To solve this please make sure to provide a path to a file that returns a class instance.',
);
}

return loadedResolver;
}

export default async function loadResolvers(
input: string[] | undefined,
): Promise<Resolver | undefined> {
if (!input || input.length === 0) {
return;
}

if (input.length > 1) {
return new ChainResolver(await Promise.all(input.map(loadResolver)), {
chainingLogic: ChainResolver.Logic.ALL,
});
}

return loadResolver(input[0]);
}

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

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

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const code = `
({
displayName: 'Custom',
})
`;

const customResolver = class {
resolve(file) {
const newFile = file.parse(code, 'x.js');
let path;

newFile.traverse({
Program(p) {
path = p;
p.stop();
}
});

return [path.get('body')[0].get('expression')];
}
}

module.exports = new customResolver();
Loading