mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 02:48:24 +00:00
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:
parent
b4aa3e6d1e
commit
3a1f3455b0
5 changed files with 161 additions and 1 deletions
|
@ -791,6 +791,37 @@ class MatchCtx {
|
|||
return readType(this.ctx.buf, idx);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} idx
|
||||
* @param {number} propId
|
||||
* @returns {number}
|
||||
*/
|
||||
getField(idx, propId) {
|
||||
if (idx === AST_IDX_INVALID) return -1;
|
||||
|
||||
// Bail out on fields that can never point to another node
|
||||
switch (propId) {
|
||||
case AST_PROP_TYPE:
|
||||
case AST_PROP_PARENT:
|
||||
case AST_PROP_RANGE:
|
||||
return -1;
|
||||
}
|
||||
|
||||
const { buf } = this.ctx;
|
||||
let offset = readPropOffset(this.ctx, idx);
|
||||
offset = findPropOffset(buf, offset, propId);
|
||||
|
||||
if (offset === -1) return -1;
|
||||
const _prop = buf[offset++];
|
||||
const kind = buf[offset++];
|
||||
|
||||
if (kind === PropFlags.Ref) {
|
||||
return readU32(buf, offset);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} idx - Node idx
|
||||
* @param {number[]} propIds
|
||||
|
@ -798,7 +829,7 @@ class MatchCtx {
|
|||
* @returns {unknown}
|
||||
*/
|
||||
getAttrPathValue(idx, propIds, propIdx) {
|
||||
if (idx === 0) throw -1;
|
||||
if (idx === AST_IDX_INVALID) throw -1;
|
||||
|
||||
const { buf, strTable, strByType } = this.ctx;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
7
cli/js/40_lint_types.d.ts
vendored
7
cli/js/40_lint_types.d.ts
vendored
|
@ -50,6 +50,11 @@ export interface ElemSelector {
|
|||
elem: number;
|
||||
}
|
||||
|
||||
export interface FieldSelector {
|
||||
type: 10;
|
||||
props: number[];
|
||||
}
|
||||
|
||||
export interface PseudoNthChild {
|
||||
type: 5;
|
||||
op: string | null;
|
||||
|
@ -81,6 +86,7 @@ export interface Relation {
|
|||
|
||||
export type Selector = Array<
|
||||
| ElemSelector
|
||||
| FieldSelector
|
||||
| Relation
|
||||
| AttrExists
|
||||
| AttrBin
|
||||
|
@ -101,6 +107,7 @@ export interface MatchContext {
|
|||
getLastChild(id: number): number;
|
||||
getSiblings(id: number): number[];
|
||||
getParent(id: number): number;
|
||||
getField(id: number, prop: number): number;
|
||||
getType(id: number): number;
|
||||
getAttrPathValue(id: number, propIds: number[], idx: number): unknown;
|
||||
}
|
||||
|
|
|
@ -177,6 +177,29 @@ Deno.test("Plugin - visitor subsequent sibling", () => {
|
|||
assertEquals(result.map((r) => r.node.name), ["bar", "baz"]);
|
||||
});
|
||||
|
||||
Deno.test("Plugin - visitor field", () => {
|
||||
let result = testVisit(
|
||||
"if (foo()) {}",
|
||||
"IfStatement.test.callee",
|
||||
);
|
||||
assertEquals(result[0].node.type, "Identifier");
|
||||
assertEquals(result[0].node.name, "foo");
|
||||
|
||||
result = testVisit(
|
||||
"if (foo()) {}",
|
||||
"IfStatement .test .callee",
|
||||
);
|
||||
assertEquals(result[0].node.type, "Identifier");
|
||||
assertEquals(result[0].node.name, "foo");
|
||||
|
||||
result = testVisit(
|
||||
"if (foo(bar())) {}",
|
||||
"IfStatement.test CallExpression.callee",
|
||||
);
|
||||
assertEquals(result[0].node.type, "Identifier");
|
||||
assertEquals(result[0].node.name, "bar");
|
||||
});
|
||||
|
||||
Deno.test("Plugin - visitor attr", () => {
|
||||
let result = testVisit(
|
||||
"for (const a of b) {}",
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
ATTR_EXISTS_NODE,
|
||||
BinOp,
|
||||
ELEM_NODE,
|
||||
FIELD_NODE,
|
||||
Lexer,
|
||||
parseSelector,
|
||||
PSEUDO_FIRST_CHILD,
|
||||
|
@ -255,6 +256,19 @@ Deno.test("Lexer - Pseudo", () => {
|
|||
]);
|
||||
});
|
||||
|
||||
Deno.test("Lexer - field", () => {
|
||||
assertEquals(testLexer(".bar"), [
|
||||
{ token: Token.Dot, value: "" },
|
||||
{ token: Token.Word, value: "bar" },
|
||||
]);
|
||||
assertEquals(testLexer(".bar.baz"), [
|
||||
{ token: Token.Dot, value: "" },
|
||||
{ token: Token.Word, value: "bar" },
|
||||
{ token: Token.Dot, value: "" },
|
||||
{ token: Token.Word, value: "baz" },
|
||||
]);
|
||||
});
|
||||
|
||||
Deno.test("Parser - Elem", () => {
|
||||
assertEquals(testParse("Foo"), [[
|
||||
{
|
||||
|
@ -337,6 +351,33 @@ Deno.test("Parser - Relation", () => {
|
|||
]]);
|
||||
});
|
||||
|
||||
Deno.test("Parser - Field", () => {
|
||||
assertEquals(testParse("Foo.bar"), [[
|
||||
{
|
||||
type: ELEM_NODE,
|
||||
elem: 1,
|
||||
wildcard: false,
|
||||
},
|
||||
{ type: FIELD_NODE, props: [2] },
|
||||
]]);
|
||||
assertEquals(testParse("Foo .bar"), [[
|
||||
{
|
||||
type: ELEM_NODE,
|
||||
elem: 1,
|
||||
wildcard: false,
|
||||
},
|
||||
{ type: FIELD_NODE, props: [2] },
|
||||
]]);
|
||||
assertEquals(testParse("Foo .foo.bar"), [[
|
||||
{
|
||||
type: ELEM_NODE,
|
||||
elem: 1,
|
||||
wildcard: false,
|
||||
},
|
||||
{ type: FIELD_NODE, props: [1, 2] },
|
||||
]]);
|
||||
});
|
||||
|
||||
Deno.test("Parser - Attr", () => {
|
||||
assertEquals(testParse("[foo]"), [[
|
||||
{
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue