diff --git a/apps/oxlint/src-js/plugins/utils.ts b/apps/oxlint/src-js/plugins/utils.ts index 96d8c48267f68..f19469e4f90df 100644 --- a/apps/oxlint/src-js/plugins/utils.ts +++ b/apps/oxlint/src-js/plugins/utils.ts @@ -3,18 +3,28 @@ * * `err` is expected to be an `Error` object, but can be anything. * - * This function will never throw, and always returns a string, even if: + * * If it's an `Error`, the error message and stack trace is returned. + * * If it's another object with a string `message` property, `message` is returned. + * * Otherwise, a generic "Unknown error" message is returned. + * + * This function will never throw, and always returns a non-empty string, even if: * * * `err` is `null` or `undefined`. * * `err` is an object with a getter for `message` property which throws. - * * `err` has a getter for `message` property which returns a different value each time it's accessed. + * * `err` has a getter for `stack` or `message` property which returns a different value each time it's accessed. * * @param err - Error * @returns Error message */ export function getErrorMessage(err: unknown): string { try { - const { message } = err as undefined | { message: string }; + if (err instanceof Error) { + // Note: `stack` includes the error message + const { stack } = err; + if (typeof stack === 'string' && stack !== '') return stack; + } + + const { message } = err as { message?: unknown }; if (typeof message === 'string' && message !== '') return message; } catch {} diff --git a/apps/oxlint/test/__snapshots__/e2e.test.ts.snap b/apps/oxlint/test/__snapshots__/e2e.test.ts.snap index 03c8b1fa6fdf8..00e0ac50c075c 100644 --- a/apps/oxlint/test/__snapshots__/e2e.test.ts.snap +++ b/apps/oxlint/test/__snapshots__/e2e.test.ts.snap @@ -444,7 +444,14 @@ exports[`oxlint CLI > should report an error if a a rule is not found within a c " `; -exports[`oxlint CLI > should report an error if a custom plugin cannot be loaded 1`] = ` +exports[`oxlint CLI > should report an error if a custom plugin in config but JS plugins are not enabled 1`] = ` +"Failed to parse configuration file. + + x JS plugins are not supported without \`--experimental-js-plugins\` CLI option +" +`; + +exports[`oxlint CLI > should report an error if a custom plugin is missing 1`] = ` "Failed to parse configuration file. x Failed to load JS plugin: ./test_plugin @@ -452,10 +459,12 @@ exports[`oxlint CLI > should report an error if a custom plugin cannot be loaded " `; -exports[`oxlint CLI > should report an error if a custom plugin in config but JS plugins are not enabled 1`] = ` +exports[`oxlint CLI > should report an error if a custom plugin throws an error during import 1`] = ` "Failed to parse configuration file. - x JS plugins are not supported without \`--experimental-js-plugins\` CLI option + x Failed to load JS plugin: ./test_plugin + | Error: whoops! + | at /apps/oxlint/test/fixtures/custom_plugin_import_error/test_plugin/index.js:1:7 " `; diff --git a/apps/oxlint/test/e2e.test.ts b/apps/oxlint/test/e2e.test.ts index ee10da7965839..cf0e1ede1d344 100644 --- a/apps/oxlint/test/e2e.test.ts +++ b/apps/oxlint/test/e2e.test.ts @@ -6,6 +6,8 @@ import { execa } from 'execa'; const PACKAGE_ROOT_PATH = dirname(import.meta.dirname); const CLI_PATH = pathJoin(PACKAGE_ROOT_PATH, 'dist/cli.js'); +const ROOT_URL = new URL('../../../', import.meta.url).href; +const FIXTURES_URL = new URL('./fixtures/', import.meta.url).href; async function runOxlintWithoutPlugins(cwd: string, args: string[] = []) { return await execa('node', [CLI_PATH, ...args], { @@ -19,9 +21,28 @@ async function runOxlint(cwd: string, args: string[] = []) { } function normalizeOutput(output: string): string { - return output - .replace(/Finished in \d+(\.\d+)?(s|ms|us|ns)/, 'Finished in Xms') - .replace(/using \d+ threads./, 'using X threads.'); + let lines = output.split('\n'); + + // Remove timing and thread count info which can vary between runs + lines[lines.length - 1] = lines[lines.length - 1].replace( + /^Finished in \d+(?:\.\d+)?(?:s|ms|us|ns) on (\d+) file(s?) using \d+ threads.$/, + 'Finished in Xms on $1 file$2 using X threads.', + ); + + // Remove lines from stack traces which are outside `fixtures` directory. + // Replace path to repo root in stack traces with ``. + lines = lines.flatMap((line) => { + // e.g. ` | at file:///path/to/oxc/apps/oxlint/test/fixtures/foor/bar.js:1:1` + // e.g. ` | at whatever (file:///path/to/oxc/apps/oxlint/test/fixtures/foor/bar.js:1:1)` + const match = line.match(/^(\s*\|\s+at (?:.+?\()?)(.+)$/); + if (match) { + const [, premable, at] = match; + return at.startsWith(FIXTURES_URL) ? [`${premable}/${at.slice(ROOT_URL.length)}`] : []; + } + return [line]; + }); + + return lines.join('\n'); } describe('oxlint CLI', () => { @@ -73,12 +94,18 @@ describe('oxlint CLI', () => { expect(normalizeOutput(stdout)).toMatchSnapshot(); }); - it('should report an error if a custom plugin cannot be loaded', async () => { + it('should report an error if a custom plugin is missing', async () => { const { stdout, exitCode } = await runOxlint('test/fixtures/missing_custom_plugin'); expect(exitCode).toBe(1); expect(normalizeOutput(stdout)).toMatchSnapshot(); }); + it('should report an error if a custom plugin throws an error during import', async () => { + const { stdout, exitCode } = await runOxlint('test/fixtures/custom_plugin_import_error'); + expect(exitCode).toBe(1); + expect(normalizeOutput(stdout)).toMatchSnapshot(); + }); + it('should report an error if a rule is not found within a custom plugin', async () => { const { stdout, exitCode } = await runOxlint('test/fixtures/custom_plugin_missing_rule'); expect(exitCode).toBe(1); diff --git a/apps/oxlint/test/fixtures/custom_plugin_import_error/.oxlintrc.json b/apps/oxlint/test/fixtures/custom_plugin_import_error/.oxlintrc.json new file mode 100644 index 0000000000000..21032f3ec8189 --- /dev/null +++ b/apps/oxlint/test/fixtures/custom_plugin_import_error/.oxlintrc.json @@ -0,0 +1,7 @@ +{ + "plugins": ["./test_plugin"], + "categories": { "correctness": "off" }, + "rules": { + "basic-custom-plugin/unknown-rule": "error" + } +} diff --git a/apps/oxlint/test/fixtures/custom_plugin_import_error/test_plugin/index.js b/apps/oxlint/test/fixtures/custom_plugin_import_error/test_plugin/index.js new file mode 100644 index 0000000000000..a996b9d495b2c --- /dev/null +++ b/apps/oxlint/test/fixtures/custom_plugin_import_error/test_plugin/index.js @@ -0,0 +1 @@ +throw new Error("whoops!");