diff --git a/.changeset/friendly-timers-tap.md b/.changeset/friendly-timers-tap.md new file mode 100644 index 00000000000..1c2e4ce7ed9 --- /dev/null +++ b/.changeset/friendly-timers-tap.md @@ -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. diff --git a/.changeset/hungry-crabs-scream.md b/.changeset/hungry-crabs-scream.md new file mode 100644 index 00000000000..e619c443e7a --- /dev/null +++ b/.changeset/hungry-crabs-scream.md @@ -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, +}); +``` diff --git a/.changeset/quiet-spiders-grab.md b/.changeset/quiet-spiders-grab.md new file mode 100644 index 00000000000..8fc4d84aa6b --- /dev/null +++ b/.changeset/quiet-spiders-grab.md @@ -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(); +``` diff --git a/.changeset/shy-papayas-attack.md b/.changeset/shy-papayas-attack.md new file mode 100644 index 00000000000..026d1ebeea4 --- /dev/null +++ b/.changeset/shy-papayas-attack.md @@ -0,0 +1,6 @@ +--- +'@react-docgen/cli': major +--- + +Renamed `--handlers` option to`--handler`. This unifies all options to be +singular. diff --git a/.changeset/thin-tables-sneeze.md b/.changeset/thin-tables-sneeze.md new file mode 100644 index 00000000000..75503965793 --- /dev/null +++ b/.changeset/thin-tables-sneeze.md @@ -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 }) + ``` diff --git a/packages/react-docgen-cli/package.json b/packages/react-docgen-cli/package.json index 6029e2132b2..bea26cc6edb 100644 --- a/packages/react-docgen-cli/package.json +++ b/packages/react-docgen-cli/package.json @@ -15,6 +15,7 @@ }, "scripts": { "build": "rimraf dist/ && tsc", + "test": "vitest run", "watch": "rimraf dist/ && tsc --watch" }, "keywords": [ diff --git a/packages/react-docgen-cli/src/commands/parse/command.ts b/packages/react-docgen-cli/src/commands/parse/command.ts index 05f86f7849a..878fac3c5ff 100644 --- a/packages/react-docgen-cli/src/commands/parse/command.ts +++ b/packages/react-docgen-cli/src/commands/parse/command.ts @@ -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'); @@ -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 = []; } @@ -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 @@ -73,18 +76,21 @@ program false, ) .option( - '--resolver ', - 'Built-in resolver name (findAllComponentDefinitions, findAllExportedComponentDefinitions, findExportedComponentDefinition) or path to a module that exports a resolver.', - 'findExportedComponentDefinition', + '--resolver ', + `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 ', - '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 ', - 'Comma separated list of handlers to use. Can also be used multiple times. When used no default handlers will be added.', + '--handler ', + 'Comma separated list of handlers to use. Can also be used multiple times. When used, no default handlers will be added.', collect, defaultHandlers, ) @@ -93,7 +99,7 @@ program const { defaultIgnores, failOnWarning, - handlers, + handler, ignore, importer, out: output, @@ -111,7 +117,7 @@ program } const options = await loadOptions({ - handlers, + handler, importer, resolver, }); diff --git a/packages/react-docgen-cli/src/commands/parse/options/loadOptions.ts b/packages/react-docgen-cli/src/commands/parse/options/loadOptions.ts index e17466529d8..23e83069d64 100644 --- a/packages/react-docgen-cli/src/commands/parse/options/loadOptions.ts +++ b/packages/react-docgen-cli/src/commands/parse/options/loadOptions.ts @@ -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( - input.resolver, - 'resolver', - builtinResolvers, - ) - : undefined; - const importer = input.importer && input.importer.length !== 0 ? await loadReactDocgenPlugin( @@ -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', @@ -45,5 +33,9 @@ export default async function loadOptions(input: { ) : undefined; - return { handlers, importer, resolver }; + return { + handlers, + importer, + resolver: await loadResolvers(input.resolver), + }; } diff --git a/packages/react-docgen-cli/src/commands/parse/options/loadReactDocgenPlugin.ts b/packages/react-docgen-cli/src/commands/parse/options/loadReactDocgenPlugin.ts index 317f7733fbd..7f2151d35d1 100644 --- a/packages/react-docgen-cli/src/commands/parse/options/loadReactDocgenPlugin.ts +++ b/packages/react-docgen-cli/src/commands/parse/options/loadReactDocgenPlugin.ts @@ -4,19 +4,19 @@ import importFile from '../../../utils/importFile.js'; export default async function loadReactDocgenPlugin( input: string, name: string, - builtins: Record, + builtins?: Record, ): Promise { - 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( diff --git a/packages/react-docgen-cli/src/commands/parse/options/loadResolvers.ts b/packages/react-docgen-cli/src/commands/parse/options/loadResolvers.ts new file mode 100644 index 00000000000..cb71b41b84c --- /dev/null +++ b/packages/react-docgen-cli/src/commands/parse/options/loadResolvers.ts @@ -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 { + 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( + 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 { + 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]); +} diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-cjs/Component.js b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/Component.js similarity index 100% rename from packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-cjs/Component.js rename to packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/Component.js diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver-class/index.js b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver-class/index.js new file mode 100644 index 00000000000..559f79ff687 --- /dev/null +++ b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver-class/index.js @@ -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 = customResolver; diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver-class/package.json b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver-class/package.json new file mode 100644 index 00000000000..8c629f16740 --- /dev/null +++ b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver-class/package.json @@ -0,0 +1,3 @@ +{ + "name": "test-react-docgen-resolver-class" +} diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver/index.js b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver/index.js new file mode 100644 index 00000000000..a55182ff122 --- /dev/null +++ b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver/index.js @@ -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(); diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-npm/node_modules/test-react-docgen-resolver/package.json b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver/package.json similarity index 100% rename from packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-npm/node_modules/test-react-docgen-resolver/package.json rename to packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/node_modules/test-react-docgen-resolver/package.json diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/resolver.cjs b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/resolver.cjs new file mode 100644 index 00000000000..a55182ff122 --- /dev/null +++ b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/resolver.cjs @@ -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(); diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/resolver.mjs b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/resolver.mjs new file mode 100644 index 00000000000..30b014dff87 --- /dev/null +++ b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-class/resolver.mjs @@ -0,0 +1,25 @@ +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')]; + } +} + +const resolver = new customResolver(); + +export default resolver; diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-esm/Component.js b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/Component.js similarity index 100% rename from packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-esm/Component.js rename to packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/Component.js diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-npm/node_modules/test-react-docgen-resolver/index.js b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/node_modules/test-react-docgen-resolver/index.js similarity index 100% rename from packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-npm/node_modules/test-react-docgen-resolver/index.js rename to packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/node_modules/test-react-docgen-resolver/index.js diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/node_modules/test-react-docgen-resolver/package.json b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/node_modules/test-react-docgen-resolver/package.json new file mode 100644 index 00000000000..8d3ed80b3d1 --- /dev/null +++ b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/node_modules/test-react-docgen-resolver/package.json @@ -0,0 +1,3 @@ +{ + "name": "test-react-docgen-resolver" +} diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-cjs/resolver.cjs b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/resolver.cjs similarity index 100% rename from packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-cjs/resolver.cjs rename to packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/resolver.cjs diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-esm/resolver.mjs b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/resolver.mjs similarity index 100% rename from packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-esm/resolver.mjs rename to packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-function/resolver.mjs diff --git a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-npm/Component.js b/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-npm/Component.js deleted file mode 100644 index 1a0ceba0d5f..00000000000 --- a/packages/react-docgen-cli/tests/integration/__fixtures__/custom-resolver-npm/Component.js +++ /dev/null @@ -1,8 +0,0 @@ -const React = require('react'); - -const Component = React.createClass({ - displayName: 'Component', - render: function () {}, -}); - -module.exports = Component; diff --git a/packages/react-docgen-cli/tests/integration/cli-test.ts b/packages/react-docgen-cli/tests/integration/cli-test.ts index 91a212d031b..fea04350065 100644 --- a/packages/react-docgen-cli/tests/integration/cli-test.ts +++ b/packages/react-docgen-cli/tests/integration/cli-test.ts @@ -1,54 +1,11 @@ -// NOTE: This test spawns a subprocesses that load the files from dist/, not -// src/. Before running this test run `build`. - -import { readFile, rm, stat } from 'fs/promises'; -import { dirname, join } from 'path'; -import type { ExecaError } from 'execa'; -import { execaNode } from 'execa'; -import copy from 'cpy'; -import { temporaryDirectory, temporaryFile } from 'tempy'; +import { readFile } from 'fs/promises'; +import { join } from 'path'; +import { temporaryFile } from 'tempy'; import { describe, expect, test } from 'vitest'; -import { fileURLToPath } from 'url'; -import { - builtinHandlers, - builtinImporters, - builtinResolvers, -} from 'react-docgen'; - -const __dir = dirname(fileURLToPath(import.meta.url)); - -const fixtureDir = join(__dir, '__fixtures__'); -const cliBinary = join(__dir, '../../dist/cli.js'); +import { builtinHandlers, builtinImporters } from 'react-docgen'; +import withFixture from './utils/withFixture'; describe('cli', () => { - async function withFixture( - fixture: string, - callback: (api: { - dir: string; - run: ( - args: readonly string[], - ) => Promise<{ stdout: string; stderr: string; exitCode: number }>; - }) => Promise, - ): Promise { - const tempDir = temporaryDirectory(); - - async function run(args: readonly string[]) { - try { - return await execaNode(cliBinary, args, { - cwd: tempDir, - }); - } catch (error) { - return error as ExecaError; - } - } - - await stat(join(fixtureDir, fixture)); - - await copy(join(fixtureDir, fixture, '**/*'), tempDir, {}); - await callback({ dir: tempDir, run }); - await rm(tempDir, { force: true, recursive: true }); - } - describe('glob', () => { test('reads files provided as command line arguments', async () => { await withFixture('basic', async ({ dir, run }) => { @@ -215,104 +172,6 @@ describe('cli', () => { }); }); - describe('resolver', () => { - describe('accepts the names of builtin resolvers', () => { - test.each(Object.keys(builtinResolvers))('%s', async importer => { - await withFixture('basic', async ({ dir, run }) => { - const { stdout, stderr } = await run([ - `--resolver=${importer}`, - `${dir}/Component.js`, - ]); - - expect(stderr).toBe(''); - expect(stdout).toContain('Component'); - expect(() => JSON.parse(stdout)).not.toThrowError(); - }); - }); - }); - - describe('custom resolver', () => { - test('accepts an absolute local CommonJS path', async () => { - await withFixture('custom-resolver-cjs', async ({ dir, run }) => { - const { stdout, stderr } = await run([ - `--resolver=${join(dir, 'resolver.cjs')}`, - `${dir}/Component.js`, - ]); - - expect(stderr).toBe(''); - expect(stdout).toContain('Custom'); - expect(() => JSON.parse(stdout)).not.toThrowError(); - }); - }); - - test('accepts a relative local CommonJS path', async () => { - await withFixture('custom-resolver-cjs', async ({ dir, run }) => { - const { stdout, stderr } = await run([ - '--resolver', - './resolver.cjs', - `${dir}/Component.js`, - ]); - - expect(stderr).toBe(''); - expect(stdout).toContain('Custom'); - expect(() => JSON.parse(stdout)).not.toThrowError(); - }); - }); - - test('accepts an absolute local ESM path', async () => { - await withFixture('custom-resolver-esm', async ({ dir, run }) => { - const { stdout, stderr } = await run([ - `--resolver=${join(dir, 'resolver.mjs')}`, - `${dir}/Component.js`, - ]); - - expect(stderr).toBe(''); - expect(stdout).toContain('Custom'); - expect(() => JSON.parse(stdout)).not.toThrowError(); - }); - }); - - test('accepts a relative local ESM path', async () => { - await withFixture('custom-resolver-esm', async ({ dir, run }) => { - const { stdout, stderr } = await run([ - '--resolver', - './resolver.mjs', - `${dir}/Component.js`, - ]); - - expect(stderr).toBe(''); - expect(stdout).toContain('Custom'); - expect(() => JSON.parse(stdout)).not.toThrowError(); - }); - }); - - test('accepts a npm package', async () => { - await withFixture('custom-resolver-npm', async ({ dir, run }) => { - const { stdout, stderr } = await run([ - '--resolver=test-react-docgen-resolver', - `${dir}/Component.js`, - ]); - - expect(stderr).toBe(''); - expect(stdout).toContain('Custom'); - expect(() => JSON.parse(stdout)).not.toThrowError(); - }); - }); - - test('throws error when not found', async () => { - await withFixture('basic', async ({ dir, run }) => { - const { stdout, stderr } = await run([ - '--resolver=does-not-exist', - `${dir}/Component.js`, - ]); - - expect(stderr).toContain('Unknown resolver: "does-not-exist"'); - expect(stdout).toBe(''); - }); - }); - }); - }); - describe('importer', () => { describe('accepts the names of builtin importers', () => { test.each(Object.keys(builtinImporters))('%s', async importer => { @@ -416,7 +275,7 @@ describe('cli', () => { test.each(Object.keys(builtinHandlers))('%s', async importer => { await withFixture('basic', async ({ dir, run }) => { const { stdout, stderr } = await run([ - `--handlers=${importer}`, + `--handler=${importer}`, `${dir}/Component.js`, ]); @@ -431,9 +290,9 @@ describe('cli', () => { test('multiple handlers arguments', async () => { await withFixture('basic', async ({ dir, run }) => { const { stdout, stderr } = await run([ - `--handlers=displayNameHandler`, - `--handlers=componentDocblockHandler`, - `--handlers=componentMethodsHandler`, + `--handler=displayNameHandler`, + `--handler=componentDocblockHandler`, + `--handler=componentMethodsHandler`, `${dir}/Component.js`, ]); @@ -448,7 +307,7 @@ describe('cli', () => { test('multiple handlers comma separated', async () => { await withFixture('basic', async ({ dir, run }) => { const { stdout, stderr } = await run([ - `--handlers=displayNameHandler,componentDocblockHandler,componentMethodsHandler`, + `--handler=displayNameHandler,componentDocblockHandler,componentMethodsHandler`, `${dir}/Component.js`, ]); @@ -465,7 +324,7 @@ describe('cli', () => { test('accepts an absolute local CommonJS path', async () => { await withFixture('custom-handler-cjs', async ({ dir, run }) => { const { stdout, stderr } = await run([ - `--handlers=${join(dir, 'handler.cjs')}`, + `--handler=${join(dir, 'handler.cjs')}`, `${dir}/Component.js`, ]); @@ -478,7 +337,7 @@ describe('cli', () => { test('accepts a relative local CommonJS path', async () => { await withFixture('custom-handler-cjs', async ({ dir, run }) => { const { stdout, stderr } = await run([ - '--handlers', + '--handler', './handler.cjs', `${dir}/Component.js`, ]); @@ -492,7 +351,7 @@ describe('cli', () => { test('accepts an absolute local ESM path', async () => { await withFixture('custom-handler-esm', async ({ dir, run }) => { const { stdout, stderr } = await run([ - `--handlers=${join(dir, 'handler.mjs')}`, + `--handler=${join(dir, 'handler.mjs')}`, `${dir}/Component.js`, ]); @@ -505,7 +364,7 @@ describe('cli', () => { test('accepts a relative local ESM path', async () => { await withFixture('custom-handler-esm', async ({ dir, run }) => { const { stdout, stderr } = await run([ - '--handlers', + '--handler', './handler.mjs', `${dir}/Component.js`, ]); @@ -519,7 +378,7 @@ describe('cli', () => { test('accepts a npm package', async () => { await withFixture('custom-handler-npm', async ({ dir, run }) => { const { stdout, stderr } = await run([ - '--handlers=test-react-docgen-handler', + '--handler=test-react-docgen-handler', `${dir}/Component.js`, ]); @@ -532,7 +391,7 @@ describe('cli', () => { test('throws error when not found', async () => { await withFixture('basic', async ({ dir, run }) => { const { stdout, stderr } = await run([ - '--handlers=does-not-exist', + '--handler=does-not-exist', `${dir}/Component.js`, ]); diff --git a/packages/react-docgen-cli/tests/integration/resolver-test.ts b/packages/react-docgen-cli/tests/integration/resolver-test.ts new file mode 100644 index 00000000000..83932a6955b --- /dev/null +++ b/packages/react-docgen-cli/tests/integration/resolver-test.ts @@ -0,0 +1,186 @@ +import { join } from 'path'; +import { describe, expect, test } from 'vitest'; +import { ResolverConfigs } from '../../src/commands/parse/options/loadResolvers.js'; +import withFixture from './utils/withFixture'; + +describe('resolver', () => { + describe('accepts the names of builtin resolver configs', () => { + test.each(Object.values(ResolverConfigs))('%s', async importer => { + await withFixture('basic', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + `--resolver=${importer}`, + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Component'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + }); + + describe('custom resolver', () => { + describe('function', () => { + test('accepts an absolute local CommonJS path', async () => { + await withFixture('custom-resolver-function', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + `--resolver=${join(dir, 'resolver.cjs')}`, + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('accepts a relative local CommonJS path', async () => { + await withFixture('custom-resolver-function', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + '--resolver', + './resolver.cjs', + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('accepts an absolute local ESM path', async () => { + await withFixture('custom-resolver-function', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + `--resolver=${join(dir, 'resolver.mjs')}`, + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('accepts a relative local ESM path', async () => { + await withFixture('custom-resolver-function', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + '--resolver', + './resolver.mjs', + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('accepts a npm package', async () => { + await withFixture('custom-resolver-function', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + '--resolver=test-react-docgen-resolver', + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + }); + describe('class', () => { + test('accepts an absolute local CommonJS path', async () => { + await withFixture('custom-resolver-class', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + `--resolver=${join(dir, 'resolver.cjs')}`, + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('accepts a relative local CommonJS path', async () => { + await withFixture('custom-resolver-class', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + '--resolver', + './resolver.cjs', + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('accepts an absolute local ESM path', async () => { + await withFixture('custom-resolver-class', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + `--resolver=${join(dir, 'resolver.mjs')}`, + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('accepts a relative local ESM path', async () => { + await withFixture('custom-resolver-class', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + '--resolver', + './resolver.mjs', + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('accepts a npm package', async () => { + await withFixture('custom-resolver-class', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + '--resolver=test-react-docgen-resolver', + `${dir}/Component.js`, + ]); + + expect(stderr).toBe(''); + expect(stdout).toContain('Custom'); + expect(() => JSON.parse(stdout)).not.toThrowError(); + }); + }); + + test('throws if export is not a class instance', async () => { + await withFixture('custom-resolver-class', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + '--resolver=test-react-docgen-resolver-class', + `${dir}/Component.js`, + ]); + + expect(stderr).toContain( + "The provided resolver 'test-react-docgen-resolver-class' is not a function or a class instance but instead a class", + ); + expect(stdout).toBe(''); + }); + }); + }); + + test('throws error when not found', async () => { + await withFixture('basic', async ({ dir, run }) => { + const { stdout, stderr } = await run([ + '--resolver=does-not-exist', + `${dir}/Component.js`, + ]); + + expect(stderr).toContain('Unknown resolver: "does-not-exist"'); + expect(stdout).toBe(''); + }); + }); + }); +}); diff --git a/packages/react-docgen-cli/tests/integration/utils/withFixture.ts b/packages/react-docgen-cli/tests/integration/utils/withFixture.ts new file mode 100644 index 00000000000..474b9d6f50c --- /dev/null +++ b/packages/react-docgen-cli/tests/integration/utils/withFixture.ts @@ -0,0 +1,40 @@ +import { rm, stat } from 'fs/promises'; +import { dirname, join } from 'path'; +import type { ExecaError } from 'execa'; +import { execaNode } from 'execa'; +import copy from 'cpy'; +import { temporaryDirectory } from 'tempy'; +import { fileURLToPath } from 'url'; + +const __dir = dirname(fileURLToPath(import.meta.url)); + +const fixtureDir = join(__dir, '../__fixtures__'); +const cliBinary = join(__dir, '../../../dist/cli.js'); + +export default async function withFixture( + fixture: string, + callback: (api: { + dir: string; + run: ( + args: readonly string[], + ) => Promise<{ stdout: string; stderr: string; exitCode: number }>; + }) => Promise, +): Promise { + const tempDir = temporaryDirectory(); + + async function run(args: readonly string[]) { + try { + return await execaNode(cliBinary, args, { + cwd: tempDir, + }); + } catch (error) { + return error as ExecaError; + } + } + + await stat(join(fixtureDir, fixture)); + + await copy(join(fixtureDir, fixture, '**/*'), tempDir, {}); + await callback({ dir: tempDir, run }); + await rm(tempDir, { force: true, recursive: true }); +} diff --git a/packages/react-docgen-cli/vitest.config.ts b/packages/react-docgen-cli/vitest.config.ts index 2df2410c1c2..3253d2abb94 100644 --- a/packages/react-docgen-cli/vitest.config.ts +++ b/packages/react-docgen-cli/vitest.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + name: 'cli', include: ['**/__tests__/**/*-test.ts', '**/tests/integration/**/*-test.ts'], testTimeout: 30_000, deps: { diff --git a/packages/react-docgen/package.json b/packages/react-docgen/package.json index a4421d2db12..9425a5ebf66 100644 --- a/packages/react-docgen/package.json +++ b/packages/react-docgen/package.json @@ -18,6 +18,7 @@ "typings": "dist/main.d.ts", "scripts": { "build": "rimraf dist/ && tsc", + "test": "vitest run", "watch": "rimraf dist/ && tsc --watch" }, "keywords": [ diff --git a/packages/react-docgen/src/__mocks__/FileState.ts b/packages/react-docgen/src/__mocks__/FileState.ts new file mode 100644 index 00000000000..ccd22c0633a --- /dev/null +++ b/packages/react-docgen/src/__mocks__/FileState.ts @@ -0,0 +1,17 @@ +import { file, program } from '@babel/types'; +import FileState from '../FileState.js'; + +export default class FileStateMock extends FileState { + constructor() { + super( + {}, + { + code: '', + ast: file(program([])), + importer: () => { + return null; + }, + }, + ); + } +} diff --git a/packages/react-docgen/src/__tests__/parse-test.ts b/packages/react-docgen/src/__tests__/parse-test.ts index ac123fff2cb..80f3c8a314a 100644 --- a/packages/react-docgen/src/__tests__/parse-test.ts +++ b/packages/react-docgen/src/__tests__/parse-test.ts @@ -5,113 +5,138 @@ import { parse as testParse, noopImporter } from '../../tests/utils'; import parse from '../parse.js'; import { describe, expect, test, vi } from 'vitest'; import { ERROR_CODES } from '../error'; +import type { ComponentNodePath, ResolverClass } from '../resolver/index.js'; -describe('parse', () => { - test('allows custom component definition resolvers', () => { - const path = testParse.expression('{foo: "bar"}'); - const resolver = vi.fn(() => [path]); - const handler = vi.fn(); - - parse('//empty', { - resolver, - handlers: [handler], - importer: noopImporter, - babelOptions: {}, +const createEmptyClassResolver = (path?: ComponentNodePath) => + new (class implements ResolverClass { + resolve = vi.fn(() => { + return path ? [path] : []; }); + })(); - expect(resolver).toBeCalled(); - expect(handler.mock.calls[0][1]).toBe(path); - }); +const createEmptyFunctionResolver = (path?: ComponentNodePath) => + vi.fn(() => (path ? [path] : [])); - test('errors if component definition is not found', () => { - const resolver = vi.fn(() => []); +describe('parse', () => { + describe.each([ + { name: 'function', createEmptyResolver: createEmptyFunctionResolver }, + { name: 'class', createEmptyResolver: createEmptyClassResolver }, + ])('with $name resolver', ({ createEmptyResolver }) => { + test('allows custom component definition resolvers', () => { + const path = testParse.expression('{foo: "bar"}'); + const resolver = createEmptyResolver(path); + const handler = vi.fn(); - expect(() => parse('//empty', { resolver, - handlers: [], + handlers: [handler], importer: noopImporter, babelOptions: {}, - }), - ).toThrowError( - expect.objectContaining({ - code: ERROR_CODES.MISSING_DEFINITION, - }), - ); - expect(resolver).toBeCalled(); + }); - expect(() => - parse('//empty', { - resolver, - handlers: [], - importer: noopImporter, - babelOptions: {}, - }), - ).toThrowError( - expect.objectContaining({ - code: ERROR_CODES.MISSING_DEFINITION, - }), - ); - expect(resolver).toBeCalled(); - }); + expect( + typeof resolver === 'object' ? resolver.resolve : resolver, + ).toBeCalled(); + expect(handler.mock.calls[0][1]).toBe(path); + }); - test('uses local babelrc', () => { - const dir = temporaryDirectory(); + test('errors if component definition is not found', () => { + const resolver = createEmptyResolver(); - try { - // Write and empty babelrc to override the parser defaults - fs.writeFileSync(`${dir}/.babelrc`, '{}'); + expect(() => + parse('//empty', { + resolver, + handlers: [], + importer: noopImporter, + babelOptions: {}, + }), + ).toThrowError( + expect.objectContaining({ + code: ERROR_CODES.MISSING_DEFINITION, + }), + ); + expect( + typeof resolver === 'object' ? resolver.resolve : resolver, + ).toBeCalled(); expect(() => - parse('const chained = () => a |> b', { - resolver: () => [], + parse('//empty', { + resolver, handlers: [], importer: noopImporter, - babelOptions: { cwd: dir, filename: `${dir}/component.js` }, + babelOptions: {}, }), ).toThrowError( - /.*Support for the experimental syntax 'pipelineOperator' isn't currently enabled.*/, + expect.objectContaining({ + code: ERROR_CODES.MISSING_DEFINITION, + }), ); - } finally { - fs.unlinkSync(`${dir}/.babelrc`); - fs.rmdirSync(dir); - } - }); + expect( + typeof resolver === 'object' ? resolver.resolve : resolver, + ).toBeCalled(); + }); - test('supports custom parserOptions with plugins', () => { - expect(() => - parse('const chained: Type = 1;', { - resolver: () => [], - handlers: [], - importer: noopImporter, - babelOptions: { - parserOpts: { - plugins: [ - // no flow - 'jsx', - ], + test('uses local babelrc', () => { + const dir = temporaryDirectory(); + + try { + // Write and empty babelrc to override the parser defaults + fs.writeFileSync(`${dir}/.babelrc`, '{}'); + + expect(() => + parse('const chained = () => a |> b', { + resolver: createEmptyResolver(), + handlers: [], + importer: noopImporter, + babelOptions: { + cwd: dir, + filename: `${dir}/component.js`, + }, + }), + ).toThrowError( + /.*Support for the experimental syntax 'pipelineOperator' isn't currently enabled.*/, + ); + } finally { + fs.unlinkSync(`${dir}/.babelrc`); + fs.rmdirSync(dir); + } + }); + + test('supports custom parserOptions with plugins', () => { + expect(() => + parse('const chained: Type = 1;', { + resolver: createEmptyResolver(), + handlers: [], + importer: noopImporter, + babelOptions: { + parserOpts: { + plugins: [ + // no flow + 'jsx', + ], + }, }, - }, - }), - ).toThrowError(/.*\(1:13\).*/); - }); + }), + ).toThrowError(/.*\(1:13\).*/); + }); - test('supports custom parserOptions without plugins', () => { - expect(() => - parse('const chained: Type = 1;', { - resolver: () => [], - handlers: [], - importer: noopImporter, - babelOptions: { - parserOpts: { - allowSuperOutsideMethod: true, + test('supports custom parserOptions without plugins', () => { + expect(() => + parse('const chained: Type = 1;', { + resolver: createEmptyResolver(), + handlers: [], + importer: noopImporter, + babelOptions: { + parserOpts: { + allowSuperOutsideMethod: true, + }, }, - }, - }), - ).toThrowError( - expect.objectContaining({ - code: ERROR_CODES.MISSING_DEFINITION, - }), - ); + }), + ).toThrowError( + expect.objectContaining({ + code: ERROR_CODES.MISSING_DEFINITION, + }), + ); + }); }); }); diff --git a/packages/react-docgen/src/config.ts b/packages/react-docgen/src/config.ts index 1ec8abcc937..b00cdb4279d 100644 --- a/packages/react-docgen/src/config.ts +++ b/packages/react-docgen/src/config.ts @@ -16,7 +16,7 @@ import { import type { Importer } from './importer/index.js'; import { fsImporter } from './importer/index.js'; import type { Resolver } from './resolver/index.js'; -import { findExportedComponentDefinition } from './resolver/index.js'; +import { FindExportedDefinitionsResolver } from './resolver/index.js'; export interface Config { handlers?: Handler[]; @@ -32,7 +32,9 @@ export interface Config { } export type InternalConfig = Omit, 'filename'>; -const defaultResolver: Resolver = findExportedComponentDefinition; +const defaultResolver: Resolver = new FindExportedDefinitionsResolver({ + limit: 1, +}); const defaultImporter: Importer = fsImporter; export const defaultHandlers: Handler[] = [ diff --git a/packages/react-docgen/src/main.ts b/packages/react-docgen/src/main.ts index f4fefb24ac3..1ccc7504b9d 100644 --- a/packages/react-docgen/src/main.ts +++ b/packages/react-docgen/src/main.ts @@ -8,7 +8,11 @@ import { } from './importer/index.js'; import * as utils from './utils/index.js'; import type { DocumentationObject as Documentation } from './Documentation.js'; -import type { Resolver } from './resolver/index.js'; +import type { + Resolver, + ResolverClass, + ResolverFunction, +} from './resolver/index.js'; import type { Importer } from './importer/index.js'; import type { Handler } from './handlers/index.js'; import type FileState from './FileState.js'; @@ -67,4 +71,13 @@ export { ERROR_CODES, }; -export type { Importer, Handler, Resolver, FileState, Config, Documentation }; +export type { + Importer, + Handler, + Resolver, + ResolverClass, + ResolverFunction, + FileState, + Config, + Documentation, +}; diff --git a/packages/react-docgen/src/parse.ts b/packages/react-docgen/src/parse.ts index f0a6086cfef..504727cdfa4 100644 --- a/packages/react-docgen/src/parse.ts +++ b/packages/react-docgen/src/parse.ts @@ -8,6 +8,7 @@ import type { ComponentNode } from './resolver/index.js'; import FileState from './FileState.js'; import type { InternalConfig } from './config.js'; import { ERROR_CODES, ReactDocgenError } from './error.js'; +import runResolver from './resolver/utils/runResolver.js'; function executeHandlers( handlers: Handler[], @@ -54,7 +55,7 @@ export default function parse( importer, }); - const componentDefinitions = resolver(fileState); + const componentDefinitions = runResolver(resolver, fileState); if (componentDefinitions.length === 0) { throw new ReactDocgenError(ERROR_CODES.MISSING_DEFINITION); diff --git a/packages/react-docgen/src/resolver/ChainResolver.ts b/packages/react-docgen/src/resolver/ChainResolver.ts new file mode 100644 index 00000000000..4ad6a6c541e --- /dev/null +++ b/packages/react-docgen/src/resolver/ChainResolver.ts @@ -0,0 +1,58 @@ +import type FileState from '../FileState.js'; +import type { ComponentNodePath, Resolver, ResolverClass } from './index.js'; +import runResolver from './utils/runResolver.js'; + +enum ChainingLogic { + ALL, + FIRST_FOUND, +} + +interface ChainResolverOptions { + chainingLogic?: ChainingLogic; +} + +export default class ChainResolver implements ResolverClass { + resolvers: Resolver[]; + options: ChainResolverOptions; + + static Logic = ChainingLogic; + + constructor(resolvers: Resolver[], options: ChainResolverOptions) { + this.resolvers = resolvers; + this.options = options; + } + + private resolveFirstOnly(file: FileState): ComponentNodePath[] { + for (const resolver of this.resolvers) { + const components = runResolver(resolver, file); + + if (components.length > 0) { + return components; + } + } + + return []; + } + + private resolveAll(file: FileState): ComponentNodePath[] { + const allComponents = new Set(); + + for (const resolver of this.resolvers) { + const components = runResolver(resolver, file); + + components.forEach(component => { + allComponents.add(component); + }); + } + + return Array.from(allComponents); + } + + resolve(file: FileState): ComponentNodePath[] { + if (this.options.chainingLogic === ChainingLogic.FIRST_FOUND) { + return this.resolveFirstOnly(file); + } + + return this.resolveAll(file); + } +} diff --git a/packages/react-docgen/src/resolver/findAllComponentDefinitions.ts b/packages/react-docgen/src/resolver/FindAllDefinitionsResolver.ts similarity index 83% rename from packages/react-docgen/src/resolver/findAllComponentDefinitions.ts rename to packages/react-docgen/src/resolver/FindAllDefinitionsResolver.ts index 7a83414f76e..ecfa18d0e13 100644 --- a/packages/react-docgen/src/resolver/findAllComponentDefinitions.ts +++ b/packages/react-docgen/src/resolver/FindAllDefinitionsResolver.ts @@ -7,7 +7,7 @@ import resolveToValue from '../utils/resolveToValue.js'; import type { NodePath } from '@babel/traverse'; import { visitors } from '@babel/traverse'; import type FileState from '../FileState.js'; -import type { ComponentNode, Resolver } from './index.js'; +import type { ComponentNodePath, ResolverClass } from './index.js'; import type { ArrowFunctionExpression, FunctionDeclaration, @@ -16,7 +16,7 @@ import type { } from '@babel/types'; interface TraverseState { - foundDefinitions: Set>; + foundDefinitions: Set; } function classVisitor(path: NodePath, state: TraverseState) { @@ -58,7 +58,7 @@ const explodedVisitors = visitors.explode({ // replace it with the parent node const inner = resolveToValue( path.get('arguments')[0], - ) as NodePath; + ) as ComponentNodePath; state.foundDefinitions.delete(inner); state.foundDefinitions.add(path); @@ -83,16 +83,14 @@ const explodedVisitors = visitors.explode({ * Given an AST, this function tries to find all object expressions that are * passed to `React.createClass` calls, by resolving all references properly. */ -const findAllComponentDefinitions: Resolver = function ( - file: FileState, -): Array> { - const state: TraverseState = { - foundDefinitions: new Set>(), - }; +export default class FindAllDefinitionsResolver implements ResolverClass { + resolve(file: FileState): ComponentNodePath[] { + const state: TraverseState = { + foundDefinitions: new Set(), + }; - file.traverse(explodedVisitors, state); + file.traverse(explodedVisitors, state); - return Array.from(state.foundDefinitions); -}; - -export default findAllComponentDefinitions; + return Array.from(state.foundDefinitions); + } +} diff --git a/packages/react-docgen/src/resolver/FindExportedDefinitionsResolver.ts b/packages/react-docgen/src/resolver/FindExportedDefinitionsResolver.ts new file mode 100644 index 00000000000..24c8f5f625a --- /dev/null +++ b/packages/react-docgen/src/resolver/FindExportedDefinitionsResolver.ts @@ -0,0 +1,132 @@ +import isExportsOrModuleAssignment from '../utils/isExportsOrModuleAssignment.js'; +import resolveExportDeclaration from '../utils/resolveExportDeclaration.js'; +import resolveToValue from '../utils/resolveToValue.js'; +import resolveHOC from '../utils/resolveHOC.js'; +import type { NodePath } from '@babel/traverse'; +import { visitors } from '@babel/traverse'; +import { shallowIgnoreVisitors } from '../utils/traverse.js'; +import type { + AssignmentExpression, + ExportDefaultDeclaration, + ExportNamedDeclaration, +} from '@babel/types'; +import type FileState from '../FileState.js'; +import type { ComponentNodePath, ResolverClass } from './index.js'; +import resolveComponentDefinition, { + isComponentDefinition, +} from '../utils/resolveComponentDefinition.js'; +import { ERROR_CODES, ReactDocgenError } from '../error.js'; + +interface TraverseState { + foundDefinitions: Set; + limit: number; +} + +function exportDeclaration( + path: NodePath, + state: TraverseState, +): void { + resolveExportDeclaration(path).forEach(definition => { + if (!isComponentDefinition(definition)) { + definition = resolveToValue(resolveHOC(definition)); + + if (!isComponentDefinition(definition)) { + return; + } + } + + if (state.limit > 0 && state.foundDefinitions.size > 0) { + // If a file exports multiple components, ... complain! + throw new ReactDocgenError(ERROR_CODES.MULTIPLE_DEFINITIONS); + } + + const resolved = resolveComponentDefinition(definition); + + if (resolved) { + state.foundDefinitions.add(resolved); + } + }); + + return path.skip(); +} + +function assignmentExpressionVisitor( + path: NodePath, + state: TraverseState, +): void { + // Ignore anything that is not `exports.X = ...;` or + // `module.exports = ...;` + if (!isExportsOrModuleAssignment(path)) { + return path.skip(); + } + // Resolve the value of the right hand side. It should resolve to a call + // expression, something like React.createClass + let resolvedPath = resolveToValue(path.get('right')); + + if (!isComponentDefinition(resolvedPath)) { + resolvedPath = resolveToValue(resolveHOC(resolvedPath)); + if (!isComponentDefinition(resolvedPath)) { + return path.skip(); + } + } + if (state.limit > 0 && state.foundDefinitions.size > 0) { + // If a file exports multiple components, ... complain! + throw new ReactDocgenError(ERROR_CODES.MULTIPLE_DEFINITIONS); + } + + const definition = resolveComponentDefinition(resolvedPath); + + if (definition) { + state.foundDefinitions.add(definition); + } + + return path.skip(); +} + +const explodedVisitors = visitors.explode({ + ...shallowIgnoreVisitors, + + ExportNamedDeclaration: { enter: exportDeclaration }, + ExportDefaultDeclaration: { enter: exportDeclaration }, + AssignmentExpression: { enter: assignmentExpressionVisitor }, +}); + +interface FindExportedDefinitionsResolverOptions { + limit?: number; +} + +/** + * Given an AST, this function tries to find the exported component definitions. + * + * The component definitions are either the ObjectExpression passed to + * `React.createClass` or a `class` definition extending `React.Component` or + * having a `render()` method. + * + * If a definition is part of the following statements, it is considered to be + * exported: + * + * modules.exports = Definition; + * exports.foo = Definition; + * export default Definition; + * export var Definition = ...; + * + * limit can be used to limit the components to be found. When the limit is reached an error will be thrown + */ +export default class FindExportedDefinitionsResolver implements ResolverClass { + limit: number; + + constructor({ limit = 0 }: FindExportedDefinitionsResolverOptions = {}) { + this.limit = limit; + } + + resolve(file: FileState): ComponentNodePath[] { + const state: TraverseState = { + foundDefinitions: new Set(), + limit: this.limit, + }; + + file.traverse(explodedVisitors, state); + + return Array.from(state.foundDefinitions); + } +} diff --git a/packages/react-docgen/src/resolver/__tests__/ChainResolver-test.ts b/packages/react-docgen/src/resolver/__tests__/ChainResolver-test.ts new file mode 100644 index 00000000000..731c04d9eff --- /dev/null +++ b/packages/react-docgen/src/resolver/__tests__/ChainResolver-test.ts @@ -0,0 +1,109 @@ +import ChainResolver from '../ChainResolver.js'; +import { describe, expect, test, vi } from 'vitest'; +import FileStateMock from '../../__mocks__/FileState'; +import { blockStatement, functionExpression } from '@babel/types'; + +describe('ChainResolver', () => { + const fileStateMock = new FileStateMock(); + const component1 = functionExpression(null, [], blockStatement([])); + const component2 = functionExpression(null, [], blockStatement([])); + + test('can be used with only one resolver', () => { + const resolver = vi.fn().mockReturnValue([]); + const chainResolver = new ChainResolver([resolver], { + chainingLogic: ChainResolver.Logic.ALL, + }); + + chainResolver.resolve(fileStateMock); + + expect(resolver).toHaveBeenCalledOnce(); + expect(resolver).toHaveBeenCalledWith(fileStateMock); + }); + + test('can be used with multiple resolvers', () => { + const resolver1 = vi.fn().mockReturnValue([]); + const resolver2 = vi.fn().mockReturnValue([]); + const chainResolver = new ChainResolver([resolver1, resolver2], { + chainingLogic: ChainResolver.Logic.ALL, + }); + + chainResolver.resolve(fileStateMock); + + expect(resolver1).toHaveBeenCalledOnce(); + expect(resolver1).toHaveBeenCalledWith(fileStateMock); + expect(resolver2).toHaveBeenCalledOnce(); + expect(resolver2).toHaveBeenCalledWith(fileStateMock); + }); + + test('returns all components from all resolvers with ALL logic', () => { + const resolver1 = vi.fn().mockReturnValue([component1]); + const resolver2 = vi.fn().mockReturnValue([component2]); + const chainResolver = new ChainResolver([resolver1, resolver2], { + chainingLogic: ChainResolver.Logic.ALL, + }); + + const result = chainResolver.resolve(fileStateMock); + + expect(result).toEqual([component1, component2]); + }); + + test('returns no duplicates with ALL logic', () => { + const resolver1 = vi.fn().mockReturnValue([component1]); + const resolver2 = vi.fn().mockReturnValue([component1, component2]); + const chainResolver = new ChainResolver([resolver1, resolver2], { + chainingLogic: ChainResolver.Logic.ALL, + }); + + const result = chainResolver.resolve(fileStateMock); + + expect(result).toEqual([component1, component2]); + }); + + test('returns first found components with FIRST_FOUND logic', () => { + const resolver1 = vi.fn().mockReturnValue([component1]); + const resolver2 = vi.fn().mockReturnValue([component2]); + const chainResolver = new ChainResolver([resolver1, resolver2], { + chainingLogic: ChainResolver.Logic.FIRST_FOUND, + }); + + const result = chainResolver.resolve(fileStateMock); + + expect(result).toEqual([component1]); + }); + + test('returns empty array if no components found with ALL logic', () => { + const resolver1 = vi.fn().mockReturnValue([]); + const resolver2 = vi.fn().mockReturnValue([]); + const chainResolver = new ChainResolver([resolver1, resolver2], { + chainingLogic: ChainResolver.Logic.ALL, + }); + + const result = chainResolver.resolve(fileStateMock); + + expect(result).toEqual([]); + }); + + test('returns empty array if no components found with FIRST_FOUND logic', () => { + const resolver1 = vi.fn().mockReturnValue([]); + const resolver2 = vi.fn().mockReturnValue([]); + const chainResolver = new ChainResolver([resolver1, resolver2], { + chainingLogic: ChainResolver.Logic.FIRST_FOUND, + }); + + const result = chainResolver.resolve(fileStateMock); + + expect(result).toEqual([]); + }); + + test('throws if resolver throws', () => { + const error = new Error('test'); + const resolver = vi.fn().mockImplementation(() => { + throw error; + }); + const chainResolver = new ChainResolver([resolver], { + chainingLogic: ChainResolver.Logic.ALL, + }); + + expect(() => chainResolver.resolve(fileStateMock)).toThrowError(error); + }); +}); diff --git a/packages/react-docgen/src/resolver/__tests__/findAllComponentDefinitions-test.ts b/packages/react-docgen/src/resolver/__tests__/FindAllDefinitionsResolver-test.ts similarity index 97% rename from packages/react-docgen/src/resolver/__tests__/findAllComponentDefinitions-test.ts rename to packages/react-docgen/src/resolver/__tests__/FindAllDefinitionsResolver-test.ts index dd043ff6990..bf67575936d 100644 --- a/packages/react-docgen/src/resolver/__tests__/findAllComponentDefinitions-test.ts +++ b/packages/react-docgen/src/resolver/__tests__/FindAllDefinitionsResolver-test.ts @@ -1,14 +1,16 @@ import { NodePath } from '@babel/traverse'; import { parse, makeMockImporter, noopImporter } from '../../../tests/utils'; -import findAllComponentDefinitions from '../findAllComponentDefinitions.js'; +import FindAllDefinitionsResolver from '../FindAllDefinitionsResolver.js'; import { describe, expect, test } from 'vitest'; -describe('findAllComponentDefinitions', () => { +describe('FindAllDefinitionsResolver', () => { + const resolver = new FindAllDefinitionsResolver(); + function findComponentsInSource( source: string, importer = noopImporter, ): NodePath[] { - return findAllComponentDefinitions(parse(source, {}, importer, true)); + return resolver.resolve(parse(source, {}, importer, true)); } const mockImporter = makeMockImporter({ diff --git a/packages/react-docgen/src/resolver/__tests__/FindExportedDefinitionsResolver-test.ts b/packages/react-docgen/src/resolver/__tests__/FindExportedDefinitionsResolver-test.ts new file mode 100644 index 00000000000..ec8cf7b039f --- /dev/null +++ b/packages/react-docgen/src/resolver/__tests__/FindExportedDefinitionsResolver-test.ts @@ -0,0 +1,2444 @@ +import type { NodePath } from '@babel/traverse'; +import { parse, noopImporter, makeMockImporter } from '../../../tests/utils'; +import FindExportedDefinitionsResolver from '../FindExportedDefinitionsResolver.js'; +import { describe, expect, test } from 'vitest'; + +describe('FindExportedDefinitionsResolver', () => { + describe('no limit', () => { + const resolver = new FindExportedDefinitionsResolver(); + + function findComponentsInSource( + source: string, + importer = noopImporter, + ): NodePath[] { + return resolver.resolve(parse(source, {}, importer, true)); + } + + const mockImporter = makeMockImporter({ + createClass: stmtLast => + stmtLast(` + import React from 'react' + export default React.createClass({}) + `).get('declaration'), + + classDec: stmtLast => + stmtLast(` + import React from 'react' + export default class Component extends React.Component {} + `).get('declaration'), + + classExpr: stmtLast => + stmtLast(` + import React from 'react' + var Component = class extends React.Component {} + export default Component + `).get('declaration'), + + statelessJsx: stmtLast => + stmtLast(` + export default () =>
+ `).get('declaration'), + + statelessCreateElement: stmtLast => + stmtLast(` + import React from 'react' + export default () => React.createElement('div', {}) + `).get('declaration'), + + forwardRef: stmtLast => + stmtLast(` + import React from 'react' + export default React.forwardRef((props, ref) => ( +
+ )) + `).get('declaration'), + }); + + describe('CommonJS module exports', () => { + describe('React.createClass', () => { + test('finds React.createClass', () => { + const result = findComponentsInSource(` + var React = require("React"); + var Component = React.createClass({}); + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds React.createClass, independent of the var name', () => { + const result = findComponentsInSource(` + var R = require("React"); + var Component = R.createClass({}); + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('does not process X.createClass of other modules', () => { + const result = findComponentsInSource(` + var R = require("NoReact"); + var Component = R.createClass({}); + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('resolves an imported variable to React.createClass', () => { + const result = findComponentsInSource( + ` + import Component from 'createClass'; + module.exports = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('class definitions', () => { + test('finds class declarations', () => { + const result = findComponentsInSource(` + var React = require("React"); + class Component extends React.Component {} + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds class expression', () => { + const result = findComponentsInSource(` + var React = require("React"); + var Component = class extends React.Component {} + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds class definition, independent of the var name', () => { + const result = findComponentsInSource(` + var R = require("React"); + class Component extends R.Component {} + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('resolves an imported variable to class declaration', () => { + const result = findComponentsInSource( + ` + import Component from 'classDec'; + module.exports = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + + test('resolves an imported variable to class expression', () => { + const result = findComponentsInSource( + ` + import Component from 'classExpr'; + module.exports = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('stateless components', () => { + test('finds stateless component with JSX', () => { + const result = findComponentsInSource(` + var React = require("React"); + var Component = () =>
; + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds stateless components with React.createElement, independent of the var name', () => { + const result = findComponentsInSource(` + var R = require("React"); + var Component = () => R.createElement('div', {}); + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('does not process X.createElement of other modules', () => { + const result = findComponentsInSource(` + var R = require("NoReact"); + var Component = () => R.createElement({}); + module.exports = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('resolves an imported stateless component with JSX', () => { + const result = findComponentsInSource( + ` + import Component from 'statelessJsx'; + module.exports = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + + test('resolves an imported stateless component with React.createElement', () => { + const result = findComponentsInSource( + ` + import Component from 'statelessCreateElement'; + module.exports = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('forwardRef components', () => { + test('finds forwardRef components', () => { + const result = findComponentsInSource(` + import React from 'react'; + import PropTypes from 'prop-types'; + import extendStyles from 'enhancers/extendStyles'; + + const ColoredView = React.forwardRef((props, ref) => ( +
+ )); + + module.exports = extendStyles(ColoredView); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds none inline forwardRef components', () => { + const result = findComponentsInSource(` + import React from 'react'; + import PropTypes from 'prop-types'; + import extendStyles from 'enhancers/extendStyles'; + + function ColoredView(props, ref) { + return
+ } + + const ForwardedColoredView = React.forwardRef(ColoredView); + + module.exports = ForwardedColoredView + `); + + expect(result).toMatchSnapshot(); + }); + + test('resolves an imported forwardRef component', () => { + const result = findComponentsInSource( + ` + import Component from 'forwardRef'; + module.exports = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('module.exports = ; / exports.foo = ;', () => { + describe('React.createClass', () => { + test('finds assignments to exports', () => { + const result = findComponentsInSource(` + var R = require("React"); + var Component = R.createClass({}); + exports.foo = 42; + exports.Component = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple exported components', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + exports.ComponentA = ComponentA; + exports.ComponentB = ComponentB; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple exported components with hocs', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + exports.ComponentA = hoc(ComponentA); + exports.ComponentB = hoc(ComponentB); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components on export', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + exports.ComponentB = ComponentB; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + module.exports = ComponentB; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported components only once', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + exports.ComponentA = ComponentA; + exports.ComponentB = ComponentA; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + ` + import Component from 'createClass'; + exports.ComponentA = Component; + exports.ComponentB = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('class definition', () => { + test('finds assignments to exports', () => { + const result = findComponentsInSource(` + var R = require("React"); + class Component extends R.Component {} + exports.foo = 42; + exports.Component = Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple exported components', () => { + const result = findComponentsInSource(` + var R = require("React"); + class ComponentA extends R.Component {} + class ComponentB extends R.Component {} + exports.ComponentA = ComponentA; + exports.ComponentB = ComponentB; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components on export', () => { + const result = findComponentsInSource(` + var R = require("React"); + class ComponentA extends R.Component {} + class ComponentB extends R.Component {} + exports.ComponentB = ComponentB; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + var R = require("React"); + class ComponentA extends R.Component {} + class ComponentB extends R.Component {} + module.exports = ComponentB; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported components only once', () => { + const result = findComponentsInSource(` + var R = require("React"); + class ComponentA extends R.Component {} + exports.ComponentA = ComponentA; + exports.ComponentB = ComponentA; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + ` + import Component from 'classDec'; + exports.ComponentA = Component; + exports.ComponentB = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + }); + }); + + describe('ES6 export declarations', () => { + describe('export default ;', () => { + describe('React.createClass', () => { + test('finds reassigned default export', () => { + const result = findComponentsInSource(` + var React = require("React"); + var Component = React.createClass({}); + export default Component + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds default export', () => { + const result = findComponentsInSource(` + var React = require("React"); + export default React.createClass({}); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple exported components with export var', () => { + const result = findComponentsInSource(` + import React, { createElement } from "React" + export var Component = React.createClass({}); + export default React.createClass({}); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple exported components with named export', () => { + const result = findComponentsInSource(` + import React, { createElement } from "React" + var Component = React.createClass({}) + export {Component}; + export default React.createClass({}); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + import React, { createElement } from "React" + var Component = React.createClass({}) + export default React.createClass({}); + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + `import Component from 'createClass'; + export default Component;`, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('class definition', () => { + test('finds default export', () => { + const result = findComponentsInSource(` + import React from 'React'; + class Component extends React.Component {} + export default Component; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds default export inline', () => { + const result = findComponentsInSource(` + import React from 'React'; + export default class Component extends React.Component {}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple exported components with export var', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var Component = class extends React.Component {}; + export default class ComponentB extends React.Component{}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple exported components with named export', () => { + const result = findComponentsInSource(` + import React from 'React'; + var Component = class extends React.Component {}; + export {Component}; + export default class ComponentB extends React.Component{}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + import React from 'React'; + var Component = class extends React.Component {}; + export default class ComponentB extends React.Component{}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + `import Component from 'classDec'; + export default Component;`, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('forwardRef components', () => { + test('finds forwardRef components', () => { + const result = findComponentsInSource(` + import React from 'react'; + import PropTypes from 'prop-types'; + import extendStyles from 'enhancers/extendStyles'; + + const ColoredView = React.forwardRef((props, ref) => ( +
+ )); + + export default extendStyles(ColoredView); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds none inline forwardRef components', () => { + const result = findComponentsInSource(` + import React from 'react'; + import PropTypes from 'prop-types'; + import extendStyles from 'enhancers/extendStyles'; + + function ColoredView(props, ref) { + return
+ } + + const ForwardedColoredView = React.forwardRef(ColoredView); + + export default ForwardedColoredView + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + `import Component from 'forwardRef'; + export default Component;`, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + }); + + describe('export var foo = , ...;', () => { + describe('React.createClass', () => { + test('finds named exports 1', () => { + const result = findComponentsInSource(` + var React = require("React"); + export var somethingElse = 42, Component = React.createClass({}); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds named exports 2', () => { + const result = findComponentsInSource(` + var React = require("React"); + export let Component = React.createClass({}), somethingElse = 42; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds named exports 3', () => { + const result = findComponentsInSource(` + var React = require("React"); + export const something = 21, + Component = React.createClass({}), + somethingElse = 42; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds named exports 4', () => { + const result = findComponentsInSource(` + var React = require("React"); + export var somethingElse = function() {}; + export let Component = React.createClass({}); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components', () => { + const result = findComponentsInSource(` + var R = require("React"); + export var ComponentA = R.createClass({}), + ComponentB = R.createClass({}); + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components with separate export statements', () => { + const result = findComponentsInSource(` + var R = require("React"); + export var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + export {ComponentB}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + export let ComponentB = R.createClass({}); + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + `import Component from 'createClass'; + export let ComponentA = Component; + export let ComponentB = Component;`, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('class definition', () => { + test('finds named exports 1', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var somethingElse = 42, + Component = class extends React.Component {}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds named exports 2', () => { + const result = findComponentsInSource(` + import React from 'React'; + export let Component = class extends React.Component {}, + somethingElse = 42; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds named exports 3', () => { + const result = findComponentsInSource(` + import React from 'React'; + export const something = 21, + Component = class extends React.Component {}, + somethingElse = 42; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds named exports 4', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var somethingElse = function() {}; + export let Component = class extends React.Component {}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var ComponentA = class extends React.Component {}; + export var ComponentB = class extends React.Component {}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components with assigned component', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var ComponentA = class extends React.Component {}; + var ComponentB = class extends React.Component {}; + export {ComponentB}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + import React from 'React'; + var ComponentA = class extends React.Component {} + export var ComponentB = class extends React.Component {}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + ` + import Component from 'classDec'; + export let ComponentA = Component; + export let ComponentB = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('stateless components', () => { + test('finds named exports 1', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var somethingElse = 42, + Component = () =>
; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components 2', () => { + const result = findComponentsInSource(` + import React from 'React'; + export let Component = () =>
, + somethingElse = 42; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components 3', () => { + const result = findComponentsInSource(` + import React from 'React'; + export const something = 21, + Component = () =>
, + somethingElse = 42; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components 4', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var somethingElse = function() {}; + export let Component = () =>
+ `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var ComponentA = () =>
+ export var ComponentB = () =>
+ `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components with named export', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var ComponentA = () =>
+ var ComponentB = () =>
+ export {ComponentB}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + import React from 'React'; + var ComponentA = class extends React.Component {} + export var ComponentB = function() { return
; }; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + `import Component1 from 'statelessJsx'; + import Component2 from 'statelessCreateElement'; + export var ComponentA = Component1, ComponentB = Component2;`, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('forwardRef components', () => { + test('finds forwardRef components', () => { + const result = findComponentsInSource(` + import React from 'react'; + import PropTypes from 'prop-types'; + import extendStyles from 'enhancers/extendStyles'; + + export const ColoredView = extendStyles(React.forwardRef((props, ref) => ( +
+ ))); + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + ` + import Component from 'forwardRef'; + export let ComponentA = Component; + export let ComponentB = Component; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + }); + + describe('export {};', () => { + describe('React.createClass', () => { + test('finds exported specifiers', () => { + const result = findComponentsInSource(` + var React = require("React"); + var foo = 42; + var Component = React.createClass({}); + export {foo, Component} + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported specifiers 2', () => { + const result = findComponentsInSource(` + import React from "React" + var foo = 42; + var Component = React.createClass({}); + export {Component, foo} + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported specifiers 3', () => { + const result = findComponentsInSource(` + import React, { createElement } from "React" + var foo = 42; + var baz = 21; + var Component = React.createClass({}); + export {foo, Component as bar, baz} + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + export {ComponentA as foo, ComponentB}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components with hocs', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = hoc(R.createClass({})); + var ComponentB = hoc(R.createClass({})); + export {ComponentA as foo, ComponentB}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + export {ComponentA} + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported components only once', () => { + const result = findComponentsInSource(` + var R = require("React"); + var ComponentA = R.createClass({}); + export {ComponentA as foo, ComponentA as bar}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + `import Component from 'createClass'; + export { Component, Component as ComponentB };`, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('class definition', () => { + test('finds exported specifiers 1', () => { + const result = findComponentsInSource(` + import React from 'React'; + var foo = 42; + var Component = class extends React.Component {}; + export {foo, Component}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported specifiers 2', () => { + const result = findComponentsInSource(` + import React from 'React'; + var foo = 42; + var Component = class extends React.Component {}; + export {Component, foo}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported specifiers 3', () => { + const result = findComponentsInSource(` + import React from 'React'; + var foo = 42; + var baz = 21; + var Component = class extends React.Component {}; + export {foo, Component as bar, baz}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components', () => { + const result = findComponentsInSource(` + import React from 'React'; + var ComponentA = class extends React.Component {}; + var ComponentB = class extends React.Component {}; + export {ComponentA as foo, ComponentB}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components with hocs', () => { + const result = findComponentsInSource(` + import React from 'React'; + class ComponentA extends React.Component {}; + class ComponentB extends React.Component {}; + var WrappedA = hoc(ComponentA); + var WrappedB = hoc(ComponentB); + export {WrappedA, WrappedB}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + import React from 'React'; + var ComponentA = class extends React.Component {}; + var ComponentB = class extends React.Component {}; + export {ComponentA}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported components only once', () => { + const result = findComponentsInSource(` + import React from 'React'; + var ComponentA = class extends React.Component {}; + var ComponentB = class extends React.Component {}; + export {ComponentA as foo, ComponentA as bar}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + ` + import Component from 'classDec'; + export { Component, Component as ComponentB }; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('stateless components', () => { + test('finds exported specifiers 1', () => { + const result = findComponentsInSource(` + import React from 'React'; + var foo = 42; + function Component() { return
; } + export {foo, Component}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported specifiers 2', () => { + const result = findComponentsInSource(` + import React from 'React'; + var foo = 42; + var Component = () =>
; + export {Component, foo}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported specifiers 3', () => { + const result = findComponentsInSource(` + import React from 'React'; + var foo = 42; + var baz = 21; + var Component = function () { return
; } + export {foo, Component as bar, baz}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components', () => { + const result = findComponentsInSource(` + import React from 'React'; + var ComponentA = () =>
; + function ComponentB() { return
; } + export {ComponentA as foo, ComponentB}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + import React from 'React'; + var ComponentA = () =>
; + var ComponentB = () =>
; + export {ComponentA}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds exported components only once', () => { + const result = findComponentsInSource(` + import React from 'React'; + var ComponentA = () =>
; + var ComponentB = () =>
; + export {ComponentA as foo, ComponentA as bar}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + ` + import ComponentA from 'statelessJsx'; + import ComponentB from 'statelessCreateElement'; + export { ComponentA, ComponentB }; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('forwardRef components', () => { + test('finds forwardRef components', () => { + const result = findComponentsInSource(` + import React from 'react'; + import PropTypes from 'prop-types'; + import extendStyles from 'enhancers/extendStyles'; + + const ColoredView = extendStyles(React.forwardRef((props, ref) => ( +
+ ))); + + export { ColoredView } + `); + + expect(result).toMatchSnapshot(); + }); + + test('supports imported components', () => { + const result = findComponentsInSource( + ` + import Component from 'forwardRef'; + export { Component, Component as ComponentB }; + `, + mockImporter, + ); + + expect(result).toMatchSnapshot(); + }); + }); + }); + + describe('export ;', () => { + describe('class definition', () => { + test('finds named exports', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var foo = 42; + export class Component extends React.Component {}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components', () => { + const result = findComponentsInSource(` + import React from 'React'; + export class ComponentA extends React.Component {}; + export class ComponentB extends React.Component {}; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + import React from 'React'; + class ComponentA extends React.Component {}; + export class ComponentB extends React.Component {}; + `); + + expect(result).toMatchSnapshot(); + }); + }); + + describe('function declaration', () => { + test('finds named exports', () => { + const result = findComponentsInSource(` + import React from 'React'; + export var foo = 42; + export function Component() { return
; }; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds multiple components', () => { + const result = findComponentsInSource(` + import React from 'React'; + export function ComponentA() { return
; }; + export function ComponentB() { return
; }; + `); + + expect(result).toMatchSnapshot(); + }); + + test('finds only exported components', () => { + const result = findComponentsInSource(` + import React from 'React'; + function ComponentA() { return
; } + export function ComponentB() { return
; }; + `); + + expect(result).toMatchSnapshot(); + }); + }); + }); + }); + }); + + describe('limit 1', () => { + const resolver = new FindExportedDefinitionsResolver({ limit: 1 }); + + function findComponentsInSource( + source: string, + importer = noopImporter, + ): NodePath[] { + return resolver.resolve(parse(source, {}, importer, true)); + } + const mockImporter = makeMockImporter({ + createClass: stmtLast => + stmtLast(` + import React from 'react' + export default React.createClass({}) + `).get('declaration'), + + classDec: stmtLast => + stmtLast(` + import React from 'react' + export default class Component extends React.Component {} + `).get('declaration'), + + classExpr: stmtLast => + stmtLast(` + import React from 'react' + var Component = class extends React.Component {} + export default Component + `).get('declaration'), + + statelessJsx: stmtLast => + stmtLast(`export default () =>
`).get('declaration'), + + statelessCreateElement: stmtLast => + stmtLast(` + import React from 'react' + export default () => React.createElement('div', {}) + `).get('declaration'), + + forwardRef: stmtLast => + stmtLast(` + import React from 'react' + export default React.forwardRef((props, ref) => ( +
+ )) + `).get('declaration'), + }); + + describe('CommonJS module exports', () => { + describe('React.createClass', () => { + test('finds React.createClass', () => { + const source = ` + var React = require("React"); + var Component = React.createClass({}); + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds React.createClass with hoc', () => { + const source = ` + var React = require("React"); + var Component = React.createClass({}); + module.exports = hoc(Component); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds React.createClass with hoc and args', () => { + const source = ` + var React = require("React"); + var Component = React.createClass({}); + module.exports = hoc(arg1, arg2)(Component); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds React.createClass with two hocs', () => { + const source = ` + var React = require("React"); + var Component = React.createClass({}); + module.exports = hoc2(arg2b, arg2b)( + hoc1(arg1a, arg2a)(Component) + ); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds React.createClass with three hocs', () => { + const source = ` + var React = require("React"); + var Component = React.createClass({}); + module.exports = hoc3(arg3a, arg3b)( + hoc2(arg2b, arg2b)( + hoc1(arg1a, arg2a)(Component) + ) + ); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds React.createClass, independent of the var name', () => { + const source = ` + var R = require("React"); + var Component = R.createClass({}); + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('does not process X.createClass of other modules', () => { + const source = ` + var R = require("NoReact"); + var Component = R.createClass({}); + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(0); + }); + + test('resolves an imported variable to React.createClass', () => { + const source = ` + import Component from 'createClass'; + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(0); + }); + }); + + describe('class definitions', () => { + test('finds class declarations', () => { + const source = ` + var React = require("React"); + class Component extends React.Component {} + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('finds class expression', () => { + const source = ` + var React = require("React"); + var Component = class extends React.Component {} + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + }); + + test('finds class definition, independent of the var name', () => { + const source = ` + var R = require("React"); + class Component extends R.Component {} + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('resolves an imported variable to class declaration', () => { + const source = ` + import Component from 'classDec'; + module.exports = Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('resolves an imported variable to class expression', () => { + const source = ` + import Component from 'classExpr'; + module.exports = Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + }); + }); + + describe('stateless components', () => { + test('finds stateless component with JSX', () => { + const source = ` + var React = require("React"); + var Component = () =>
; + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds stateless components with React.createElement, independent of the var name', () => { + const source = ` + var R = require("React"); + var Component = () => R.createElement('div', {}); + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('does not process X.createElement of other modules', () => { + const source = ` + var R = require("NoReact"); + var Component = () => R.createElement({}); + module.exports = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(0); + }); + + test('resolves an imported stateless component with JSX', () => { + const source = ` + import Component from 'statelessJsx'; + module.exports = Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('resolves an imported stateless component with React.createElement', () => { + const source = ` + import Component from 'statelessCreateElement'; + module.exports = Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + }); + + describe('module.exports = ; / exports.foo = ;', () => { + describe('React.createClass', () => { + test('finds assignments to exports', () => { + const source = ` + var R = require("React"); + var Component = R.createClass({}); + exports.foo = 42; + exports.Component = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('errors if multiple components are exported', () => { + const source = ` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + exports.ComponentA = ComponentA; + exports.ComponentB = ComponentB; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported on exports', () => { + const source = ` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + exports.ComponentB = ComponentB; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + module.exports = ComponentB; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('supports imported components', () => { + const source = ` + import Component from 'createClass'; + exports.ComponentB = Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + }); + + describe('class definition', () => { + test('finds assignments to exports', () => { + const source = ` + var R = require("React"); + class Component extends R.Component {} + exports.foo = 42; + exports.Component = Component; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('errors if multiple components are exported', () => { + const source = ` + var R = require("React"); + class ComponentA extends R.Component {} + class ComponentB extends R.Component {} + exports.ComponentA = ComponentA; + exports.ComponentB = ComponentB; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + let source = ` + var R = require("React"); + class ComponentA extends R.Component {} + class ComponentB extends R.Component {} + exports.ComponentB = ComponentB; + `; + + let result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + + source = ` + var R = require("React"); + class ComponentA extends R.Component {} + class ComponentB extends R.Component {} + module.exports = ComponentB; + `; + + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('supports imported components', () => { + const source = ` + import Component from 'classDec'; + exports.ComponentB = Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + }); + }); + }); + + describe('ES6 export declarations', () => { + describe('export default ;', () => { + describe('React.createClass', () => { + test('finds default export', () => { + const source = ` + var React = require("React"); + var Component = React.createClass({}); + export default Component + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds default export inline', () => { + const source = ` + var React = require("React"); + export default React.createClass({}); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('errors if multiple components are exported', () => { + const source = ` + import React, { createElement } from "React" + export var Component = React.createClass({}) + export default React.createClass({}); + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('errors if multiple components are exported with named export', () => { + const source = ` + import React, { createElement } from "React" + var Component = React.createClass({}) + export {Component}; + export default React.createClass({}); + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + import React, { createElement } from "React" + var Component = React.createClass({}) + export default React.createClass({}); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('supports imported components', () => { + const source = ` + import Component from 'createClass'; + export default Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + }); + + describe('class definition', () => { + test('finds default export', () => { + let source = ` + import React from 'React'; + class Component extends React.Component {} + export default Component; + `; + + let result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + + source = ` + import React from 'React'; + export default class Component extends React.Component {}; + `; + + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('finds default export with hoc', () => { + const source = ` + import React from 'React'; + class Component extends React.Component {} + export default hoc(Component); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('finds default export with hoc and args', () => { + const source = ` + import React from 'React'; + class Component extends React.Component {} + export default hoc(arg1, arg2)(Component); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('finds default export with two hocs', () => { + const source = ` + import React from 'React'; + class Component extends React.Component {} + export default hoc2(arg2b, arg2b)( + hoc1(arg1a, arg2a)(Component) + ); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('errors if multiple components are exported', () => { + let source = ` + import React from 'React'; + export var Component = class extends React.Component {}; + export default class ComponentB extends React.Component{}; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + + source = ` + import React from 'React'; + var Component = class extends React.Component {}; + export {Component}; + export default class ComponentB extends React.Component{}; + `; + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + import React from 'React'; + var Component = class extends React.Component {}; + export default class ComponentB extends React.Component{}; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('supports imported components', () => { + const source = ` + import Component from 'classDec'; + export default Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + }); + }); + + describe('export var foo = , ...;', () => { + describe('React.createClass', () => { + test('finds named exports with export var', () => { + const source = ` + var React = require("React"); + export var somethingElse = 42, Component = React.createClass({}); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds named exports with export let', () => { + const source = ` + var React = require("React"); + export let Component = React.createClass({}), somethingElse = 42; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds named exports with export const', () => { + const source = ` + var React = require("React"); + export const something = 21, + Component = React.createClass({}), + somethingElse = 42; + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds named exports with export let and additional export', () => { + const source = ` + var React = require("React"); + export var somethingElse = function() {}; + export let Component = React.createClass({}); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('errors if multiple components are exported', () => { + let source = ` + var R = require("React"); + export var ComponentA = R.createClass({}), + ComponentB = R.createClass({}); + `; + + expect(() => findComponentsInSource(source)).toThrow(); + + source = ` + var R = require("React"); + export var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + export {ComponentB}; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + var R = require("React"); + var ComponentA = R.createClass({}); + export let ComponentB = R.createClass({}); + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('supports imported components', () => { + const source = ` + import Component from 'createClass'; + export let ComponentB = Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + }); + + describe('class definition', () => { + test('finds named exports', () => { + let source = ` + import React from 'React'; + export var somethingElse = 42, + Component = class extends React.Component {}; + `; + let result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + + source = ` + import React from 'React'; + export let Component = class extends React.Component {}, + somethingElse = 42; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + + source = ` + import React from 'React'; + export const something = 21, + Component = class extends React.Component {}, + somethingElse = 42; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + + source = ` + import React from 'React'; + export var somethingElse = function() {}; + export let Component = class extends React.Component {}; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + }); + + test('errors if multiple components are exported', () => { + let source = ` + import React from 'React'; + export var ComponentA = class extends React.Component {}; + export var ComponentB = class extends React.Component {}; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + + source = ` + import React from 'React'; + export var ComponentA = class extends React.Component {}; + var ComponentB = class extends React.Component {}; + export {ComponentB}; + `; + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + import React from 'React'; + var ComponentA = class extends React.Component {} + export var ComponentB = class extends React.Component {}; + `; + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + }); + + test('supports imported components', () => { + const source = ` + import Component from 'classDec'; + export let ComponentB = Component; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + }); + + describe('stateless components', () => { + test('finds named exports', () => { + let source = ` + import React from 'React'; + export var somethingElse = 42, + Component = () =>
; + `; + let result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + + source = ` + import React from 'React'; + export let Component = () =>
, + somethingElse = 42; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + + source = ` + import React from 'React'; + export const something = 21, + Component = () =>
, + somethingElse = 42; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + + source = ` + import React from 'React'; + export var somethingElse = function() {}; + export let Component = () =>
+ `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + }); + + test('errors if multiple components are exported', () => { + let source = ` + import React from 'React'; + export var ComponentA = () =>
+ export var ComponentB = () =>
+ `; + + expect(() => findComponentsInSource(source)).toThrow(); + + source = ` + import React from 'React'; + export var ComponentA = () =>
+ var ComponentB = () =>
+ export {ComponentB}; + `; + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + import React from 'React'; + var ComponentA = class extends React.Component {} + export var ComponentB = function() { return
; }; + `; + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('FunctionExpression'); + }); + + test('supports imported components', () => { + let source = ` + import Component from 'statelessJsx'; + export var ComponentA = Component; + `; + + let result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + + source = ` + import Component from 'statelessCreateElement'; + export var ComponentB = Component; + `; + + result = findComponentsInSource(source, mockImporter); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + }); + }); + }); + + describe('export {};', () => { + describe('React.createClass', () => { + test('finds exported specifiers 1', () => { + const source = ` + var React = require("React"); + var foo = 42; + var Component = React.createClass({}); + export {foo, Component} + `; + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds exported specifiers 2', () => { + const source = ` + import React from "React" + var foo = 42; + var Component = React.createClass({}); + export {Component, foo} + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('finds exported specifiers 3', () => { + const source = ` + import React, { createElement } from "React" + var foo = 42; + var baz = 21; + var Component = React.createClass({}); + export {foo, Component as bar, baz} + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('errors if multiple components are exported', () => { + const source = ` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + export {ComponentA as foo, ComponentB}; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + var R = require("React"); + var ComponentA = R.createClass({}); + var ComponentB = R.createClass({}); + export {ComponentA} + `; + + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + + test('supports imported components', () => { + const source = ` + import Component from 'createClass'; + export { Component }; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + }); + }); + + describe('class definition', () => { + test('finds exported specifiers', () => { + let source = ` + import React from 'React'; + var foo = 42; + var Component = class extends React.Component {}; + export {foo, Component}; + `; + let result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + + source = ` + import React from 'React'; + var foo = 42; + var Component = class extends React.Component {}; + export {Component, foo}; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + + source = ` + import React from 'React'; + var foo = 42; + var baz = 21; + var Component = class extends React.Component {}; + export {foo, Component as bar, baz}; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + }); + + test('errors if multiple components are exported', () => { + const source = ` + import React from 'React'; + var ComponentA = class extends React.Component {}; + var ComponentB = class extends React.Component {}; + export {ComponentA as foo, ComponentB}; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + import React from 'React'; + var ComponentA = class extends React.Component {}; + var ComponentB = class extends React.Component {}; + export {ComponentA}; + `; + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassExpression'); + }); + + test('supports imported components', () => { + const source = ` + import Component from 'classDec'; + export { Component }; + `; + + const result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + }); + + describe('stateless components', () => { + test('finds exported specifiers', () => { + let source = ` + import React from 'React'; + var foo = 42; + function Component() { return
; } + export {foo, Component}; + `; + let result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('FunctionDeclaration'); + + source = ` + import React from 'React'; + var foo = 42; + var Component = () =>
; + export {Component, foo}; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + + source = ` + import React from 'React'; + var foo = 42; + var baz = 21; + var Component = function () { return
; } + export {foo, Component as bar, baz}; + `; + result = findComponentsInSource(source); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('FunctionExpression'); + }); + + test('errors if multiple components are exported', () => { + const source = ` + import React from 'React'; + var ComponentA = () =>
; + function ComponentB() { return
; } + export {ComponentA as foo, ComponentB}; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + import React from 'React'; + var ComponentA = () =>
; + var ComponentB = () =>
; + export {ComponentA}; + `; + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + }); + + test('supports imported components', () => { + let source = ` + import Component from 'statelessJsx'; + export { Component as ComponentA }; + `; + + let result = findComponentsInSource(source, mockImporter); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + + source = ` + import Component from 'statelessCreateElement'; + export { Component as ComponentB }; + `; + + result = findComponentsInSource(source, mockImporter); + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ArrowFunctionExpression'); + }); + }); + }); + + // Only applies to classes + describe('export ;', () => { + test('finds named exports', () => { + const source = ` + import React from 'React'; + export var foo = 42; + export class Component extends React.Component {}; + `; + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + + test('errors if multiple components are exported', () => { + const source = ` + import React from 'React'; + export class ComponentA extends React.Component {}; + export class ComponentB extends React.Component {}; + `; + + expect(() => findComponentsInSource(source)).toThrow(); + }); + + test('accepts multiple definitions if only one is exported', () => { + const source = ` + import React from 'React'; + class ComponentA extends React.Component {}; + export class ComponentB extends React.Component {}; + `; + const result = findComponentsInSource(source); + + expect(Array.isArray(result)).toBe(true); + expect(result).toHaveLength(1); + expect(result[0].node.type).toBe('ClassDeclaration'); + }); + }); + }); + }); +}); diff --git a/packages/react-docgen/src/resolver/__tests__/__snapshots__/findAllExportedComponentDefinitions-test.ts.snap b/packages/react-docgen/src/resolver/__tests__/__snapshots__/FindExportedDefinitionsResolver-test.ts.snap similarity index 79% rename from packages/react-docgen/src/resolver/__tests__/__snapshots__/findAllExportedComponentDefinitions-test.ts.snap rename to packages/react-docgen/src/resolver/__tests__/__snapshots__/FindExportedDefinitionsResolver-test.ts.snap index bfa93165b42..68ff5a1c312 100644 --- a/packages/react-docgen/src/resolver/__tests__/__snapshots__/findAllExportedComponentDefinitions-test.ts.snap +++ b/packages/react-docgen/src/resolver/__tests__/__snapshots__/FindExportedDefinitionsResolver-test.ts.snap @@ -1,8 +1,8 @@ // Vitest Snapshot v1 -exports[`findAllExportedComponentDefinitions > CommonJS module exports > React.createClass > does not process X.createClass of other modules 1`] = `[]`; +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > React.createClass > does not process X.createClass of other modules 1`] = `[]`; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > React.createClass > finds React.createClass 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > React.createClass > finds React.createClass 1`] = ` [ Node { "properties": [], @@ -11,7 +11,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > React.c ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > React.createClass > finds React.createClass, independent of the var name 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > React.createClass > finds React.createClass, independent of the var name 1`] = ` [ Node { "properties": [], @@ -20,7 +20,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > React.c ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > React.createClass > resolves an imported variable to React.createClass 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > React.createClass > resolves an imported variable to React.createClass 1`] = ` [ Node { "properties": [], @@ -29,7 +29,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > React.c ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > class definitions > finds class declarations 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > class definitions > finds class declarations 1`] = ` [ Node { "body": Node { @@ -57,7 +57,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > class d ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > class definitions > finds class definition, independent of the var name 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > class definitions > finds class definition, independent of the var name 1`] = ` [ Node { "body": Node { @@ -85,7 +85,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > class d ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > class definitions > finds class expression 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > class definitions > finds class expression 1`] = ` [ Node { "body": Node { @@ -110,7 +110,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > class d ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > class definitions > resolves an imported variable to class declaration 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > class definitions > resolves an imported variable to class declaration 1`] = ` [ Node { "body": Node { @@ -138,7 +138,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > class d ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > class definitions > resolves an imported variable to class expression 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > class definitions > resolves an imported variable to class expression 1`] = ` [ Node { "body": Node { @@ -163,7 +163,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > class d ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > forwardRef components > finds forwardRef components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > forwardRef components > finds forwardRef components 1`] = ` [ Node { "arguments": [ @@ -271,7 +271,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > forward ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > forwardRef components > finds none inline forwardRef components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > forwardRef components > finds none inline forwardRef components 1`] = ` [ Node { "arguments": [ @@ -297,7 +297,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > forward ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > forwardRef components > resolves an imported forwardRef component 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > forwardRef components > resolves an imported forwardRef component 1`] = ` [ Node { "arguments": [ @@ -405,7 +405,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > forward ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds assignments to exports 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds assignments to exports 1`] = ` [ Node { "properties": [], @@ -414,7 +414,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds exported components only once 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds exported components only once 1`] = ` [ Node { "properties": [], @@ -423,7 +423,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds multiple exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds multiple exported components 1`] = ` [ Node { "properties": [], @@ -436,7 +436,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds multiple exported components with hocs 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds multiple exported components with hocs 1`] = ` [ Node { "properties": [], @@ -449,7 +449,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds only exported components 1`] = ` [ Node { "properties": [], @@ -458,7 +458,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds only exported components on export 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > finds only exported components on export 1`] = ` [ Node { "properties": [], @@ -467,7 +467,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > React.createClass > supports imported components 1`] = ` [ Node { "properties": [], @@ -476,7 +476,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds assignments to exports 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds assignments to exports 1`] = ` [ Node { "body": Node { @@ -504,7 +504,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds exported components only once 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds exported components only once 1`] = ` [ Node { "body": Node { @@ -532,7 +532,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds multiple exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds multiple exported components 1`] = ` [ Node { "body": Node { @@ -583,7 +583,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds only exported components 1`] = ` [ Node { "body": Node { @@ -611,7 +611,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds only exported components on export 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > finds only exported components on export 1`] = ` [ Node { "body": Node { @@ -639,7 +639,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > module.exports = ; / exports.foo = ; > class definition > supports imported components 1`] = ` [ Node { "body": Node { @@ -667,9 +667,9 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > module. ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > stateless components > does not process X.createElement of other modules 1`] = `[]`; +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > stateless components > does not process X.createElement of other modules 1`] = `[]`; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > stateless components > finds stateless component with JSX 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > stateless components > finds stateless component with JSX 1`] = ` [ Node { "async": false, @@ -695,7 +695,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > statele ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > stateless components > finds stateless components with React.createElement, independent of the var name 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > stateless components > finds stateless components with React.createElement, independent of the var name 1`] = ` [ Node { "async": false, @@ -736,7 +736,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > statele ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > stateless components > resolves an imported stateless component with JSX 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > stateless components > resolves an imported stateless component with JSX 1`] = ` [ Node { "async": false, @@ -762,7 +762,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > statele ] `; -exports[`findAllExportedComponentDefinitions > CommonJS module exports > stateless components > resolves an imported stateless component with React.createElement 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > CommonJS module exports > stateless components > resolves an imported stateless component with React.createElement 1`] = ` [ Node { "async": false, @@ -803,7 +803,7 @@ exports[`findAllExportedComponentDefinitions > CommonJS module exports > statele ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ; > class definition > finds multiple components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export ; > class definition > finds multiple components 1`] = ` [ Node { "body": Node { @@ -854,7 +854,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ; > class definition > finds named exports 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export ; > class definition > finds named exports 1`] = ` [ Node { "body": Node { @@ -882,7 +882,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ; > class definition > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export ; > class definition > finds only exported components 1`] = ` [ Node { "body": Node { @@ -910,7 +910,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ; > function declaration > finds multiple components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export ; > function declaration > finds multiple components 1`] = ` [ Node { "async": false, @@ -981,7 +981,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ; > function declaration > finds named exports 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export ; > function declaration > finds named exports 1`] = ` [ Node { "async": false, @@ -1019,7 +1019,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ; > function declaration > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export ; > function declaration > finds only exported components 1`] = ` [ Node { "async": false, @@ -1057,7 +1057,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > React.createClass > finds exported components only once 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > React.createClass > finds exported components only once 1`] = ` [ Node { "properties": [], @@ -1066,7 +1066,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > React.createClass > finds exported specifiers 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > React.createClass > finds exported specifiers 1`] = ` [ Node { "properties": [], @@ -1075,7 +1075,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > React.createClass > finds exported specifiers 2 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > React.createClass > finds exported specifiers 2 1`] = ` [ Node { "properties": [], @@ -1084,7 +1084,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > React.createClass > finds exported specifiers 3 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > React.createClass > finds exported specifiers 3 1`] = ` [ Node { "properties": [], @@ -1093,7 +1093,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > React.createClass > finds multiple components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > React.createClass > finds multiple components 1`] = ` [ Node { "properties": [], @@ -1106,7 +1106,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > React.createClass > finds multiple components with hocs 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > React.createClass > finds multiple components with hocs 1`] = ` [ Node { "properties": [], @@ -1119,7 +1119,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > React.createClass > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > React.createClass > finds only exported components 1`] = ` [ Node { "properties": [], @@ -1128,7 +1128,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > React.createClass > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > React.createClass > supports imported components 1`] = ` [ Node { "properties": [], @@ -1137,7 +1137,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > class definition > finds exported components only once 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > class definition > finds exported components only once 1`] = ` [ Node { "body": Node { @@ -1162,7 +1162,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > class definition > finds exported specifiers 1 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > class definition > finds exported specifiers 1 1`] = ` [ Node { "body": Node { @@ -1187,7 +1187,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > class definition > finds exported specifiers 2 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > class definition > finds exported specifiers 2 1`] = ` [ Node { "body": Node { @@ -1212,7 +1212,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > class definition > finds exported specifiers 3 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > class definition > finds exported specifiers 3 1`] = ` [ Node { "body": Node { @@ -1237,7 +1237,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > class definition > finds multiple components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > class definition > finds multiple components 1`] = ` [ Node { "body": Node { @@ -1282,7 +1282,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > class definition > finds multiple components with hocs 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > class definition > finds multiple components with hocs 1`] = ` [ Node { "body": Node { @@ -1333,7 +1333,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > class definition > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > class definition > finds only exported components 1`] = ` [ Node { "body": Node { @@ -1358,7 +1358,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > class definition > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > class definition > supports imported components 1`] = ` [ Node { "body": Node { @@ -1386,7 +1386,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > forwardRef components > finds forwardRef components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > forwardRef components > finds forwardRef components 1`] = ` [ Node { "arguments": [ @@ -1494,7 +1494,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > forwardRef components > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > forwardRef components > supports imported components 1`] = ` [ Node { "arguments": [ @@ -1602,7 +1602,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > stateless components > finds exported components only once 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > stateless components > finds exported components only once 1`] = ` [ Node { "async": false, @@ -1628,7 +1628,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > stateless components > finds exported specifiers 1 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > stateless components > finds exported specifiers 1 1`] = ` [ Node { "async": false, @@ -1666,7 +1666,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > stateless components > finds exported specifiers 2 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > stateless components > finds exported specifiers 2 1`] = ` [ Node { "async": false, @@ -1692,7 +1692,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > stateless components > finds exported specifiers 3 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > stateless components > finds exported specifiers 3 1`] = ` [ Node { "async": false, @@ -1727,7 +1727,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > stateless components > finds multiple components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > stateless components > finds multiple components 1`] = ` [ Node { "async": false, @@ -1786,7 +1786,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > stateless components > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > stateless components > finds only exported components 1`] = ` [ Node { "async": false, @@ -1812,7 +1812,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export {}; > stateless components > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export {}; > stateless components > supports imported components 1`] = ` [ Node { "async": false, @@ -1874,7 +1874,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > React.createClass > finds default export 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > React.createClass > finds default export 1`] = ` [ Node { "properties": [], @@ -1883,7 +1883,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > React.createClass > finds multiple exported components with export var 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > React.createClass > finds multiple exported components with export var 1`] = ` [ Node { "properties": [], @@ -1896,7 +1896,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > React.createClass > finds multiple exported components with named export 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > React.createClass > finds multiple exported components with named export 1`] = ` [ Node { "properties": [], @@ -1909,7 +1909,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > React.createClass > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > React.createClass > finds only exported components 1`] = ` [ Node { "properties": [], @@ -1918,7 +1918,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > React.createClass > finds reassigned default export 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > React.createClass > finds reassigned default export 1`] = ` [ Node { "properties": [], @@ -1927,7 +1927,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > React.createClass > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > React.createClass > supports imported components 1`] = ` [ Node { "properties": [], @@ -1936,7 +1936,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > class definition > finds default export 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > class definition > finds default export 1`] = ` [ Node { "body": Node { @@ -1964,7 +1964,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > class definition > finds default export inline 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > class definition > finds default export inline 1`] = ` [ Node { "body": Node { @@ -1992,7 +1992,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > class definition > finds multiple exported components with export var 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > class definition > finds multiple exported components with export var 1`] = ` [ Node { "body": Node { @@ -2040,7 +2040,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > class definition > finds multiple exported components with named export 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > class definition > finds multiple exported components with named export 1`] = ` [ Node { "body": Node { @@ -2088,7 +2088,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > class definition > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > class definition > finds only exported components 1`] = ` [ Node { "body": Node { @@ -2116,7 +2116,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > class definition > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > class definition > supports imported components 1`] = ` [ Node { "body": Node { @@ -2144,7 +2144,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > forwardRef components > finds forwardRef components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > forwardRef components > finds forwardRef components 1`] = ` [ Node { "arguments": [ @@ -2252,7 +2252,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > forwardRef components > finds none inline forwardRef components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > forwardRef components > finds none inline forwardRef components 1`] = ` [ Node { "arguments": [ @@ -2278,7 +2278,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export default ; > forwardRef components > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export default ; > forwardRef components > supports imported components 1`] = ` [ Node { "arguments": [ @@ -2386,7 +2386,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > React.createClass > finds multiple components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > React.createClass > finds multiple components 1`] = ` [ Node { "properties": [], @@ -2399,7 +2399,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > React.createClass > finds multiple components with separate export statements 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > React.createClass > finds multiple components with separate export statements 1`] = ` [ Node { "properties": [], @@ -2412,7 +2412,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > React.createClass > finds named exports 1 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > React.createClass > finds named exports 1 1`] = ` [ Node { "properties": [], @@ -2421,7 +2421,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > React.createClass > finds named exports 2 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > React.createClass > finds named exports 2 1`] = ` [ Node { "properties": [], @@ -2430,7 +2430,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > React.createClass > finds named exports 3 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > React.createClass > finds named exports 3 1`] = ` [ Node { "properties": [], @@ -2439,7 +2439,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > React.createClass > finds named exports 4 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > React.createClass > finds named exports 4 1`] = ` [ Node { "properties": [], @@ -2448,7 +2448,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > React.createClass > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > React.createClass > finds only exported components 1`] = ` [ Node { "properties": [], @@ -2457,7 +2457,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > React.createClass > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > React.createClass > supports imported components 1`] = ` [ Node { "properties": [], @@ -2466,7 +2466,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > class definition > finds multiple components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > class definition > finds multiple components 1`] = ` [ Node { "body": Node { @@ -2511,7 +2511,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > class definition > finds multiple components with assigned component 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > class definition > finds multiple components with assigned component 1`] = ` [ Node { "body": Node { @@ -2556,7 +2556,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > class definition > finds named exports 1 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > class definition > finds named exports 1 1`] = ` [ Node { "body": Node { @@ -2581,7 +2581,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > class definition > finds named exports 2 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > class definition > finds named exports 2 1`] = ` [ Node { "body": Node { @@ -2606,7 +2606,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > class definition > finds named exports 3 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > class definition > finds named exports 3 1`] = ` [ Node { "body": Node { @@ -2631,7 +2631,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > class definition > finds named exports 4 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > class definition > finds named exports 4 1`] = ` [ Node { "body": Node { @@ -2656,7 +2656,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > class definition > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > class definition > finds only exported components 1`] = ` [ Node { "body": Node { @@ -2681,7 +2681,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > class definition > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > class definition > supports imported components 1`] = ` [ Node { "body": Node { @@ -2709,7 +2709,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > forwardRef components > finds forwardRef components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > forwardRef components > finds forwardRef components 1`] = ` [ Node { "arguments": [ @@ -2817,7 +2817,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > forwardRef components > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > forwardRef components > supports imported components 1`] = ` [ Node { "arguments": [ @@ -2925,7 +2925,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > stateless components > finds multiple components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > stateless components > finds multiple components 1`] = ` [ Node { "async": false, @@ -2972,7 +2972,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > stateless components > finds multiple components with named export 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > stateless components > finds multiple components with named export 1`] = ` [ Node { "async": false, @@ -3019,7 +3019,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > stateless components > finds named exports 1 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > stateless components > finds named exports 1 1`] = ` [ Node { "async": false, @@ -3045,7 +3045,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > stateless components > finds only exported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > stateless components > finds only exported components 1`] = ` [ Node { "async": false, @@ -3080,7 +3080,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > stateless components > supports imported components 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > stateless components > supports imported components 1`] = ` [ Node { "async": false, @@ -3142,7 +3142,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > stateless components > supports imported components 2 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > stateless components > supports imported components 2 1`] = ` [ Node { "async": false, @@ -3168,7 +3168,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > stateless components > supports imported components 3 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > stateless components > supports imported components 3 1`] = ` [ Node { "async": false, @@ -3194,7 +3194,7 @@ exports[`findAllExportedComponentDefinitions > ES6 export declarations > export ] `; -exports[`findAllExportedComponentDefinitions > ES6 export declarations > export var foo = , ...; > stateless components > supports imported components 4 1`] = ` +exports[`FindExportedDefinitionsResolver > no limit > ES6 export declarations > export var foo = , ...; > stateless components > supports imported components 4 1`] = ` [ Node { "async": false, diff --git a/packages/react-docgen/src/resolver/__tests__/findAllExportedComponentDefinitions-test.ts b/packages/react-docgen/src/resolver/__tests__/findAllExportedComponentDefinitions-test.ts deleted file mode 100644 index dbaab49b5c4..00000000000 --- a/packages/react-docgen/src/resolver/__tests__/findAllExportedComponentDefinitions-test.ts +++ /dev/null @@ -1,1240 +0,0 @@ -import type { NodePath } from '@babel/traverse'; -import { parse, noopImporter, makeMockImporter } from '../../../tests/utils'; -import findAllExportedComponentDefinitions from '../findAllExportedComponentDefinitions.js'; -import { describe, expect, test } from 'vitest'; - -describe('findAllExportedComponentDefinitions', () => { - function findComponentsInSource( - source: string, - importer = noopImporter, - ): NodePath[] { - return findAllExportedComponentDefinitions( - parse(source, {}, importer, true), - ); - } - - const mockImporter = makeMockImporter({ - createClass: stmtLast => - stmtLast(` - import React from 'react' - export default React.createClass({}) - `).get('declaration'), - - classDec: stmtLast => - stmtLast(` - import React from 'react' - export default class Component extends React.Component {} - `).get('declaration'), - - classExpr: stmtLast => - stmtLast(` - import React from 'react' - var Component = class extends React.Component {} - export default Component - `).get('declaration'), - - statelessJsx: stmtLast => - stmtLast(` - export default () =>
- `).get('declaration'), - - statelessCreateElement: stmtLast => - stmtLast(` - import React from 'react' - export default () => React.createElement('div', {}) - `).get('declaration'), - - forwardRef: stmtLast => - stmtLast(` - import React from 'react' - export default React.forwardRef((props, ref) => ( -
- )) - `).get('declaration'), - }); - - describe('CommonJS module exports', () => { - describe('React.createClass', () => { - test('finds React.createClass', () => { - const result = findComponentsInSource(` - var React = require("React"); - var Component = React.createClass({}); - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds React.createClass, independent of the var name', () => { - const result = findComponentsInSource(` - var R = require("React"); - var Component = R.createClass({}); - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('does not process X.createClass of other modules', () => { - const result = findComponentsInSource(` - var R = require("NoReact"); - var Component = R.createClass({}); - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('resolves an imported variable to React.createClass', () => { - const result = findComponentsInSource( - ` - import Component from 'createClass'; - module.exports = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('class definitions', () => { - test('finds class declarations', () => { - const result = findComponentsInSource(` - var React = require("React"); - class Component extends React.Component {} - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds class expression', () => { - const result = findComponentsInSource(` - var React = require("React"); - var Component = class extends React.Component {} - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds class definition, independent of the var name', () => { - const result = findComponentsInSource(` - var R = require("React"); - class Component extends R.Component {} - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('resolves an imported variable to class declaration', () => { - const result = findComponentsInSource( - ` - import Component from 'classDec'; - module.exports = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - - test('resolves an imported variable to class expression', () => { - const result = findComponentsInSource( - ` - import Component from 'classExpr'; - module.exports = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('stateless components', () => { - test('finds stateless component with JSX', () => { - const result = findComponentsInSource(` - var React = require("React"); - var Component = () =>
; - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds stateless components with React.createElement, independent of the var name', () => { - const result = findComponentsInSource(` - var R = require("React"); - var Component = () => R.createElement('div', {}); - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('does not process X.createElement of other modules', () => { - const result = findComponentsInSource(` - var R = require("NoReact"); - var Component = () => R.createElement({}); - module.exports = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('resolves an imported stateless component with JSX', () => { - const result = findComponentsInSource( - ` - import Component from 'statelessJsx'; - module.exports = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - - test('resolves an imported stateless component with React.createElement', () => { - const result = findComponentsInSource( - ` - import Component from 'statelessCreateElement'; - module.exports = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('forwardRef components', () => { - test('finds forwardRef components', () => { - const result = findComponentsInSource(` - import React from 'react'; - import PropTypes from 'prop-types'; - import extendStyles from 'enhancers/extendStyles'; - - const ColoredView = React.forwardRef((props, ref) => ( -
- )); - - module.exports = extendStyles(ColoredView); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds none inline forwardRef components', () => { - const result = findComponentsInSource(` - import React from 'react'; - import PropTypes from 'prop-types'; - import extendStyles from 'enhancers/extendStyles'; - - function ColoredView(props, ref) { - return
- } - - const ForwardedColoredView = React.forwardRef(ColoredView); - - module.exports = ForwardedColoredView - `); - - expect(result).toMatchSnapshot(); - }); - - test('resolves an imported forwardRef component', () => { - const result = findComponentsInSource( - ` - import Component from 'forwardRef'; - module.exports = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('module.exports = ; / exports.foo = ;', () => { - describe('React.createClass', () => { - test('finds assignments to exports', () => { - const result = findComponentsInSource(` - var R = require("React"); - var Component = R.createClass({}); - exports.foo = 42; - exports.Component = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple exported components', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - exports.ComponentA = ComponentA; - exports.ComponentB = ComponentB; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple exported components with hocs', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - exports.ComponentA = hoc(ComponentA); - exports.ComponentB = hoc(ComponentB); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components on export', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - exports.ComponentB = ComponentB; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - module.exports = ComponentB; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported components only once', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - exports.ComponentA = ComponentA; - exports.ComponentB = ComponentA; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - ` - import Component from 'createClass'; - exports.ComponentA = Component; - exports.ComponentB = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('class definition', () => { - test('finds assignments to exports', () => { - const result = findComponentsInSource(` - var R = require("React"); - class Component extends R.Component {} - exports.foo = 42; - exports.Component = Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple exported components', () => { - const result = findComponentsInSource(` - var R = require("React"); - class ComponentA extends R.Component {} - class ComponentB extends R.Component {} - exports.ComponentA = ComponentA; - exports.ComponentB = ComponentB; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components on export', () => { - const result = findComponentsInSource(` - var R = require("React"); - class ComponentA extends R.Component {} - class ComponentB extends R.Component {} - exports.ComponentB = ComponentB; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - var R = require("React"); - class ComponentA extends R.Component {} - class ComponentB extends R.Component {} - module.exports = ComponentB; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported components only once', () => { - const result = findComponentsInSource(` - var R = require("React"); - class ComponentA extends R.Component {} - exports.ComponentA = ComponentA; - exports.ComponentB = ComponentA; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - ` - import Component from 'classDec'; - exports.ComponentA = Component; - exports.ComponentB = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - }); - }); - - describe('ES6 export declarations', () => { - describe('export default ;', () => { - describe('React.createClass', () => { - test('finds reassigned default export', () => { - const result = findComponentsInSource(` - var React = require("React"); - var Component = React.createClass({}); - export default Component - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds default export', () => { - const result = findComponentsInSource(` - var React = require("React"); - export default React.createClass({}); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple exported components with export var', () => { - const result = findComponentsInSource(` - import React, { createElement } from "React" - export var Component = React.createClass({}); - export default React.createClass({}); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple exported components with named export', () => { - const result = findComponentsInSource(` - import React, { createElement } from "React" - var Component = React.createClass({}) - export {Component}; - export default React.createClass({}); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - import React, { createElement } from "React" - var Component = React.createClass({}) - export default React.createClass({}); - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - `import Component from 'createClass'; - export default Component;`, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('class definition', () => { - test('finds default export', () => { - const result = findComponentsInSource(` - import React from 'React'; - class Component extends React.Component {} - export default Component; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds default export inline', () => { - const result = findComponentsInSource(` - import React from 'React'; - export default class Component extends React.Component {}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple exported components with export var', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var Component = class extends React.Component {}; - export default class ComponentB extends React.Component{}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple exported components with named export', () => { - const result = findComponentsInSource(` - import React from 'React'; - var Component = class extends React.Component {}; - export {Component}; - export default class ComponentB extends React.Component{}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - import React from 'React'; - var Component = class extends React.Component {}; - export default class ComponentB extends React.Component{}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - `import Component from 'classDec'; - export default Component;`, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('forwardRef components', () => { - test('finds forwardRef components', () => { - const result = findComponentsInSource(` - import React from 'react'; - import PropTypes from 'prop-types'; - import extendStyles from 'enhancers/extendStyles'; - - const ColoredView = React.forwardRef((props, ref) => ( -
- )); - - export default extendStyles(ColoredView); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds none inline forwardRef components', () => { - const result = findComponentsInSource(` - import React from 'react'; - import PropTypes from 'prop-types'; - import extendStyles from 'enhancers/extendStyles'; - - function ColoredView(props, ref) { - return
- } - - const ForwardedColoredView = React.forwardRef(ColoredView); - - export default ForwardedColoredView - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - `import Component from 'forwardRef'; - export default Component;`, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - }); - - describe('export var foo = , ...;', () => { - describe('React.createClass', () => { - test('finds named exports 1', () => { - const result = findComponentsInSource(` - var React = require("React"); - export var somethingElse = 42, Component = React.createClass({}); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds named exports 2', () => { - const result = findComponentsInSource(` - var React = require("React"); - export let Component = React.createClass({}), somethingElse = 42; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds named exports 3', () => { - const result = findComponentsInSource(` - var React = require("React"); - export const something = 21, - Component = React.createClass({}), - somethingElse = 42; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds named exports 4', () => { - const result = findComponentsInSource(` - var React = require("React"); - export var somethingElse = function() {}; - export let Component = React.createClass({}); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components', () => { - const result = findComponentsInSource(` - var R = require("React"); - export var ComponentA = R.createClass({}), - ComponentB = R.createClass({}); - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components with separate export statements', () => { - const result = findComponentsInSource(` - var R = require("React"); - export var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - export {ComponentB}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - export let ComponentB = R.createClass({}); - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - `import Component from 'createClass'; - export let ComponentA = Component; - export let ComponentB = Component;`, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('class definition', () => { - test('finds named exports 1', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var somethingElse = 42, - Component = class extends React.Component {}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds named exports 2', () => { - const result = findComponentsInSource(` - import React from 'React'; - export let Component = class extends React.Component {}, - somethingElse = 42; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds named exports 3', () => { - const result = findComponentsInSource(` - import React from 'React'; - export const something = 21, - Component = class extends React.Component {}, - somethingElse = 42; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds named exports 4', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var somethingElse = function() {}; - export let Component = class extends React.Component {}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var ComponentA = class extends React.Component {}; - export var ComponentB = class extends React.Component {}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components with assigned component', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var ComponentA = class extends React.Component {}; - var ComponentB = class extends React.Component {}; - export {ComponentB}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - import React from 'React'; - var ComponentA = class extends React.Component {} - export var ComponentB = class extends React.Component {}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - ` - import Component from 'classDec'; - export let ComponentA = Component; - export let ComponentB = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('stateless components', () => { - test('finds named exports 1', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var somethingElse = 42, - Component = () =>
; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components 2', () => { - const result = findComponentsInSource(` - import React from 'React'; - export let Component = () =>
, - somethingElse = 42; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components 3', () => { - const result = findComponentsInSource(` - import React from 'React'; - export const something = 21, - Component = () =>
, - somethingElse = 42; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components 4', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var somethingElse = function() {}; - export let Component = () =>
- `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var ComponentA = () =>
- export var ComponentB = () =>
- `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components with named export', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var ComponentA = () =>
- var ComponentB = () =>
- export {ComponentB}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - import React from 'React'; - var ComponentA = class extends React.Component {} - export var ComponentB = function() { return
; }; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - `import Component1 from 'statelessJsx'; - import Component2 from 'statelessCreateElement'; - export var ComponentA = Component1, ComponentB = Component2;`, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('forwardRef components', () => { - test('finds forwardRef components', () => { - const result = findComponentsInSource(` - import React from 'react'; - import PropTypes from 'prop-types'; - import extendStyles from 'enhancers/extendStyles'; - - export const ColoredView = extendStyles(React.forwardRef((props, ref) => ( -
- ))); - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - ` - import Component from 'forwardRef'; - export let ComponentA = Component; - export let ComponentB = Component; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - }); - - describe('export {};', () => { - describe('React.createClass', () => { - test('finds exported specifiers', () => { - const result = findComponentsInSource(` - var React = require("React"); - var foo = 42; - var Component = React.createClass({}); - export {foo, Component} - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported specifiers 2', () => { - const result = findComponentsInSource(` - import React from "React" - var foo = 42; - var Component = React.createClass({}); - export {Component, foo} - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported specifiers 3', () => { - const result = findComponentsInSource(` - import React, { createElement } from "React" - var foo = 42; - var baz = 21; - var Component = React.createClass({}); - export {foo, Component as bar, baz} - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - export {ComponentA as foo, ComponentB}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components with hocs', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = hoc(R.createClass({})); - var ComponentB = hoc(R.createClass({})); - export {ComponentA as foo, ComponentB}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - export {ComponentA} - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported components only once', () => { - const result = findComponentsInSource(` - var R = require("React"); - var ComponentA = R.createClass({}); - export {ComponentA as foo, ComponentA as bar}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - `import Component from 'createClass'; - export { Component, Component as ComponentB };`, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('class definition', () => { - test('finds exported specifiers 1', () => { - const result = findComponentsInSource(` - import React from 'React'; - var foo = 42; - var Component = class extends React.Component {}; - export {foo, Component}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported specifiers 2', () => { - const result = findComponentsInSource(` - import React from 'React'; - var foo = 42; - var Component = class extends React.Component {}; - export {Component, foo}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported specifiers 3', () => { - const result = findComponentsInSource(` - import React from 'React'; - var foo = 42; - var baz = 21; - var Component = class extends React.Component {}; - export {foo, Component as bar, baz}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components', () => { - const result = findComponentsInSource(` - import React from 'React'; - var ComponentA = class extends React.Component {}; - var ComponentB = class extends React.Component {}; - export {ComponentA as foo, ComponentB}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components with hocs', () => { - const result = findComponentsInSource(` - import React from 'React'; - class ComponentA extends React.Component {}; - class ComponentB extends React.Component {}; - var WrappedA = hoc(ComponentA); - var WrappedB = hoc(ComponentB); - export {WrappedA, WrappedB}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - import React from 'React'; - var ComponentA = class extends React.Component {}; - var ComponentB = class extends React.Component {}; - export {ComponentA}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported components only once', () => { - const result = findComponentsInSource(` - import React from 'React'; - var ComponentA = class extends React.Component {}; - var ComponentB = class extends React.Component {}; - export {ComponentA as foo, ComponentA as bar}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - ` - import Component from 'classDec'; - export { Component, Component as ComponentB }; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('stateless components', () => { - test('finds exported specifiers 1', () => { - const result = findComponentsInSource(` - import React from 'React'; - var foo = 42; - function Component() { return
; } - export {foo, Component}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported specifiers 2', () => { - const result = findComponentsInSource(` - import React from 'React'; - var foo = 42; - var Component = () =>
; - export {Component, foo}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported specifiers 3', () => { - const result = findComponentsInSource(` - import React from 'React'; - var foo = 42; - var baz = 21; - var Component = function () { return
; } - export {foo, Component as bar, baz}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components', () => { - const result = findComponentsInSource(` - import React from 'React'; - var ComponentA = () =>
; - function ComponentB() { return
; } - export {ComponentA as foo, ComponentB}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - import React from 'React'; - var ComponentA = () =>
; - var ComponentB = () =>
; - export {ComponentA}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds exported components only once', () => { - const result = findComponentsInSource(` - import React from 'React'; - var ComponentA = () =>
; - var ComponentB = () =>
; - export {ComponentA as foo, ComponentA as bar}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - ` - import ComponentA from 'statelessJsx'; - import ComponentB from 'statelessCreateElement'; - export { ComponentA, ComponentB }; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('forwardRef components', () => { - test('finds forwardRef components', () => { - const result = findComponentsInSource(` - import React from 'react'; - import PropTypes from 'prop-types'; - import extendStyles from 'enhancers/extendStyles'; - - const ColoredView = extendStyles(React.forwardRef((props, ref) => ( -
- ))); - - export { ColoredView } - `); - - expect(result).toMatchSnapshot(); - }); - - test('supports imported components', () => { - const result = findComponentsInSource( - ` - import Component from 'forwardRef'; - export { Component, Component as ComponentB }; - `, - mockImporter, - ); - - expect(result).toMatchSnapshot(); - }); - }); - }); - - describe('export ;', () => { - describe('class definition', () => { - test('finds named exports', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var foo = 42; - export class Component extends React.Component {}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components', () => { - const result = findComponentsInSource(` - import React from 'React'; - export class ComponentA extends React.Component {}; - export class ComponentB extends React.Component {}; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - import React from 'React'; - class ComponentA extends React.Component {}; - export class ComponentB extends React.Component {}; - `); - - expect(result).toMatchSnapshot(); - }); - }); - - describe('function declaration', () => { - test('finds named exports', () => { - const result = findComponentsInSource(` - import React from 'React'; - export var foo = 42; - export function Component() { return
; }; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds multiple components', () => { - const result = findComponentsInSource(` - import React from 'React'; - export function ComponentA() { return
; }; - export function ComponentB() { return
; }; - `); - - expect(result).toMatchSnapshot(); - }); - - test('finds only exported components', () => { - const result = findComponentsInSource(` - import React from 'React'; - function ComponentA() { return
; } - export function ComponentB() { return
; }; - `); - - expect(result).toMatchSnapshot(); - }); - }); - }); - }); -}); diff --git a/packages/react-docgen/src/resolver/__tests__/findExportedComponentDefinition-test.ts b/packages/react-docgen/src/resolver/__tests__/findExportedComponentDefinition-test.ts deleted file mode 100644 index 59b5514950f..00000000000 --- a/packages/react-docgen/src/resolver/__tests__/findExportedComponentDefinition-test.ts +++ /dev/null @@ -1,1205 +0,0 @@ -import type { NodePath } from '@babel/traverse'; -import { noopImporter, makeMockImporter, parse } from '../../../tests/utils'; -import findExportedComponentDefinition from '../findExportedComponentDefinition.js'; -import { describe, expect, test } from 'vitest'; - -describe('findExportedComponentDefinition', () => { - function findComponentsInSource( - source: string, - importer = noopImporter, - ): NodePath[] { - return findExportedComponentDefinition(parse(source, {}, importer, true)); - } - - const mockImporter = makeMockImporter({ - createClass: stmtLast => - stmtLast(` - import React from 'react' - export default React.createClass({}) - `).get('declaration'), - - classDec: stmtLast => - stmtLast(` - import React from 'react' - export default class Component extends React.Component {} - `).get('declaration'), - - classExpr: stmtLast => - stmtLast(` - import React from 'react' - var Component = class extends React.Component {} - export default Component - `).get('declaration'), - - statelessJsx: stmtLast => - stmtLast(`export default () =>
`).get('declaration'), - - statelessCreateElement: stmtLast => - stmtLast(` - import React from 'react' - export default () => React.createElement('div', {}) - `).get('declaration'), - - forwardRef: stmtLast => - stmtLast(` - import React from 'react' - export default React.forwardRef((props, ref) => ( -
- )) - `).get('declaration'), - }); - - describe('CommonJS module exports', () => { - describe('React.createClass', () => { - test('finds React.createClass', () => { - const source = ` - var React = require("React"); - var Component = React.createClass({}); - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds React.createClass with hoc', () => { - const source = ` - var React = require("React"); - var Component = React.createClass({}); - module.exports = hoc(Component); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds React.createClass with hoc and args', () => { - const source = ` - var React = require("React"); - var Component = React.createClass({}); - module.exports = hoc(arg1, arg2)(Component); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds React.createClass with two hocs', () => { - const source = ` - var React = require("React"); - var Component = React.createClass({}); - module.exports = hoc2(arg2b, arg2b)( - hoc1(arg1a, arg2a)(Component) - ); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds React.createClass with three hocs', () => { - const source = ` - var React = require("React"); - var Component = React.createClass({}); - module.exports = hoc3(arg3a, arg3b)( - hoc2(arg2b, arg2b)( - hoc1(arg1a, arg2a)(Component) - ) - ); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds React.createClass, independent of the var name', () => { - const source = ` - var R = require("React"); - var Component = R.createClass({}); - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('does not process X.createClass of other modules', () => { - const source = ` - var R = require("NoReact"); - var Component = R.createClass({}); - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(0); - }); - - test('resolves an imported variable to React.createClass', () => { - const source = ` - import Component from 'createClass'; - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(0); - }); - }); - - describe('class definitions', () => { - test('finds class declarations', () => { - const source = ` - var React = require("React"); - class Component extends React.Component {} - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('finds class expression', () => { - const source = ` - var React = require("React"); - var Component = class extends React.Component {} - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - }); - - test('finds class definition, independent of the var name', () => { - const source = ` - var R = require("React"); - class Component extends R.Component {} - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('resolves an imported variable to class declaration', () => { - const source = ` - import Component from 'classDec'; - module.exports = Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('resolves an imported variable to class expression', () => { - const source = ` - import Component from 'classExpr'; - module.exports = Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - }); - }); - - describe('stateless components', () => { - test('finds stateless component with JSX', () => { - const source = ` - var React = require("React"); - var Component = () =>
; - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds stateless components with React.createElement, independent of the var name', () => { - const source = ` - var R = require("React"); - var Component = () => R.createElement('div', {}); - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('does not process X.createElement of other modules', () => { - const source = ` - var R = require("NoReact"); - var Component = () => R.createElement({}); - module.exports = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(0); - }); - - test('resolves an imported stateless component with JSX', () => { - const source = ` - import Component from 'statelessJsx'; - module.exports = Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('resolves an imported stateless component with React.createElement', () => { - const source = ` - import Component from 'statelessCreateElement'; - module.exports = Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - }); - - describe('module.exports = ; / exports.foo = ;', () => { - describe('React.createClass', () => { - test('finds assignments to exports', () => { - const source = ` - var R = require("React"); - var Component = R.createClass({}); - exports.foo = 42; - exports.Component = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('errors if multiple components are exported', () => { - const source = ` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - exports.ComponentA = ComponentA; - exports.ComponentB = ComponentB; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported on exports', () => { - const source = ` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - exports.ComponentB = ComponentB; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - module.exports = ComponentB; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('supports imported components', () => { - const source = ` - import Component from 'createClass'; - exports.ComponentB = Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - }); - - describe('class definition', () => { - test('finds assignments to exports', () => { - const source = ` - var R = require("React"); - class Component extends R.Component {} - exports.foo = 42; - exports.Component = Component; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('errors if multiple components are exported', () => { - const source = ` - var R = require("React"); - class ComponentA extends R.Component {} - class ComponentB extends R.Component {} - exports.ComponentA = ComponentA; - exports.ComponentB = ComponentB; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - let source = ` - var R = require("React"); - class ComponentA extends R.Component {} - class ComponentB extends R.Component {} - exports.ComponentB = ComponentB; - `; - - let result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - - source = ` - var R = require("React"); - class ComponentA extends R.Component {} - class ComponentB extends R.Component {} - module.exports = ComponentB; - `; - - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('supports imported components', () => { - const source = ` - import Component from 'classDec'; - exports.ComponentB = Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - }); - }); - }); - - describe('ES6 export declarations', () => { - describe('export default ;', () => { - describe('React.createClass', () => { - test('finds default export', () => { - const source = ` - var React = require("React"); - var Component = React.createClass({}); - export default Component - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds default export inline', () => { - const source = ` - var React = require("React"); - export default React.createClass({}); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('errors if multiple components are exported', () => { - const source = ` - import React, { createElement } from "React" - export var Component = React.createClass({}) - export default React.createClass({}); - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('errors if multiple components are exported with named export', () => { - const source = ` - import React, { createElement } from "React" - var Component = React.createClass({}) - export {Component}; - export default React.createClass({}); - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - import React, { createElement } from "React" - var Component = React.createClass({}) - export default React.createClass({}); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('supports imported components', () => { - const source = ` - import Component from 'createClass'; - export default Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - }); - - describe('class definition', () => { - test('finds default export', () => { - let source = ` - import React from 'React'; - class Component extends React.Component {} - export default Component; - `; - - let result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - - source = ` - import React from 'React'; - export default class Component extends React.Component {}; - `; - - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('finds default export with hoc', () => { - const source = ` - import React from 'React'; - class Component extends React.Component {} - export default hoc(Component); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('finds default export with hoc and args', () => { - const source = ` - import React from 'React'; - class Component extends React.Component {} - export default hoc(arg1, arg2)(Component); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('finds default export with two hocs', () => { - const source = ` - import React from 'React'; - class Component extends React.Component {} - export default hoc2(arg2b, arg2b)( - hoc1(arg1a, arg2a)(Component) - ); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('errors if multiple components are exported', () => { - let source = ` - import React from 'React'; - export var Component = class extends React.Component {}; - export default class ComponentB extends React.Component{}; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - - source = ` - import React from 'React'; - var Component = class extends React.Component {}; - export {Component}; - export default class ComponentB extends React.Component{}; - `; - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - import React from 'React'; - var Component = class extends React.Component {}; - export default class ComponentB extends React.Component{}; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('supports imported components', () => { - const source = ` - import Component from 'classDec'; - export default Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - }); - }); - - describe('export var foo = , ...;', () => { - describe('React.createClass', () => { - test('finds named exports with export var', () => { - const source = ` - var React = require("React"); - export var somethingElse = 42, Component = React.createClass({}); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds named exports with export let', () => { - const source = ` - var React = require("React"); - export let Component = React.createClass({}), somethingElse = 42; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds named exports with export const', () => { - const source = ` - var React = require("React"); - export const something = 21, - Component = React.createClass({}), - somethingElse = 42; - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds named exports with export let and additional export', () => { - const source = ` - var React = require("React"); - export var somethingElse = function() {}; - export let Component = React.createClass({}); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('errors if multiple components are exported', () => { - let source = ` - var R = require("React"); - export var ComponentA = R.createClass({}), - ComponentB = R.createClass({}); - `; - - expect(() => findComponentsInSource(source)).toThrow(); - - source = ` - var R = require("React"); - export var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - export {ComponentB}; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - var R = require("React"); - var ComponentA = R.createClass({}); - export let ComponentB = R.createClass({}); - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('supports imported components', () => { - const source = ` - import Component from 'createClass'; - export let ComponentB = Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - }); - - describe('class definition', () => { - test('finds named exports', () => { - let source = ` - import React from 'React'; - export var somethingElse = 42, - Component = class extends React.Component {}; - `; - let result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - - source = ` - import React from 'React'; - export let Component = class extends React.Component {}, - somethingElse = 42; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - - source = ` - import React from 'React'; - export const something = 21, - Component = class extends React.Component {}, - somethingElse = 42; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - - source = ` - import React from 'React'; - export var somethingElse = function() {}; - export let Component = class extends React.Component {}; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - }); - - test('errors if multiple components are exported', () => { - let source = ` - import React from 'React'; - export var ComponentA = class extends React.Component {}; - export var ComponentB = class extends React.Component {}; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - - source = ` - import React from 'React'; - export var ComponentA = class extends React.Component {}; - var ComponentB = class extends React.Component {}; - export {ComponentB}; - `; - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - import React from 'React'; - var ComponentA = class extends React.Component {} - export var ComponentB = class extends React.Component {}; - `; - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - }); - - test('supports imported components', () => { - const source = ` - import Component from 'classDec'; - export let ComponentB = Component; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - }); - - describe('stateless components', () => { - test('finds named exports', () => { - let source = ` - import React from 'React'; - export var somethingElse = 42, - Component = () =>
; - `; - let result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - - source = ` - import React from 'React'; - export let Component = () =>
, - somethingElse = 42; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - - source = ` - import React from 'React'; - export const something = 21, - Component = () =>
, - somethingElse = 42; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - - source = ` - import React from 'React'; - export var somethingElse = function() {}; - export let Component = () =>
- `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - }); - - test('errors if multiple components are exported', () => { - let source = ` - import React from 'React'; - export var ComponentA = () =>
- export var ComponentB = () =>
- `; - - expect(() => findComponentsInSource(source)).toThrow(); - - source = ` - import React from 'React'; - export var ComponentA = () =>
- var ComponentB = () =>
- export {ComponentB}; - `; - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - import React from 'React'; - var ComponentA = class extends React.Component {} - export var ComponentB = function() { return
; }; - `; - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('FunctionExpression'); - }); - - test('supports imported components', () => { - let source = ` - import Component from 'statelessJsx'; - export var ComponentA = Component; - `; - - let result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - - source = ` - import Component from 'statelessCreateElement'; - export var ComponentB = Component; - `; - - result = findComponentsInSource(source, mockImporter); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - }); - }); - }); - - describe('export {};', () => { - describe('React.createClass', () => { - test('finds exported specifiers 1', () => { - const source = ` - var React = require("React"); - var foo = 42; - var Component = React.createClass({}); - export {foo, Component} - `; - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds exported specifiers 2', () => { - const source = ` - import React from "React" - var foo = 42; - var Component = React.createClass({}); - export {Component, foo} - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('finds exported specifiers 3', () => { - const source = ` - import React, { createElement } from "React" - var foo = 42; - var baz = 21; - var Component = React.createClass({}); - export {foo, Component as bar, baz} - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('errors if multiple components are exported', () => { - const source = ` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - export {ComponentA as foo, ComponentB}; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - var R = require("React"); - var ComponentA = R.createClass({}); - var ComponentB = R.createClass({}); - export {ComponentA} - `; - - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - - test('supports imported components', () => { - const source = ` - import Component from 'createClass'; - export { Component }; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - }); - }); - - describe('class definition', () => { - test('finds exported specifiers', () => { - let source = ` - import React from 'React'; - var foo = 42; - var Component = class extends React.Component {}; - export {foo, Component}; - `; - let result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - - source = ` - import React from 'React'; - var foo = 42; - var Component = class extends React.Component {}; - export {Component, foo}; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - - source = ` - import React from 'React'; - var foo = 42; - var baz = 21; - var Component = class extends React.Component {}; - export {foo, Component as bar, baz}; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - }); - - test('errors if multiple components are exported', () => { - const source = ` - import React from 'React'; - var ComponentA = class extends React.Component {}; - var ComponentB = class extends React.Component {}; - export {ComponentA as foo, ComponentB}; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - import React from 'React'; - var ComponentA = class extends React.Component {}; - var ComponentB = class extends React.Component {}; - export {ComponentA}; - `; - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassExpression'); - }); - - test('supports imported components', () => { - const source = ` - import Component from 'classDec'; - export { Component }; - `; - - const result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - }); - - describe('stateless components', () => { - test('finds exported specifiers', () => { - let source = ` - import React from 'React'; - var foo = 42; - function Component() { return
; } - export {foo, Component}; - `; - let result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('FunctionDeclaration'); - - source = ` - import React from 'React'; - var foo = 42; - var Component = () =>
; - export {Component, foo}; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - - source = ` - import React from 'React'; - var foo = 42; - var baz = 21; - var Component = function () { return
; } - export {foo, Component as bar, baz}; - `; - result = findComponentsInSource(source); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('FunctionExpression'); - }); - - test('errors if multiple components are exported', () => { - const source = ` - import React from 'React'; - var ComponentA = () =>
; - function ComponentB() { return
; } - export {ComponentA as foo, ComponentB}; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - import React from 'React'; - var ComponentA = () =>
; - var ComponentB = () =>
; - export {ComponentA}; - `; - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - }); - - test('supports imported components', () => { - let source = ` - import Component from 'statelessJsx'; - export { Component as ComponentA }; - `; - - let result = findComponentsInSource(source, mockImporter); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - - source = ` - import Component from 'statelessCreateElement'; - export { Component as ComponentB }; - `; - - result = findComponentsInSource(source, mockImporter); - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ArrowFunctionExpression'); - }); - }); - }); - - // Only applies to classes - describe('export ;', () => { - test('finds named exports', () => { - const source = ` - import React from 'React'; - export var foo = 42; - export class Component extends React.Component {}; - `; - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - - test('errors if multiple components are exported', () => { - const source = ` - import React from 'React'; - export class ComponentA extends React.Component {}; - export class ComponentB extends React.Component {}; - `; - - expect(() => findComponentsInSource(source)).toThrow(); - }); - - test('accepts multiple definitions if only one is exported', () => { - const source = ` - import React from 'React'; - class ComponentA extends React.Component {}; - export class ComponentB extends React.Component {}; - `; - const result = findComponentsInSource(source); - - expect(Array.isArray(result)).toBe(true); - expect(result).toHaveLength(1); - expect(result[0].node.type).toBe('ClassDeclaration'); - }); - }); - }); -}); diff --git a/packages/react-docgen/src/resolver/findAllExportedComponentDefinitions.ts b/packages/react-docgen/src/resolver/findAllExportedComponentDefinitions.ts deleted file mode 100644 index f57b2c370ed..00000000000 --- a/packages/react-docgen/src/resolver/findAllExportedComponentDefinitions.ts +++ /dev/null @@ -1,110 +0,0 @@ -import isExportsOrModuleAssignment from '../utils/isExportsOrModuleAssignment.js'; -import resolveExportDeclaration from '../utils/resolveExportDeclaration.js'; -import resolveToValue from '../utils/resolveToValue.js'; -import resolveHOC from '../utils/resolveHOC.js'; -import type { NodePath } from '@babel/traverse'; -import { visitors } from '@babel/traverse'; -import { shallowIgnoreVisitors } from '../utils/traverse.js'; -import type { - ExportDefaultDeclaration, - ExportNamedDeclaration, -} from '@babel/types'; -import type FileState from '../FileState.js'; -import type { ComponentNode, Resolver } from './index.js'; -import resolveComponentDefinition, { - isComponentDefinition, -} from '../utils/resolveComponentDefinition.js'; - -interface TraverseState { - foundDefinitions: Array>; -} - -function exportDeclaration( - path: NodePath, - state: TraverseState, -): void { - resolveExportDeclaration(path) - .reduce((acc, definition) => { - if (isComponentDefinition(definition)) { - acc.push(definition); - } else { - const resolved = resolveToValue(resolveHOC(definition)); - - if (isComponentDefinition(resolved)) { - acc.push(resolved); - } - } - - return acc; - }, [] as Array>) - .forEach(definition => { - const resolved = resolveComponentDefinition(definition); - - if (resolved && !state.foundDefinitions.includes(resolved)) { - state.foundDefinitions.push(resolved); - } - }); - - return path.skip(); -} - -const explodedVisitors = visitors.explode({ - ...shallowIgnoreVisitors, - - ExportNamedDeclaration: { enter: exportDeclaration }, - ExportDefaultDeclaration: { enter: exportDeclaration }, - - AssignmentExpression: { - enter: function (path, state): void { - // Ignore anything that is not `exports.X = ...;` or - // `module.exports = ...;` - if (!isExportsOrModuleAssignment(path)) { - return path.skip(); - } - // Resolve the value of the right hand side. It should resolve to a call - // expression, something like React.createClass - let resolvedPath = resolveToValue(path.get('right')); - - if (!isComponentDefinition(resolvedPath)) { - resolvedPath = resolveToValue(resolveHOC(resolvedPath)); - if (!isComponentDefinition(resolvedPath)) { - return path.skip(); - } - } - const definition = resolveComponentDefinition(resolvedPath); - - if (definition && state.foundDefinitions.indexOf(definition) === -1) { - state.foundDefinitions.push(definition); - } - - return path.skip(); - }, - }, -}); - -/** - * Given an AST, this function tries to find the exported component definitions. - * - * The component definitions are either the ObjectExpression passed to - * `React.createClass` or a `class` definition extending `React.Component` or - * having a `render()` method. - * - * If a definition is part of the following statements, it is considered to be - * exported: - * - * modules.exports = Definition; - * exports.foo = Definition; - * export default Definition; - * export var Definition = ...; - */ -const findExportedComponentDefinitions: Resolver = function ( - file: FileState, -): Array> { - const state: TraverseState = { foundDefinitions: [] }; - - file.traverse(explodedVisitors, state); - - return state.foundDefinitions; -}; - -export default findExportedComponentDefinitions; diff --git a/packages/react-docgen/src/resolver/findExportedComponentDefinition.ts b/packages/react-docgen/src/resolver/findExportedComponentDefinition.ts deleted file mode 100644 index 4011ff3303c..00000000000 --- a/packages/react-docgen/src/resolver/findExportedComponentDefinition.ts +++ /dev/null @@ -1,129 +0,0 @@ -import isExportsOrModuleAssignment from '../utils/isExportsOrModuleAssignment.js'; -import resolveExportDeclaration from '../utils/resolveExportDeclaration.js'; -import resolveToValue from '../utils/resolveToValue.js'; -import resolveHOC from '../utils/resolveHOC.js'; -import type { NodePath } from '@babel/traverse'; -import { visitors } from '@babel/traverse'; -import { shallowIgnoreVisitors } from '../utils/traverse.js'; -import type { - ExportDefaultDeclaration, - ExportNamedDeclaration, -} from '@babel/types'; -import type { ComponentNode, Resolver } from './index.js'; -import type FileState from '../FileState.js'; -import resolveComponentDefinition, { - isComponentDefinition, -} from '../utils/resolveComponentDefinition.js'; -import { ERROR_CODES, ReactDocgenError } from '../error.js'; - -interface TraverseState { - foundDefinition: NodePath | null; -} - -function exportDeclaration( - path: NodePath, - state: TraverseState, -): void { - const definitions = resolveExportDeclaration(path).reduce( - (acc, definition) => { - if (isComponentDefinition(definition)) { - acc.push(definition); - } else { - const resolved = resolveToValue(resolveHOC(definition)); - - if (isComponentDefinition(resolved)) { - acc.push(resolved); - } - } - - return acc; - }, - [] as Array>, - ); - - if (definitions.length === 0) { - return path.skip(); - } - if (definitions.length > 1 || state.foundDefinition) { - // If a file exports multiple components, ... complain! - throw new ReactDocgenError(ERROR_CODES.MULTIPLE_DEFINITIONS); - } - const definition = resolveComponentDefinition(definitions[0]); - - if (definition) { - state.foundDefinition = definition; - } - - return path.skip(); -} - -const explodedVisitors = visitors.explode({ - ...shallowIgnoreVisitors, - - ExportNamedDeclaration: { enter: exportDeclaration }, - ExportDefaultDeclaration: { enter: exportDeclaration }, - - AssignmentExpression: { - enter: function (path, state): void { - // Ignore anything that is not `exports.X = ...;` or - // `module.exports = ...;` - if (!isExportsOrModuleAssignment(path)) { - return path.skip(); - } - // Resolve the value of the right hand side. It should resolve to a call - // expression, something like React.createClass - let resolvedPath = resolveToValue(path.get('right')); - - if (!isComponentDefinition(resolvedPath)) { - resolvedPath = resolveToValue(resolveHOC(resolvedPath)); - if (!isComponentDefinition(resolvedPath)) { - return path.skip(); - } - } - if (state.foundDefinition) { - // If a file exports multiple components, ... complain! - throw new ReactDocgenError(ERROR_CODES.MULTIPLE_DEFINITIONS); - } - const definition = resolveComponentDefinition(resolvedPath); - - if (definition) { - state.foundDefinition = definition; - } - - return path.skip(); - }, - }, -}); - -/** - * Given an AST, this function tries to find the exported component definition. - * - * The component definition is either the ObjectExpression passed to - * `React.createClass` or a `class` definition extending `React.Component` or - * having a `render()` method. - * - * If a definition is part of the following statements, it is considered to be - * exported: - * - * modules.exports = Definition; - * exports.foo = Definition; - * export default Definition; - * export var Definition = ...; - */ -const findExportedComponentDefinition: Resolver = function ( - file: FileState, -): Array> { - const state: TraverseState = { - foundDefinition: null, - }; - - file.traverse(explodedVisitors, state); - - if (state.foundDefinition) { - return [state.foundDefinition]; - } - - return []; -}; - -export default findExportedComponentDefinition; diff --git a/packages/react-docgen/src/resolver/index.ts b/packages/react-docgen/src/resolver/index.ts index 2966317d1f2..9732c8bdc6e 100644 --- a/packages/react-docgen/src/resolver/index.ts +++ b/packages/react-docgen/src/resolver/index.ts @@ -1,6 +1,6 @@ -import findAllComponentDefinitions from './findAllComponentDefinitions.js'; -import findAllExportedComponentDefinitions from './findAllExportedComponentDefinitions.js'; -import findExportedComponentDefinition from './findExportedComponentDefinition.js'; +import ChainResolver from './ChainResolver.js'; +import FindAllDefinitionsResolver from './FindAllDefinitionsResolver.js'; +import FindExportedDefinitionsResolver from './FindExportedDefinitionsResolver.js'; import type { NodePath } from '@babel/traverse'; import type FileState from '../FileState.js'; import type { @@ -27,10 +27,18 @@ export type ComponentNode = | ObjectExpression | StatelessComponentNode; -export type Resolver = (file: FileState) => Array>; +export type ComponentNodePath = NodePath; + +export type ResolverFunction = (file: FileState) => ComponentNodePath[]; + +export interface ResolverClass { + resolve: ResolverFunction; +} + +export type Resolver = ResolverClass | ResolverFunction; export { - findAllComponentDefinitions, - findAllExportedComponentDefinitions, - findExportedComponentDefinition, + FindAllDefinitionsResolver, + FindExportedDefinitionsResolver, + ChainResolver, }; diff --git a/packages/react-docgen/src/resolver/utils/__tests__/runResolver-test.ts b/packages/react-docgen/src/resolver/utils/__tests__/runResolver-test.ts new file mode 100644 index 00000000000..7c4e67f313b --- /dev/null +++ b/packages/react-docgen/src/resolver/utils/__tests__/runResolver-test.ts @@ -0,0 +1,39 @@ +import { describe, expect, test, vi } from 'vitest'; +import type { + ComponentNodePath, + ResolverClass, +} from '../../../resolver/index.js'; +import runResolver from '../runResolver'; +import FileStateMock from '../../../__mocks__/FileState.js'; + +const createEmptyClassResolver = (path?: ComponentNodePath) => + new (class implements ResolverClass { + resolve = vi.fn(() => { + return path ? [path] : []; + }); + })(); + +const createEmptyFunctionResolver = (path?: ComponentNodePath) => + vi.fn(() => (path ? [path] : [])); + +describe('runResolver', () => { + const fileStateMock = new FileStateMock(); + + test('does run function resolvers', () => { + const resolver = createEmptyFunctionResolver(); + + runResolver(resolver, fileStateMock); + + expect(resolver).toBeCalled(); + expect(resolver).toHaveBeenCalledWith(fileStateMock); + }); + + test('does run class resolvers', () => { + const resolver = createEmptyClassResolver(); + + runResolver(resolver, fileStateMock); + + expect(resolver.resolve).toBeCalled(); + expect(resolver.resolve).toHaveBeenCalledWith(fileStateMock); + }); +}); diff --git a/packages/react-docgen/src/resolver/utils/runResolver.ts b/packages/react-docgen/src/resolver/utils/runResolver.ts new file mode 100644 index 00000000000..a8f93a5e042 --- /dev/null +++ b/packages/react-docgen/src/resolver/utils/runResolver.ts @@ -0,0 +1,13 @@ +import type FileState from '../../FileState.js'; +import type { Resolver, ResolverClass, ResolverFunction } from '../index.js'; + +function isResolverClass(resolver: Resolver): resolver is ResolverClass { + return typeof resolver === 'object'; +} + +export default function runResolver( + resolver: Resolver, + file: FileState, +): ReturnType { + return isResolverClass(resolver) ? resolver.resolve(file) : resolver(file); +} diff --git a/packages/react-docgen/src/utils/__tests__/getMemberValuePath-test.ts b/packages/react-docgen/src/utils/__tests__/getMemberValuePath-test.ts index a7c48e5e952..6d8da488ab8 100644 --- a/packages/react-docgen/src/utils/__tests__/getMemberValuePath-test.ts +++ b/packages/react-docgen/src/utils/__tests__/getMemberValuePath-test.ts @@ -16,7 +16,6 @@ vi.mock('../getPropertyValuePath.js'); vi.mock('../getClassMemberValuePath.js'); vi.mock('../getMemberExpressionValuePath.js'); -// https://github.com/vitest-dev/vitest/issues/2381 describe('getMemberValuePath', () => { test('handles ObjectExpressions', () => { const path = parse.expression('{}'); diff --git a/packages/react-docgen/vitest.config.ts b/packages/react-docgen/vitest.config.ts index fb9f00e708d..dce3ae945b0 100644 --- a/packages/react-docgen/vitest.config.ts +++ b/packages/react-docgen/vitest.config.ts @@ -2,6 +2,7 @@ import { defineConfig } from 'vitest/config'; export default defineConfig({ test: { + name: 'lib', setupFiles: ['./tests/setupTestFramework.ts'], include: ['**/__tests__/**/*-test.ts', '**/tests/integration/**/*-test.ts'], deps: { diff --git a/packages/website/pages/docs/reference/cli.mdx b/packages/website/pages/docs/reference/cli.mdx index 4402b454748..a2a68240482 100644 --- a/packages/website/pages/docs/reference/cli.mdx +++ b/packages/website/pages/docs/reference/cli.mdx @@ -40,7 +40,7 @@ the following warnings: Any other errors will always make the process exit with status code `1`, independent of this option. -### `--handlers ` +### `--handler ` **Default**: find the current default handlers [on GitHub ↗](https://github.com/reactjs/react-docgen/blob/main/packages/react-docgen/src/config.ts#L38-L50) @@ -84,6 +84,8 @@ Store extracted information in the specified file instead of printing to This option will make the CLI print the output JSON with proper spacing and newlines. -### `--resolver ` +### `--resolver ` + +**Default**: `["find-exported-component"]` **TODO**