diff --git a/apps/oxlint/test/e2e.test.ts b/apps/oxlint/test/e2e.test.ts index 943cdc2158a44..296bd41d27a2d 100644 --- a/apps/oxlint/test/e2e.test.ts +++ b/apps/oxlint/test/e2e.test.ts @@ -15,6 +15,8 @@ interface TestOptions { snapshotName?: string; // Function to get extra data to include in the snapshot getExtraSnapshotData?: (dirPath: string) => Promise<{ [key: string]: string }>; + + overrideFiles?: string; } /** @@ -29,7 +31,7 @@ async function testFixture(fixtureName: string, options?: TestOptions): Promise< // Use current NodeJS executable, rather than `node`, to avoid problems with a Node version manager // installed on system resulting in using wrong NodeJS version command: process.execPath, - args: [CLI_PATH, ...args, 'files'], + args: [CLI_PATH, ...args, options?.overrideFiles ?? 'files'], fixtureName, snapshotName: options?.snapshotName ?? 'output', getExtraSnapshotData: options?.getExtraSnapshotData, @@ -73,6 +75,12 @@ describe('oxlint CLI', () => { await testFixture('basic_custom_plugin_many_files'); }); + it('should load a custom plugin correctly when extending in a nested config', async () => { + await testFixture('custom_plugin_nested_config', { + overrideFiles: '.', + }); + }); + it('should load a custom plugin when configured in overrides', async () => { await testFixture('custom_plugin_via_overrides'); }); diff --git a/apps/oxlint/test/fixtures/custom_plugin_nested_config/.oxlintrc.json b/apps/oxlint/test/fixtures/custom_plugin_nested_config/.oxlintrc.json new file mode 100644 index 0000000000000..cc94cf0993ae5 --- /dev/null +++ b/apps/oxlint/test/fixtures/custom_plugin_nested_config/.oxlintrc.json @@ -0,0 +1,7 @@ +{ + "jsPlugins": ["./plugin.ts"], + "rules": { + "basic-custom-plugin/no-debugger": "error" + }, + "categories": { "correctness": "off" } +} diff --git a/apps/oxlint/test/fixtures/custom_plugin_nested_config/nested/.oxlintrc.json b/apps/oxlint/test/fixtures/custom_plugin_nested_config/nested/.oxlintrc.json new file mode 100644 index 0000000000000..d4ac61eb9366e --- /dev/null +++ b/apps/oxlint/test/fixtures/custom_plugin_nested_config/nested/.oxlintrc.json @@ -0,0 +1,3 @@ +{ + "extends": ["../.oxlintrc.json"] +} diff --git a/apps/oxlint/test/fixtures/custom_plugin_nested_config/nested/index.ts b/apps/oxlint/test/fixtures/custom_plugin_nested_config/nested/index.ts new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/fixtures/custom_plugin_nested_config/nested/index.ts @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/fixtures/custom_plugin_nested_config/output.snap.md b/apps/oxlint/test/fixtures/custom_plugin_nested_config/output.snap.md new file mode 100644 index 0000000000000..bd0cca621117f --- /dev/null +++ b/apps/oxlint/test/fixtures/custom_plugin_nested_config/output.snap.md @@ -0,0 +1,20 @@ +# Exit code +1 + +# stdout +``` + x basic-custom-plugin(no-debugger): Unexpected Debugger Statement + ,-[nested/index.ts:1:1] + 1 | debugger; + : ^^^^^^^^^ + `---- + +Found 0 warnings and 1 error. +Finished in Xms on 2 files using X threads. +``` + +# stderr +``` +WARNING: JS plugins are experimental and not subject to semver. +Breaking changes are possible while JS plugins support is under development. +``` diff --git a/apps/oxlint/test/fixtures/custom_plugin_nested_config/plugin.ts b/apps/oxlint/test/fixtures/custom_plugin_nested_config/plugin.ts new file mode 100644 index 0000000000000..d8f0b772b32f1 --- /dev/null +++ b/apps/oxlint/test/fixtures/custom_plugin_nested_config/plugin.ts @@ -0,0 +1,23 @@ +import type { Plugin } from '../../../dist/index.js'; + +const plugin: Plugin = { + meta: { + name: 'basic-custom-plugin', + }, + rules: { + 'no-debugger': { + create(context) { + return { + DebuggerStatement(debuggerStatement) { + context.report({ + message: 'Unexpected Debugger Statement', + node: debuggerStatement, + }); + }, + }; + }, + }, + }, +}; + +export default plugin; diff --git a/crates/oxc_linter/src/config/oxlintrc.rs b/crates/oxc_linter/src/config/oxlintrc.rs index d5e3bd3170d85..0fcfe9273ec00 100644 --- a/crates/oxc_linter/src/config/oxlintrc.rs +++ b/crates/oxc_linter/src/config/oxlintrc.rs @@ -152,6 +152,30 @@ impl Oxlintrc { config.path = path.to_path_buf(); + // resolve relative paths + if let Some(external_plugins) = &mut config.external_plugins + && let Some(config_dir) = config.path.parent() + { + *external_plugins = std::mem::take(external_plugins) + .into_iter() + .map(|specifier| { + let is_relative = specifier.starts_with("./") || specifier.starts_with("../"); + if is_relative { + config_dir + .join(&specifier) + .into_os_string() + .into_string() + // there is a corner case where this can fail + // for non-utf8 paths, this relative plugin specifier + // may be resolved relative to the wrong config file + // when two are merged. + .unwrap_or(specifier) + } else { + specifier + } + }) + .collect(); + } Ok(config) }