feat: Add getPossibleTypesForSelectorClass to Language interface#148
feat: Add getPossibleTypesForSelectorClass to Language interface#148Kuldeep2822k wants to merge 7 commits into
Conversation
|
Hi @Kuldeep2822k!, thanks for the Pull Request The pull request title isn't properly formatted. We ask that you update the pull request title to match this format, as we use it to generate changelogs and automate releases.
To Fix: You can fix this problem by clicking 'Edit' next to the pull request title at the top of this page. Read more about contributing to ESLint here |
|
|
||
| ## Help Needed | ||
|
|
||
| I am willing to submit a pull request with the reference implementation for this RFC once it is accepted. Performance benchmarking (using `npm run test:performance`) will be done to confirm there is no regression. |
There was a problem hiding this comment.
I tested the performance with and without the current switch case for "class" in analyzeParsedSelector using npm run test:performance. There was no difference as no core rule uses the :function selector, so we would need another benchmark for this.
There was a problem hiding this comment.
You're right — no core rule uses :function, so test:performance can't exercise this path. I've dropped that reference from the RFC. The architectural framing (delegating static-analysis ownership to the language plugin) was always the primary motivation here .
| fallback: vk.getKeys, | ||
| matchClass: this.#language.matchesSelectorClass ?? (() => false), | ||
| nodeTypeKey: this.#language.nodeTypeKey, | ||
| + language: this.#language, |
There was a problem hiding this comment.
The other properties and methods from this.#language are passed directly, so why pass the whole language instead of only getPossibleTypesForSelectorClass?
There was a problem hiding this comment.
Good point, you're right. I'll switch to passing only the method.
The reason I had it as the full language was the cache — it's currently a WeakMap keyed by language object. Since the JS language exports a singleton and getSelectorClassNodeTypes is a stable reference, I can key the cache on the method instead and keep the pattern consistent:
js getSelectorClassNodeTypes: this.#language.getSelectorClassNodeTypes,
Pushing an update shortl
| const noLanguageCache = new Map(); | ||
|
|
||
| function getLanguageCache(language) { | ||
| if (!language) { |
There was a problem hiding this comment.
The language is always set, so there is no need for noLanguageCache.
| - ]; | ||
| - } | ||
| - return null; | ||
| + return language?.getPossibleTypesForSelectorClass?.(selector.name) ?? null; |
There was a problem hiding this comment.
| + return language?.getPossibleTypesForSelectorClass?.(selector.name) ?? null; | |
| + return language.getPossibleTypesForSelectorClass?.(selector.name) ?? null; |
The language cannot be nullish.
nzakas
left a comment
There was a problem hiding this comment.
Thanks for putting this together. I think it would be simpler to create a property map on each language. See my comment inline.
Question: Did AI write this RFC?
| - **`string[]`** — Only nodes of these types could match the pseudo-class. The traverser will only invoke selector matching for these node types, skipping all others. | ||
| - **`null`** — Any node type could match. The traverser must check every node (this is the safe fallback). | ||
|
|
||
| **If a language does not implement this method**, the core falls back to returning `null` for all class selectors, which is functionally equivalent to the current behavior for all pseudo-classes _except_ `:function` in JS. Existing language implementations remain unaffected. |
There was a problem hiding this comment.
Just for clarity, you're saying that any unmatched selectors today match all nodes? That's the current behavior?
There was a problem hiding this comment.
I need to clarify — I conflated static analysis with runtime matching. Class selectors other than :function do not match all nodes at runtime. What I meant is that in the static analysis layer (analyzeParsedSelector()), they get nodeTypes: null, which means the traverser can't narrow which node types to check — so it tests them against every node. But esquery.matches() still filters correctly at runtime (e.g., :statement matched 9 out of 36 nodes on a test file, not all 36).
| Instead of a separate method, modify `matchesSelectorClass()` to optionally return type information. This was rejected because it changes the semantics of an existing method and makes the return type complex (boolean vs. type array). | ||
|
|
||
|
|
||
| ## Open Questions |
There was a problem hiding this comment.
I think another question is whether unknown selectors should actually match all nodes.
There was a problem hiding this comment.
before adding this i want to mention something :- an unknown class name doesn't actually match all nodes, it throws. matchesSelectorClass has a default case that throws "Unknown class name" (lib/languages/js/index.js line 226). so the static analysis says "could match any node", but the runtime throws as soon as the selector's class match is evaluated. if i am wrong do correct me
|
|
||
| 5. **Type definitions.** The method is added as an optional property (`getSelectorClassNodeTypes?`) in `@eslint/core`'s `Language` interface, so existing TypeScript users are unaffected. | ||
|
|
||
| ## Alternatives |
There was a problem hiding this comment.
I think another, simpler alternative is to create a selectorClassNodeTypes pubic field on the Language interface that is Map<string, Array<string>>. The core can always convert the class name to lowercase and then match against the map.
I don't think we need to make this a method because there really isn't any additional logic necessary.
There was a problem hiding this comment.
I agree this is a simpler and better approach. Would you like me to revise the RFC to use the Map field as the main proposal, or add it as an alternative?
yes i did use ai to help me draft it |
Summary
Add an optional getPossibleTypesForSelectorClass(className) method to the Language interface that allows languages to declare which AST node types could match a given pseudo-class selector (e.g., :function). This removes hardcoded JavaScript-specific logic from the core selector analysis in lib/linter/esquery.js and enables any language plugin to provide the same optimization.
Related Issues
matchesSelectorClass()