diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/base.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/base.ts new file mode 100644 index 0000000000000..710be8bf75f73 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/base.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "#oxlint"; + +export default defineConfig({ + jsPlugins: [{ name: "basic-custom-plugin-js", specifier: "./plugin.ts" }], +}); diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/files/test.js b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/files/test.js new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/files/test.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/output.snap.md b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/output.snap.md new file mode 100644 index 0000000000000..888a733571800 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/output.snap.md @@ -0,0 +1,17 @@ +# Exit code +1 + +# stdout +``` +Failed to build configuration from /oxlint.config.ts. + + x Relative JS plugin specifiers are not supported in configs provided via `extends` in `oxlint.config.ts`. + | + | Found: "./plugin.ts" + | + | Use a package name (e.g. "eslint-plugin-foo") or an absolute path instead. +``` + +# stderr +``` +``` diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/oxlint.config.ts new file mode 100644 index 0000000000000..424733f86ae68 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/oxlint.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "#oxlint"; + +import base from "./base.ts"; + +export default defineConfig({ + extends: [base], +}); diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/plugin.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/plugin.ts new file mode 100644 index 0000000000000..3affb226691cc --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_object/plugin.ts @@ -0,0 +1,10 @@ +import type { Plugin } from "#oxlint/plugins"; + +const plugin: Plugin = { + meta: { + name: "basic-custom-plugin", + }, + rules: {}, +}; + +export default plugin; diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/base.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/base.ts new file mode 100644 index 0000000000000..0bb783f0723c3 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/base.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "#oxlint"; + +export default defineConfig({ + overrides: [ + { + files: ["files/**/*.js"], + jsPlugins: ["./plugin.ts"], + }, + ], +}); diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/files/test.js b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/files/test.js new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/files/test.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/output.snap.md b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/output.snap.md new file mode 100644 index 0000000000000..888a733571800 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/output.snap.md @@ -0,0 +1,17 @@ +# Exit code +1 + +# stdout +``` +Failed to build configuration from /oxlint.config.ts. + + x Relative JS plugin specifiers are not supported in configs provided via `extends` in `oxlint.config.ts`. + | + | Found: "./plugin.ts" + | + | Use a package name (e.g. "eslint-plugin-foo") or an absolute path instead. +``` + +# stderr +``` +``` diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/oxlint.config.ts new file mode 100644 index 0000000000000..424733f86ae68 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/oxlint.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "#oxlint"; + +import base from "./base.ts"; + +export default defineConfig({ + extends: [base], +}); diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/plugin.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/plugin.ts new file mode 100644 index 0000000000000..3affb226691cc --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_override/plugin.ts @@ -0,0 +1,10 @@ +import type { Plugin } from "#oxlint/plugins"; + +const plugin: Plugin = { + meta: { + name: "basic-custom-plugin", + }, + rules: {}, +}; + +export default plugin; diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/base.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/base.ts new file mode 100644 index 0000000000000..fb2ed1050e1b6 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/base.ts @@ -0,0 +1,5 @@ +import { defineConfig } from "#oxlint"; + +export default defineConfig({ + jsPlugins: ["./plugin.ts"], +}); diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/files/test.js b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/files/test.js new file mode 100644 index 0000000000000..eab74692130a6 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/files/test.js @@ -0,0 +1 @@ +debugger; diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/output.snap.md b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/output.snap.md new file mode 100644 index 0000000000000..888a733571800 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/output.snap.md @@ -0,0 +1,17 @@ +# Exit code +1 + +# stdout +``` +Failed to build configuration from /oxlint.config.ts. + + x Relative JS plugin specifiers are not supported in configs provided via `extends` in `oxlint.config.ts`. + | + | Found: "./plugin.ts" + | + | Use a package name (e.g. "eslint-plugin-foo") or an absolute path instead. +``` + +# stderr +``` +``` diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/oxlint.config.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/oxlint.config.ts new file mode 100644 index 0000000000000..424733f86ae68 --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/oxlint.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "#oxlint"; + +import base from "./base.ts"; + +export default defineConfig({ + extends: [base], +}); diff --git a/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/plugin.ts b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/plugin.ts new file mode 100644 index 0000000000000..3affb226691cc --- /dev/null +++ b/apps/oxlint/test/fixtures/js_config_extends_js_plugins_relative_banned_string/plugin.ts @@ -0,0 +1,10 @@ +import type { Plugin } from "#oxlint/plugins"; + +const plugin: Plugin = { + meta: { + name: "basic-custom-plugin", + }, + rules: {}, +}; + +export default plugin; diff --git a/crates/oxc_linter/src/config/config_builder.rs b/crates/oxc_linter/src/config/config_builder.rs index 5118bfdb1195b..01259f5c11ec0 100644 --- a/crates/oxc_linter/src/config/config_builder.rs +++ b/crates/oxc_linter/src/config/config_builder.rs @@ -106,9 +106,53 @@ impl ConfigStoreBuilder { workspace_uri: Option<&str>, ) -> Result { // TODO: this can be cached to avoid re-computing the same oxlintrc + fn is_relative_plugin_specifier(specifier: &str) -> bool { + specifier == "." + || specifier == ".." + || specifier.starts_with("./") + || specifier.starts_with("../") + || specifier.starts_with(".\\") + || specifier.starts_with("..\\") + } + + fn check_no_relative_js_plugins_in_extends( + config: &Oxlintrc, + ) -> Result<(), ConfigBuilderError> { + if let Some(external_plugins) = &config.external_plugins { + for entry in external_plugins { + if is_relative_plugin_specifier(&entry.specifier) { + return Err(ConfigBuilderError::RelativeExternalPluginSpecifierInExtends { + plugin_specifier: entry.specifier.clone(), + }); + } + } + } + + for r#override in &config.overrides { + if let Some(external_plugins) = &r#override.external_plugins { + for entry in external_plugins { + if is_relative_plugin_specifier(&entry.specifier) { + return Err( + ConfigBuilderError::RelativeExternalPluginSpecifierInExtends { + plugin_specifier: entry.specifier.clone(), + }, + ); + } + } + } + } + + Ok(()) + } + fn resolve_oxlintrc_config( config: Oxlintrc, + in_object_extends: bool, ) -> Result<(Oxlintrc, Vec), ConfigBuilderError> { + if in_object_extends { + check_no_relative_js_plugins_in_extends(&config)?; + } + let path = config.path.clone(); let root_path = path.parent(); let extends = config.extends.clone(); @@ -118,7 +162,7 @@ impl ConfigStoreBuilder { let mut oxlintrc = config; for config in extends_configs.into_iter().rev() { - let (extends, extends_paths) = resolve_oxlintrc_config(config)?; + let (extends, extends_paths) = resolve_oxlintrc_config(config, true)?; oxlintrc = oxlintrc.merge(extends); extended_paths.extend(extends_paths); } @@ -148,7 +192,7 @@ impl ConfigStoreBuilder { extended_paths.push(path.clone()); - let (extends, extends_paths) = resolve_oxlintrc_config(extends_oxlintrc)?; + let (extends, extends_paths) = resolve_oxlintrc_config(extends_oxlintrc, false)?; oxlintrc = oxlintrc.merge(extends); extended_paths.extend(extends_paths); @@ -157,7 +201,7 @@ impl ConfigStoreBuilder { Ok((oxlintrc, extended_paths)) } - let (oxlintrc, extended_paths) = resolve_oxlintrc_config(oxlintrc)?; + let (oxlintrc, extended_paths) = resolve_oxlintrc_config(oxlintrc, false)?; // Collect external plugins from both base config and overrides let mut external_plugins: FxHashSet<&ExternalPluginEntry> = FxHashSet::default(); @@ -661,6 +705,12 @@ pub enum ConfigBuilderError { ReservedExternalPluginName { plugin_name: String, }, + /// A JS config extended via `extends` contained a relative JS plugin specifier. + /// + /// Without origin metadata, relative specifiers are ambiguous and therefore disallowed. + RelativeExternalPluginSpecifierInExtends { + plugin_specifier: String, + }, /// Multiple errors parsing rule configuration options RuleConfigurationErrors { /// The errors that occurred @@ -716,6 +766,16 @@ impl Display for ConfigBuilderError { )?; Ok(()) } + ConfigBuilderError::RelativeExternalPluginSpecifierInExtends { plugin_specifier } => { + write!( + f, + "Relative JS plugin specifiers are not supported in configs provided via `extends` in `oxlint.config.ts`.\n\ + \n\ + Found: {plugin_specifier:?}\n\ + \n\ + Use a package name (e.g. \"eslint-plugin-foo\") or an absolute path instead." + ) + } ConfigBuilderError::RuleConfigurationErrors { errors } => { for (i, error) in errors.iter().enumerate() { if i > 0 {