Linter plugins: Support plugins with `/` in name
See #14503 for original bug report.
.oxlintrc.json:
{
"jsPlugins": ["@eslint-react/eslint-plugin"],
"rules": {
"@eslint-react/eslint-plugin/jsx-no-comment-textnodes": ["error"],
"@eslint-react/eslint-plugin/dom/no-dangerously-set-innerhtml": ["error"],
"@eslint-react/eslint-plugin/naming-convention/context-name": ["error"]
}
}
Result:
Failed to parse configuration file.
x Plugin '@eslint-react' not found
The problem
I assume the problem is that we split complete rule name into plugin name and rule name on the first /. But in this case, the plugin name contains a slash.
Splitting on the last slash wouldn't work either, as rule names also contain a /.
Possible solutions
- For each key in
rules, try all possible combinations - i.e.
- Plugin
@eslint-react, ruleeslint-plugin/naming-convention/context-name - Plugin
@eslint-react/eslint-plugin, rulenaming-convention/context-name - Plugin
@eslint-react/eslint-plugin/naming-convention, rulecontext-name
- Automatically remove
eslint-plugin-prefix or/eslint-pluginpostfix from plugin names.
Config would then look like:
{
"jsPlugins": ["@eslint-react/eslint-plugin"],
"rules": {
"@eslint-react/jsx-no-comment-textnodes": ["error"],
"@eslint-react/dom/no-dangerously-set-innerhtml": ["error"],
"@eslint-react/naming-convention/context-name": ["error"]
}
}
We should probably look at what ESLint does.
https://github.com/johnsoncodehk/tsslint/issues/41 TSSLint had the same issue before and they solved it. It seems we can refer to their solution https://github.com/johnsoncodehk/tsslint/commit/9b1e0f47698ef560bd35b927e0b3cc5faafb6eee.
cc @overlookmotel we should just copy this exactly i think. we don't need to try the alternative combinations
https://github.com/eslint/eslint/blob/b0674be94e4394401b4f668453a473572c321023/lib/config/config.js#L134-L156
@Rel1cx I see you're the maintainer of @eslint-react/eslint-plugin. Can I ask for your help?
Please excuse my stupidity, but I'm thoroughly confused by this. If a user of @eslint-react/eslint-plugin wants to enable a rule from one of the inherited packages, what do they put in ESLint config? Something like this?
rules: {
"@eslint-react/naming-convention/context-name": "error",
}
If so, how does that work with ESLint, since it appears to split plugin name from rule name on last / when ID starts with @:
if (ruleId.includes("/")) {
// mimic scoped npm packages
if (ruleId.startsWith("@")) {
pluginName = ruleId.slice(0, ruleId.lastIndexOf("/"));
} else {
pluginName = ruleId.slice(0, ruleId.indexOf("/"));
}
ruleName = ruleId.slice(pluginName.length + 1);
}
And the name of the plugin isn't @eslint-react/naming-convention?
@overlookmotel Thanks for reaching out, and that's an good question. The confusion is completely understandable because of how ESLint's configuration and rule parsing have evolved.
Let me address your points one by one:
If a user of
@eslint-react/eslint-pluginwants to enable a rule from one of the inherited packages, what do they put in ESLint config? Something like this?rules: { "@eslint-react/naming-convention/context-name": "error", }
Yes, that is exactly right for the modern flat config.
If so, how does that work with ESLint, since it appears to split plugin name from rule name on last
/when ID starts with@... And the name of the plugin isn't@eslint-react/naming-convention?
You've hit on the core of the issue. With the introduction of the flat config, ESLint's parsing logic changed.
- Legacy Config (e.g.,
.eslintrc): The plugin was@eslint-react, and the rule name wasnaming-convention/context-name. The second/was just a character in the rule name (it’s only meaningful semantically, and doesn’t affect parsing). - Flat Config (e.g.,
eslint.config.js): As you pointed out, ESLint now parses@eslint-react/naming-conventionas the plugin name andcontext-nameas the rule name.
To maintain compatibility with both systems, @eslint-react/eslint-plugin registers @eslint-react/naming-convention as a "sub-plugin" when used in a flat config environment. This makes your configuration work as expected without requiring users to change rule names during migration.
So, in essence, for flat config, @eslint-react/naming-convention is treated as the plugin name, just as your investigation into ESLint's source code suggests.
So, the config file as shown below is closer to ESLint’s current behavior:
{
"jsPlugins": [
"@eslint-react",
"@eslint-react/dom",
"@eslint-react/naming-convention"
],
"rules": {
"@eslint-react/jsx-no-comment-textnodes": ["error"],
"@eslint-react/dom/no-dangerously-set-innerhtml": ["error"],
"@eslint-react/naming-convention/context-name": ["error"]
}
}
Or just use the unscoped plugins from ESLint React:
{
"jsPlugins": [
"react-x",
"react-dom",
"react-naming-convention"
],
"rules": {
"react-x/jsx-no-comment-textnodes": ["error"],
"react-dom/no-dangerously-set-innerhtml": ["error"],
"react-naming-convention/context-name": ["error"]
}
}
cc @magic-akari
@overlookmotel I have reorganized the reply in https://github.com/oxc-project/oxc/issues/14557#issuecomment-3399160211, and I hope it can better answer your questions this time.
Is there any solution currently to this problem?
Not yet
This is also a problem for eslint plugin tanstack query:
{
"jsPlugins": ["@tanstack/eslint-plugin-query"],
"categories": {
"correctness": "off"
},
"rules": {
"@tanstack/query/exhaustive-deps": "error",
"@tanstack/query/no-deprecated-options": "error",
"@tanstack/query/prefer-query-object-syntax": "error",
"@tanstack/query/stable-query-client": "error"
}
}
Error:
./node_modules/.bin/oxlint
WARNING: JS plugins are experimental and not subject to semver.
Breaking changes are possible while JS plugins support is under development.
Failed to parse configuration file.
× Plugin '@tanstack' not found