oxc icon indicating copy to clipboard operation
oxc copied to clipboard

Linter plugins: Support plugins with `/` in name

Open overlookmotel opened this issue 2 months ago • 9 comments

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

  1. For each key in rules, try all possible combinations - i.e.
  • Plugin @eslint-react, rule eslint-plugin/naming-convention/context-name
  • Plugin @eslint-react/eslint-plugin, rule naming-convention/context-name
  • Plugin @eslint-react/eslint-plugin/naming-convention, rule context-name
  1. Automatically remove eslint-plugin- prefix or /eslint-plugin postfix 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.

overlookmotel avatar Oct 13 '25 14:10 overlookmotel

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.

Rel1cx avatar Oct 13 '25 14:10 Rel1cx

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

camc314 avatar Oct 13 '25 14:10 camc314

@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);
}

(source)

And the name of the plugin isn't @eslint-react/naming-convention?

overlookmotel avatar Oct 13 '25 19:10 overlookmotel

@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-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",
}

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 was naming-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-convention as the plugin name and context-name as 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.

Rel1cx avatar Oct 13 '25 21:10 Rel1cx

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

Rel1cx avatar Oct 13 '25 23:10 Rel1cx

@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.

Rel1cx avatar Oct 15 '25 13:10 Rel1cx

Is there any solution currently to this problem?

BeyramTaglietti avatar Nov 10 '25 13:11 BeyramTaglietti

Not yet

camc314 avatar Nov 12 '25 21:11 camc314

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

camc314 avatar Nov 12 '25 21:11 camc314