diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/.oxlintrc.json b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/.oxlintrc.json new file mode 100644 index 0000000000000..88213f228eec2 --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/.oxlintrc.json @@ -0,0 +1,7 @@ +{ + "jsPlugins": [ + { "name": "custom", "specifier": "./plugin1.ts" }, + { "name": "custom", "specifier": "./plugin2.ts" } + ], + "rules": {} +} diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/files/test.js b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/files/test.js new file mode 100644 index 0000000000000..943c458c79e20 --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/files/test.js @@ -0,0 +1 @@ +const x = 1; diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/output.snap.md b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/output.snap.md new file mode 100644 index 0000000000000..7daf8817f944e --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/output.snap.md @@ -0,0 +1,25 @@ +# Exit code +1 + +# stdout +``` +Failed to parse configuration file. + + x Plugin name 'custom' is already in use. + | + | Multiple plugins cannot share the same name or alias. + | Each plugin must have a unique identifier to avoid conflicts. + | + | Please provide a different alias for one of the plugins: + | + | "jsPlugins": [ + | { "name": "custom", "specifier": "plugin-one" }, + | { "name": "custom-alt", "specifier": "plugin-two" } + | ] +``` + +# 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/plugin_name_duplicate_alias/plugin1.ts b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/plugin1.ts new file mode 100644 index 0000000000000..a663b3c1d3a0b --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/plugin1.ts @@ -0,0 +1,19 @@ +import type { Plugin } from "#oxlint"; + +const plugin: Plugin = { + meta: { + name: "first-plugin", + }, + rules: { + "rule-one": { + meta: { + type: "problem", + }, + create(context) { + return {}; + }, + }, + }, +}; + +export default plugin; diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/plugin2.ts b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/plugin2.ts new file mode 100644 index 0000000000000..360b507011ff5 --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_alias/plugin2.ts @@ -0,0 +1,19 @@ +import type { Plugin } from "#oxlint"; + +const plugin: Plugin = { + meta: { + name: "second-plugin", + }, + rules: { + "rule-two": { + meta: { + type: "problem", + }, + create(context) { + return {}; + }, + }, + }, +}; + +export default plugin; diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/.oxlintrc.json b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/.oxlintrc.json new file mode 100644 index 0000000000000..d7252d2c57b6e --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/.oxlintrc.json @@ -0,0 +1,7 @@ +{ + "jsPlugins": [ + "./plugin.ts", + { "name": "foo", "specifier": "./other.ts" } + ], + "rules": {} +} diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/files/test.js b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/files/test.js new file mode 100644 index 0000000000000..943c458c79e20 --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/files/test.js @@ -0,0 +1 @@ +const x = 1; diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/other.ts b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/other.ts new file mode 100644 index 0000000000000..f8b0a5fa472fa --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/other.ts @@ -0,0 +1,10 @@ +import type { Plugin } from "#oxlint"; + +const plugin: Plugin = { + meta: { + name: "other-plugin", + }, + rules: {}, +}; + +export default plugin; diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/output.snap.md b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/output.snap.md new file mode 100644 index 0000000000000..10cb08b0d316a --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/output.snap.md @@ -0,0 +1,25 @@ +# Exit code +1 + +# stdout +``` +Failed to parse configuration file. + + x Plugin name 'foo' is already in use. + | + | Multiple plugins cannot share the same name or alias. + | Each plugin must have a unique identifier to avoid conflicts. + | + | Please provide a different alias for one of the plugins: + | + | "jsPlugins": [ + | { "name": "foo", "specifier": "plugin-one" }, + | { "name": "foo-alt", "specifier": "plugin-two" } + | ] +``` + +# 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/plugin_name_duplicate_no_alias_then_alias/plugin.ts b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/plugin.ts new file mode 100644 index 0000000000000..5b4fd04a49099 --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_no_alias_then_alias/plugin.ts @@ -0,0 +1,19 @@ +import type { Plugin } from "#oxlint"; + +const plugin: Plugin = { + meta: { + name: "foo", + }, + rules: { + "test-rule": { + meta: { + type: "problem", + }, + create(context) { + return {}; + }, + }, + }, +}; + +export default plugin; diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/.oxlintrc.json b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/.oxlintrc.json new file mode 100644 index 0000000000000..cb4fa028adb03 --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/.oxlintrc.json @@ -0,0 +1,7 @@ +{ + "jsPlugins": [ + "./plugin1.ts", + "./plugin2.ts" + ], + "rules": {} +} diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/files/test.js b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/files/test.js new file mode 100644 index 0000000000000..943c458c79e20 --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/files/test.js @@ -0,0 +1 @@ +const x = 1; diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/output.snap.md b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/output.snap.md new file mode 100644 index 0000000000000..6f90ebb927e3c --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/output.snap.md @@ -0,0 +1,25 @@ +# Exit code +1 + +# stdout +``` +Failed to parse configuration file. + + x Plugin name 'shared-name' is already in use. + | + | Multiple plugins cannot share the same name or alias. + | Each plugin must have a unique identifier to avoid conflicts. + | + | Please provide a different alias for one of the plugins: + | + | "jsPlugins": [ + | { "name": "shared-name", "specifier": "plugin-one" }, + | { "name": "shared-name-alt", "specifier": "plugin-two" } + | ] +``` + +# 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/plugin_name_duplicate_same_meta_name/plugin1.ts b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/plugin1.ts new file mode 100644 index 0000000000000..a84502677077d --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/plugin1.ts @@ -0,0 +1,19 @@ +import type { Plugin } from "#oxlint"; + +const plugin: Plugin = { + meta: { + name: "shared-name", + }, + rules: { + "rule-one": { + meta: { + type: "problem", + }, + create(context) { + return {}; + }, + }, + }, +}; + +export default plugin; diff --git a/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/plugin2.ts b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/plugin2.ts new file mode 100644 index 0000000000000..0e7c7d0200899 --- /dev/null +++ b/apps/oxlint/test/fixtures/plugin_name_duplicate_same_meta_name/plugin2.ts @@ -0,0 +1,19 @@ +import type { Plugin } from "#oxlint"; + +const plugin: Plugin = { + meta: { + name: "shared-name", + }, + rules: { + "rule-two": { + meta: { + type: "problem", + }, + create(context) { + return {}; + }, + }, + }, +}; + +export default plugin; diff --git a/crates/oxc_linter/src/config/config_builder.rs b/crates/oxc_linter/src/config/config_builder.rs index 172955f8e89bf..50b7a894a8314 100644 --- a/crates/oxc_linter/src/config/config_builder.rs +++ b/crates/oxc_linter/src/config/config_builder.rs @@ -578,17 +578,21 @@ impl ConfigStoreBuilder { })?; let plugin_name = result.name; - if LintPlugins::try_from(plugin_name.as_str()).is_err() { - external_plugin_store.register_plugin( - plugin_path, - plugin_name, - result.offset, - result.rule_names, - ); - Ok(()) - } else { - Err(ConfigBuilderError::ReservedExternalPluginName { plugin_name }) + if LintPlugins::try_from(plugin_name.as_str()).is_ok() { + return Err(ConfigBuilderError::ReservedExternalPluginName { plugin_name }); + } + + if external_plugin_store.is_plugin_name_registered(&plugin_name) { + return Err(ConfigBuilderError::DuplicatePluginAlias { plugin_name }); } + + external_plugin_store.register_plugin( + plugin_path, + plugin_name, + result.offset, + result.rule_names, + ); + Ok(()) } } @@ -632,6 +636,9 @@ pub enum ConfigBuilderError { ReservedExternalPluginName { plugin_name: String, }, + DuplicatePluginAlias { + plugin_name: String, + }, } impl Display for ConfigBuilderError { @@ -682,6 +689,23 @@ impl Display for ConfigBuilderError { )?; Ok(()) } + ConfigBuilderError::DuplicatePluginAlias { plugin_name } => { + write!( + f, + "Plugin name '{plugin_name}' is already in use.\n\ + \n\ + Multiple plugins cannot share the same name or alias.\n\ + Each plugin must have a unique identifier to avoid conflicts.\n\ + \n\ + Please provide a different alias for one of the plugins:\n\ + \n\ + \"jsPlugins\": [\n \ + {{ \"name\": \"{plugin_name}\", \"specifier\": \"plugin-one\" }},\n \ + {{ \"name\": \"{plugin_name}-alt\", \"specifier\": \"plugin-two\" }}\n\ + ]", + )?; + Ok(()) + } ConfigBuilderError::ExternalRuleLookupError(e) => std::fmt::Display::fmt(&e, f), } } diff --git a/crates/oxc_linter/src/external_plugin_store.rs b/crates/oxc_linter/src/external_plugin_store.rs index 0debcd340028a..fbe22c8257bfd 100644 --- a/crates/oxc_linter/src/external_plugin_store.rs +++ b/crates/oxc_linter/src/external_plugin_store.rs @@ -83,6 +83,10 @@ impl ExternalPluginStore { self.registered_plugin_paths.contains(plugin_path) } + pub fn is_plugin_name_registered(&self, plugin_name: &str) -> bool { + self.plugin_names.contains_key(plugin_name) + } + /// Register plugin. /// /// # Panics