feat(unstable): lint plugins support field selectors (#28324)

This PR adds support for field selectors (`.<field>`) in the lint plugin
API. This is supported in ESLint as well, but was missing in our
implementation.

```css
/* Only search the test expression of an IfStatement */
IfStatement.test
```

Fixes https://github.com/denoland/deno/issues/28314
This commit is contained in:
Marvin Hagemeister 2025-02-28 15:10:02 +01:00 committed by GitHub
parent b4aa3e6d1e
commit 3a1f3455b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 161 additions and 1 deletions

View file

@ -9,6 +9,7 @@
/** @typedef {import("./40_lint_types.d.ts").AttrBin} AttrBin */
/** @typedef {import("./40_lint_types.d.ts").AttrSelector} AttrSelector */
/** @typedef {import("./40_lint_types.d.ts").ElemSelector} ElemSelector */
/** @typedef {import("./40_lint_types.d.ts").FieldSelector} FieldSelector */
/** @typedef {import("./40_lint_types.d.ts").PseudoNthChild} PseudoNthChild */
/** @typedef {import("./40_lint_types.d.ts").PseudoHas} PseudoHas */
/** @typedef {import("./40_lint_types.d.ts").PseudoNot} PseudoNot */
@ -376,6 +377,7 @@ export const PSEUDO_HAS = 6;
export const PSEUDO_NOT = 7;
export const PSEUDO_FIRST_CHILD = 8;
export const PSEUDO_LAST_CHILD = 9;
export const FIELD_NODE = 10;
/**
* Parse out all unique selectors of a selector list.
@ -492,6 +494,26 @@ export function parseSelector(input, toElem, toAttr) {
lex.expect(Token.BracketClose);
lex.next();
continue;
} else if (lex.token === Token.Dot) {
lex.next();
lex.expect(Token.Word);
const props = [toAttr(lex.value)];
lex.next();
while (lex.token === Token.Dot) {
lex.next();
lex.expect(Token.Word);
props.push(toAttr(lex.value));
lex.next();
}
current.push({
type: FIELD_NODE,
props,
});
continue;
} else if (lex.token === Token.Colon) {
lex.next();
lex.expect(Token.Word);
@ -710,6 +732,9 @@ export function compileSelector(selector) {
case ELEM_NODE:
fn = matchElem(node, fn);
break;
case FIELD_NODE:
fn = matchField(node, fn);
break;
case RELATION_NODE:
switch (node.op) {
case BinOp.Space:
@ -960,6 +985,39 @@ function matchElem(part, next) {
};
}
/**
* @param {FieldSelector} part
* @param {MatcherFn} next
* @returns {MatcherFn}
*/
function matchField(part, next) {
return (ctx, id) => {
let child = id;
let parent = ctx.getParent(id);
if (parent === 0) return false;
// Fields are stored left-ro-right but we need to match
// them right-to-left because we're matching selectors
// in that direction. Matching right to left is done for
// performance and reduces the number of potential mismatches.
for (let i = part.props.length - 1; i >= 0; i--) {
const prop = part.props[i];
const value = ctx.getField(parent, prop);
if (value === -1) return false;
if (value !== child) return false;
if (i > 0) {
child = parent;
parent = ctx.getParent(parent);
if (parent === 0) return false;
}
}
return next(ctx, parent);
};
}
/**
* @param {AttrExists} attr
* @param {MatcherFn} next