fix: Fix plugin scope determination logic#402
Conversation
| /** | ||
| * Resolves the npm package name for an ESLint plugin given its scope name. | ||
| * | ||
| * For scoped plugin names (starting with `@`), the mapping is unambiguous: | ||
| * - `@scope` -> `@scope/eslint-plugin` | ||
| * - `@scope/sub` -> `@scope/eslint-plugin-sub` | ||
| * | ||
| * For non-scoped names, the npm package could follow either convention: | ||
| * - `eslint-plugin-{name}` (e.g. `eslint-plugin-mocha`) | ||
| * - `@{name}/eslint-plugin` (e.g. `@e18e/eslint-plugin`) | ||
| * | ||
| * We try to resolve both candidates against the installed packages and | ||
| * use the one that is actually present, falling back to the standard | ||
| * `eslint-plugin-{name}` convention when neither can be resolved. | ||
| */ |
There was a problem hiding this comment.
This comment is entirely Claude's assumption, I'm pretty sure you could (though if you did, you're insane) have a non-scoped plugin like eslint-plugin-foo but set meta.name to @foo. But I have no idea if we care about supporting that edge-case for now.
7bbd47d to
5a9a01c
Compare
| const guessEslintPluginName = (pluginName: string): string => { | ||
| const tryResolvePackage = (packageName: string): boolean => { | ||
| try { | ||
| import.meta.resolve(packageName); |
There was a problem hiding this comment.
Only concern is whether it can actually resolve this, e.g. if you use npx @oxlint/migrate but your project is using yarn or pnpm, will npx be able to resolve the packages?
This comment was marked as outdated.
This comment was marked as outdated.
ec5d2f7 to
982ccdc
Compare
|
Alright, I think this correctly handles all three cases:
I guess we could consider setting this up to explicitly use the aliasing system for the last edge case. Then the rules can have names like |
src/plugins_rules.ts
Outdated
| // with enabled rules that go through enableJsPluginRule. | ||
| const resolvedRule = resolveJsPluginRuleName( | ||
| rule, | ||
| eslintConfig.plugins |
There was a problem hiding this comment.
Can a rule be activated from a plugin declaration from another ESLint config?
Something like:
export default [
{"plugins": await import("xyz") },
{"rules": { "xyz/rule-1": "error" } }
]
There was a problem hiding this comment.
hmm, I'm not sure if you can do it async like that, but it can be activated from another config
There was a problem hiding this comment.
The async part was only for code demo :).
Then we should probably collect first all plugins across every ESLint config?
There was a problem hiding this comment.
Hmm, after thinking on it more: I think the plugins value should be fully resolved at this point in our evaluation of the config. So I'm pretty sure this wouldn't be a problem?
There was a problem hiding this comment.
Not 100% sure, somebody needs to test it :')
The first call will have only {"plugins": ...} and the second call will only have the rule {"rules": {"xyz-rule1": ...}}.
resolveJsPluginRuleName does not access the global resolved plugin cache. Instead, it relies on eslintConfig.plugins
There was a problem hiding this comment.
I've added a snapshot test and confirmed the behavior of ESLint is as you described, now I just need to fix it...
There was a problem hiding this comment.
Ah you mentioned "across all plugins". I didn't realize this case would work as well, let me make sure this case is fixed 🙃
import e18e from '@e18e/eslint-plugin';
export default [
{
rules: {
'e18e/prefer-array-at': 'error',
'e18e/prefer-includes': 'warn'
},
},
{
plugins: { e18e }
},
];
There was a problem hiding this comment.
^ FWIW I did test that just now (with @eslint-react/dom/ as well) and it worked correctly with no code changes, so we're good!
This is just taking some of the tests added in #402 and porting them to main so I can avoid merge conflicts later. I assume these tests are safe to merge since they are just tests and are simple enough.
982ccdc to
c9db8fc
Compare
This is just taking some of the tests added in #402 and porting them to main so I can avoid merge conflicts later. I assume these tests are safe to merge since they are just tests and are simple enough.
Instead of guessing the npm package name from the plugin scope
(e.g. "e18e" → "eslint-plugin-e18e"), try to resolve the actual
installed package. This correctly handles plugins like
@e18e/eslint-plugin that register with a non-scoped name ("e18e")
but whose npm package uses the @scope/eslint-plugin convention.
For non-scoped plugin names, we now try both candidate conventions:
1. eslint-plugin-{name} (standard, e.g. eslint-plugin-mocha)
2. @{name}/eslint-plugin (scoped, e.g. @e18e/eslint-plugin)
Results are cached to avoid repeated module resolution.
https://claude.ai/code/session_01GBWbJpvKSgVenUQNzc27dg
When an ESLint plugin provides meta.name, use it as the authoritative source for the npm package name rather than guessing from the rule ID. If meta.name contains "eslint-plugin" it is used directly; otherwise it is fed through the existing heuristic resolution. https://claude.ai/code/session_01GBWbJpvKSgVenUQNzc27dg
This PR fixes oxc-project#377.
When a plugin is registered under an alias (e.g. `@eslint-react/dom`) but its `meta.name` reveals a different canonical package name (e.g. `eslint-plugin-react-dom`), rewrite rule prefixes so oxlint can match them to the loaded plugin. For example: @eslint-react/dom/no-find-dom-node -> react-dom/no-find-dom-node @eslint-react/jsx-key-before-spread -> react-x/jsx-key-before-spread https://claude.ai/code/session_01GBWbJpvKSgVenUQNzc27dg
c9db8fc to
6c49337
Compare
|
I have merged the integration tests for the |
src/plugins_rules.ts
Outdated
| // with enabled rules that go through enableJsPluginRule. | ||
| const resolvedRule = resolveJsPluginRuleName( | ||
| rule, | ||
| eslintConfig.plugins |
There was a problem hiding this comment.
Not 100% sure, somebody needs to test it :')
The first call will have only {"plugins": ...} and the second call will only have the rule {"rules": {"xyz-rule1": ...}}.
resolveJsPluginRuleName does not access the global resolved plugin cache. Instead, it relies on eslintConfig.plugins
| return rule; | ||
| } | ||
|
|
||
| const metaName = plugins?.[pluginName]?.meta?.name; |
This adds a snapshot test to ensure that the, I confirmed in a test repo that this is valid and works in ESLint. We need to fix the logic of oxlint-migrate to handle it.
It does not result in the correct config right now.
|
thank you for looking into this and confirming the global plugins problem :) |
This change ensures that the plugin rules are migrated with the correct name (
e18e/rule-name, not@e18e/rule-name). It also ensures that the plugin name that is used is correct.It should migrate as
"jsPlugins": ["@e18e/eslint-plugin"], but prior to this PR did not.From what I understand, the reason it is
e18e/rule-namedespite the scoped plugin name is that it specifies themeta.namefield here.The Tanstack integration test is unchanged as it was already correct.
This also fixes using the usage of
@eslint-reactplugin. Fixes #377.Generated with Claude Code, tested by me and confirmed as fixing the problem fully.