Skip to content

Commit

Permalink
fix: use require to load CJS plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish committed Nov 16, 2022
1 parent 8660ffc commit 98f844a
Show file tree
Hide file tree
Showing 5 changed files with 514 additions and 435 deletions.
105 changes: 50 additions & 55 deletions lib/package-json.ts
@@ -1,9 +1,12 @@
import { join, resolve, extname, basename, dirname } from 'node:path';
import { join, resolve, basename, dirname } from 'node:path';
import { existsSync, readFileSync, readdirSync } from 'node:fs';
import { importAbs } from './import.js';
import { createRequire } from 'node:module';
import type { Plugin } from './types.js';
import type { PackageJson } from 'type-fest';

const require = createRequire(import.meta.url);

export function getPluginRoot(path: string) {
return join(process.cwd(), path);
}
Expand All @@ -23,66 +26,58 @@ function loadPackageJson(path: string): PackageJson {

export async function loadPlugin(path: string): Promise<Plugin> {
const pluginRoot = getPluginRoot(path);
const pluginPackageJson = loadPackageJson(path);

// Check for the entry point on the `exports` or `main` field in package.json.
let pluginEntryPoint;
const exports = pluginPackageJson.exports;
if (typeof exports === 'string') {
pluginEntryPoint = exports;
} else if (
typeof exports === 'object' &&
exports !== null &&
!Array.isArray(exports)
) {
// Check various properties on the `exports` object.
// https://nodejs.org/api/packages.html#conditional-exports
const propertiesToCheck: (keyof PackageJson.ExportConditions)[] = [
'.',
'node',
'import',
'require',
'default',
];
for (const prop of propertiesToCheck) {
// @ts-expect-error -- The union type for the object is causing trouble.
const value = exports[prop];
if (typeof value === 'string') {
pluginEntryPoint = value;
break;
try {
// Try require first which should work for CJS plugins.
if (existsSync(pluginRoot)) {
// TODO remove
console.log(readFileSync(pluginRoot, 'utf8'));
}
return require(pluginRoot); // eslint-disable-line import/no-dynamic-require
} catch {
// Otherwise, for ESM plugins, we'll have to try to resolve the exact plugin entry point and import it.
const pluginPackageJson = loadPackageJson(path);
let pluginEntryPoint;
const exports = pluginPackageJson.exports;
if (typeof exports === 'string') {
pluginEntryPoint = exports;
} else if (
typeof exports === 'object' &&
exports !== null &&
!Array.isArray(exports)
) {
// Check various properties on the `exports` object.
// https://nodejs.org/api/packages.html#conditional-exports
const propertiesToCheck: (keyof PackageJson.ExportConditions)[] = [
'.',
'node',
'import',
'require',
'default',
];
for (const prop of propertiesToCheck) {
// @ts-expect-error -- The union type for the object is causing trouble.
const value = exports[prop];
if (typeof value === 'string') {
pluginEntryPoint = value;
break;
}
}
}
} else if (typeof pluginPackageJson.main === 'string') {
pluginEntryPoint = pluginPackageJson.main;
}

if (!pluginEntryPoint) {
pluginEntryPoint = 'index.js'; // This is the default value for the `main` field: https://docs.npmjs.com/cli/v8/configuring-npm/package-json#main
}

if (pluginEntryPoint.endsWith('/')) {
// The `main` field is allowed to specify a directory.
pluginEntryPoint = `${pluginEntryPoint}/index.js`;
}
if (!pluginEntryPoint) {
throw new Error('Unable to determine plugin entry point.');
}

const SUPPORTED_FILE_TYPES = ['.js', '.cjs', '.mjs'];
if (!SUPPORTED_FILE_TYPES.includes(extname(pluginEntryPoint))) {
throw new Error(
`Unsupported file type for plugin entry point. Current types supported: ${SUPPORTED_FILE_TYPES.join(
', '
)}. Entry point detected: ${pluginEntryPoint}`
);
}
const pluginEntryPointAbs = join(pluginRoot, pluginEntryPoint);
if (!existsSync(pluginEntryPointAbs)) {
throw new Error(
`ESLint plugin entry point does not exist. Tried: ${pluginEntryPoint}`
);
}

const pluginEntryPointAbs = join(pluginRoot, pluginEntryPoint);
if (!existsSync(pluginEntryPointAbs)) {
throw new Error(
`Could not find entry point for ESLint plugin. Tried: ${pluginEntryPoint}`
);
const { default: plugin } = await importAbs(pluginEntryPointAbs);
return plugin;
}

const { default: plugin } = await importAbs(pluginEntryPointAbs);
return plugin;
}

export function getPluginPrefix(path: string): string {
Expand Down
72 changes: 72 additions & 0 deletions test/lib/__snapshots__/generate-cjs-test.ts.snap
@@ -0,0 +1,72 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`generate (cjs) CJS (non-ESM) generates the documentation 1`] = `
"<!-- begin auto-generated rules list -->
| Name | Description |
| :----------------------------- | :------------ |
| [no-foo](docs/rules/no-foo.md) | disallow foo. |
<!-- end auto-generated rules list -->"
`;

exports[`generate (cjs) CJS (non-ESM) generates the documentation 2`] = `
"# Disallow foo (\`test/no-foo\`)
<!-- end auto-generated rule header -->
"
`;

exports[`generate (cjs) config that extends another config generates the documentation 1`] = `
"## Rules
<!-- begin auto-generated rules list -->
💼 Configurations enabled in.\\
✅ Set in the \`recommended\` configuration.
| Name | Description | 💼 |
| :----------------------------- | :--------------------- | :- |
| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | ✅ |
| [no-baz](docs/rules/no-baz.md) | Description of no-baz. | ✅ |
| [no-biz](docs/rules/no-biz.md) | Description of no-biz. | ✅ |
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | ✅ |
<!-- end auto-generated rules list -->
"
`;

exports[`generate (cjs) config that extends another config generates the documentation 2`] = `
"# Description of no-foo (\`test/no-foo\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;

exports[`generate (cjs) config that extends another config generates the documentation 3`] = `
"# Description of no-bar (\`test/no-bar\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;

exports[`generate (cjs) config that extends another config generates the documentation 4`] = `
"# Description of no-baz (\`test/no-baz\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;

exports[`generate (cjs) config that extends another config generates the documentation 5`] = `
"# Description of no-biz (\`test/no-biz\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;
93 changes: 22 additions & 71 deletions test/lib/__snapshots__/generator-test.ts.snap
@@ -1,22 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`generator #generate CJS (non-ESM) generates the documentation 1`] = `
"<!-- begin auto-generated rules list -->
| Name | Description |
| :----------------------------- | :------------ |
| [no-foo](docs/rules/no-foo.md) | disallow foo. |
<!-- end auto-generated rules list -->"
`;

exports[`generator #generate CJS (non-ESM) generates the documentation 2`] = `
"# Disallow foo (\`test/no-foo\`)
<!-- end auto-generated rule header -->
"
`;

exports[`generator #generate Missing plugin package.json \`name\` field throws an error 1`] = `"Could not find \`name\` field in ESLint plugin's package.json."`;

exports[`generator #generate Missing plugin package.json throws an error 1`] = `"Could not find package.json of ESLint plugin."`;
Expand Down Expand Up @@ -79,60 +62,6 @@ exports[`generator #generate Scoped plugin name determines the correct plugin pr
"
`;

exports[`generator #generate config that extends another config generates the documentation 1`] = `
"## Rules
<!-- begin auto-generated rules list -->
💼 Configurations enabled in.\\
✅ Set in the \`recommended\` configuration.
| Name | Description | 💼 |
| :----------------------------- | :--------------------- | :- |
| [no-bar](docs/rules/no-bar.md) | Description of no-bar. | ✅ |
| [no-baz](docs/rules/no-baz.md) | Description of no-baz. | ✅ |
| [no-biz](docs/rules/no-biz.md) | Description of no-biz. | ✅ |
| [no-foo](docs/rules/no-foo.md) | Description of no-foo. | ✅ |
<!-- end auto-generated rules list -->
"
`;

exports[`generator #generate config that extends another config generates the documentation 2`] = `
"# Description of no-foo (\`test/no-foo\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;

exports[`generator #generate config that extends another config generates the documentation 3`] = `
"# Description of no-bar (\`test/no-bar\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;

exports[`generator #generate config that extends another config generates the documentation 4`] = `
"# Description of no-baz (\`test/no-baz\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;

exports[`generator #generate config that extends another config generates the documentation 5`] = `
"# Description of no-biz (\`test/no-biz\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;

exports[`generator #generate config with overrides generates the documentation 1`] = `
"## Rules
<!-- begin auto-generated rules list -->
Expand Down Expand Up @@ -463,6 +392,28 @@ exports[`generator #generate only a \`recommended\` config updates the documenta
"
`;

exports[`generator #generate plugin entry point in JSON format generates the documentation 1`] = `
"<!-- begin auto-generated rules list -->
💼 Configurations enabled in.\\
✅ Set in the \`recommended\` configuration.
| Name | Description | 💼 |
| :----------------------------- | :--------------------- | :- |
| [no-foo](docs/rules/no-foo.md) | Description for no-foo | ✅ |
<!-- end auto-generated rules list -->"
`;

exports[`generator #generate plugin entry point in JSON format generates the documentation 2`] = `
"# Description for no-foo (\`test/no-foo\`)
💼 This rule is enabled in the ✅ \`recommended\` config.
<!-- end auto-generated rule header -->
"
`;

exports[`generator #generate rule config with options generates the documentation 1`] = `
"## Rules
<!-- begin auto-generated rules list -->
Expand Down

0 comments on commit 98f844a

Please sign in to comment.