mirror of
https://github.com/denoland/deno.git
synced 2025-03-03 17:34:47 -05:00
fix(unstable): :is()/:where()/:matches
This commit is contained in:
parent
b545d250be
commit
c2abcb9a3d
5 changed files with 169 additions and 24 deletions
|
@ -954,7 +954,7 @@ class MatchCtx {
|
|||
}
|
||||
|
||||
/**
|
||||
* Used for `:has/:is/:where` and `:not`
|
||||
* Used for `:has()` and `:not()`
|
||||
* @param {MatcherFn[]} selectors
|
||||
* @param {number} idx
|
||||
* @returns {boolean}
|
||||
|
|
|
@ -172,6 +172,31 @@ export class Lexer {
|
|||
}
|
||||
}
|
||||
|
||||
peek() {
|
||||
const value = this.value;
|
||||
const start = this.start;
|
||||
const end = this.end;
|
||||
const i = this.i;
|
||||
const ch = this.ch;
|
||||
const token = this.token;
|
||||
|
||||
this.next();
|
||||
|
||||
const result = {
|
||||
token: this.token,
|
||||
value: this.value,
|
||||
};
|
||||
|
||||
this.vaue = value;
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.i = i;
|
||||
this.ch = ch;
|
||||
this.token = token;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
next() {
|
||||
this.value = "";
|
||||
|
||||
|
@ -377,6 +402,7 @@ export const PSEUDO_NOT = 7;
|
|||
export const PSEUDO_FIRST_CHILD = 8;
|
||||
export const PSEUDO_LAST_CHILD = 9;
|
||||
export const FIELD_NODE = 10;
|
||||
export const PSEUDO_IS = 11;
|
||||
|
||||
/**
|
||||
* Parse out all unique selectors of a selector list.
|
||||
|
@ -458,6 +484,19 @@ export function parseSelector(input, toElem, toAttr) {
|
|||
type: RELATION_NODE,
|
||||
op: BinOp.Space,
|
||||
});
|
||||
} else if (lex.token === Token.Colon) {
|
||||
const peeked = lex.peek();
|
||||
|
||||
if (
|
||||
peeked.token === Token.Word &&
|
||||
(peeked.value === "is" || peeked.value === "where" ||
|
||||
peeked.value === "matches")
|
||||
) {
|
||||
current.push({
|
||||
type: RELATION_NODE,
|
||||
op: BinOp.Space,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
|
@ -616,14 +655,26 @@ export function parseSelector(input, toElem, toAttr) {
|
|||
|
||||
continue;
|
||||
}
|
||||
|
||||
case "has":
|
||||
case "where":
|
||||
case "matches":
|
||||
case "is": {
|
||||
lex.next();
|
||||
lex.expect(Token.BraceOpen);
|
||||
lex.next();
|
||||
|
||||
current.push({
|
||||
type: PSEUDO_IS,
|
||||
selectors: [],
|
||||
});
|
||||
stack.push([]);
|
||||
|
||||
continue;
|
||||
}
|
||||
case "has": {
|
||||
lex.next();
|
||||
lex.expect(Token.BraceOpen);
|
||||
lex.next();
|
||||
|
||||
current.push({
|
||||
type: PSEUDO_HAS,
|
||||
selectors: [],
|
||||
|
@ -704,7 +755,10 @@ function popSelector(result, stack) {
|
|||
|
||||
if (node.type === PSEUDO_NTH_CHILD) {
|
||||
node.of = sel;
|
||||
} else if (node.type === PSEUDO_HAS || node.type === PSEUDO_NOT) {
|
||||
} else if (
|
||||
node.type === PSEUDO_HAS || node.type === PSEUDO_IS ||
|
||||
node.type === PSEUDO_NOT
|
||||
) {
|
||||
node.selectors.push(sel);
|
||||
} else {
|
||||
throw new Error(`Multiple selectors not allowed here`);
|
||||
|
@ -770,6 +824,9 @@ export function compileSelector(selector) {
|
|||
case PSEUDO_HAS:
|
||||
fn = matchHas(node.selectors, fn);
|
||||
break;
|
||||
case PSEUDO_IS:
|
||||
fn = matchIs(node.selectors, fn);
|
||||
break;
|
||||
case PSEUDO_NOT:
|
||||
fn = matchNot(node.selectors, fn);
|
||||
break;
|
||||
|
@ -865,6 +922,30 @@ function matchNthChild(node, next) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Selector[]} selectors
|
||||
* @param {MatcherFn} next
|
||||
* @returns {MatcherFn}
|
||||
*/
|
||||
function matchIs(selectors, next) {
|
||||
/** @type {MatcherFn[]} */
|
||||
const compiled = [];
|
||||
|
||||
for (let i = 0; i < selectors.length; i++) {
|
||||
const sel = selectors[i];
|
||||
compiled.push(compileSelector(sel));
|
||||
}
|
||||
|
||||
return (ctx, id) => {
|
||||
for (let i = 0; i < compiled.length; i++) {
|
||||
const sel = compiled[i];
|
||||
if (sel(ctx, id)) return next(ctx, id);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Selector[]} selectors
|
||||
* @param {MatcherFn} next
|
||||
|
|
5
cli/js/40_lint_types.d.ts
vendored
5
cli/js/40_lint_types.d.ts
vendored
|
@ -68,6 +68,10 @@ export interface PseudoHas {
|
|||
type: 6;
|
||||
selectors: Selector[];
|
||||
}
|
||||
export interface PseudoIs {
|
||||
type: 11;
|
||||
selectors: Selector[];
|
||||
}
|
||||
export interface PseudoNot {
|
||||
type: 7;
|
||||
selectors: Selector[];
|
||||
|
@ -93,6 +97,7 @@ export type Selector = Array<
|
|||
| PseudoNthChild
|
||||
| PseudoNot
|
||||
| PseudoHas
|
||||
| PseudoIs
|
||||
| PseudoFirstChild
|
||||
| PseudoLastChild
|
||||
>;
|
||||
|
|
|
@ -355,25 +355,13 @@ Deno.test("Plugin - visitor :nth-child", () => {
|
|||
assertEquals(result[1].node.name, "foobar");
|
||||
});
|
||||
|
||||
Deno.test("Plugin - visitor :has/:is/:where", () => {
|
||||
Deno.test("Plugin - visitor :has()", () => {
|
||||
let result = testVisit(
|
||||
"{ foo, bar }",
|
||||
"BlockStatement:has(Identifier[name='bar'])",
|
||||
);
|
||||
assertEquals(result[0].node.type, "BlockStatement");
|
||||
|
||||
result = testVisit(
|
||||
"{ foo, bar }",
|
||||
"BlockStatement:is(Identifier[name='bar'])",
|
||||
);
|
||||
assertEquals(result[0].node.type, "BlockStatement");
|
||||
|
||||
result = testVisit(
|
||||
"{ foo, bar }",
|
||||
"BlockStatement:where(Identifier[name='bar'])",
|
||||
);
|
||||
assertEquals(result[0].node.type, "BlockStatement");
|
||||
|
||||
// Multiple sub queries
|
||||
result = testVisit(
|
||||
"{ foo, bar }",
|
||||
|
@ -397,6 +385,29 @@ Deno.test("Plugin - visitor :has/:is/:where", () => {
|
|||
assertEquals(result[0].node.name, "bar");
|
||||
});
|
||||
|
||||
Deno.test("Plugin - visitor :is()/:where()/:matches()", () => {
|
||||
let result = testVisit(
|
||||
"{ foo, bar }",
|
||||
"BlockStatement :is(Identifier[name='bar'])",
|
||||
);
|
||||
assertEquals(result[0].node.type, "Identifier");
|
||||
assertEquals(result[0].node.name, "bar");
|
||||
|
||||
result = testVisit(
|
||||
"{ foo, bar }",
|
||||
"BlockStatement :where(Identifier[name='bar'])",
|
||||
);
|
||||
assertEquals(result[0].node.type, "Identifier");
|
||||
assertEquals(result[0].node.name, "bar");
|
||||
|
||||
result = testVisit(
|
||||
"{ foo, bar }",
|
||||
"BlockStatement :matches(Identifier[name='bar'])",
|
||||
);
|
||||
assertEquals(result[0].node.type, "Identifier");
|
||||
assertEquals(result[0].node.name, "bar");
|
||||
});
|
||||
|
||||
Deno.test("Plugin - visitor :not", () => {
|
||||
let result = testVisit(
|
||||
"{ foo, bar }",
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
parseSelector,
|
||||
PSEUDO_FIRST_CHILD,
|
||||
PSEUDO_HAS,
|
||||
PSEUDO_IS,
|
||||
PSEUDO_LAST_CHILD,
|
||||
PSEUDO_NOT,
|
||||
PSEUDO_NTH_CHILD,
|
||||
|
@ -554,7 +555,7 @@ Deno.test("Parser - Pseudo nth-child", () => {
|
|||
assertThrows(() => testParse(":nth-child(2n - 1 foo)"));
|
||||
});
|
||||
|
||||
Deno.test("Parser - Pseudo has/is/where", () => {
|
||||
Deno.test("Parser - Pseudo :has()", () => {
|
||||
assertEquals(testParse(":has(Foo:has(Foo), Bar)"), [[
|
||||
{
|
||||
type: PSEUDO_HAS,
|
||||
|
@ -574,14 +575,17 @@ Deno.test("Parser - Pseudo has/is/where", () => {
|
|||
],
|
||||
},
|
||||
]]);
|
||||
assertEquals(testParse(":where(Foo:where(Foo), Bar)"), [[
|
||||
});
|
||||
|
||||
Deno.test("Parser - Pseudo :is()/:where()/:matches()", () => {
|
||||
assertEquals(testParse(":is(Foo:is(Foo), Bar)"), [[
|
||||
{
|
||||
type: PSEUDO_HAS,
|
||||
type: PSEUDO_IS,
|
||||
selectors: [
|
||||
[
|
||||
{ type: ELEM_NODE, elem: 1, wildcard: false },
|
||||
{
|
||||
type: PSEUDO_HAS,
|
||||
type: PSEUDO_IS,
|
||||
selectors: [
|
||||
[{ type: ELEM_NODE, elem: 1, wildcard: false }],
|
||||
],
|
||||
|
@ -593,14 +597,14 @@ Deno.test("Parser - Pseudo has/is/where", () => {
|
|||
],
|
||||
},
|
||||
]]);
|
||||
assertEquals(testParse(":is(Foo:is(Foo), Bar)"), [[
|
||||
assertEquals(testParse(":where(Foo:where(Foo), Bar)"), [[
|
||||
{
|
||||
type: PSEUDO_HAS,
|
||||
type: PSEUDO_IS,
|
||||
selectors: [
|
||||
[
|
||||
{ type: ELEM_NODE, elem: 1, wildcard: false },
|
||||
{
|
||||
type: PSEUDO_HAS,
|
||||
type: PSEUDO_IS,
|
||||
selectors: [
|
||||
[{ type: ELEM_NODE, elem: 1, wildcard: false }],
|
||||
],
|
||||
|
@ -612,6 +616,50 @@ Deno.test("Parser - Pseudo has/is/where", () => {
|
|||
],
|
||||
},
|
||||
]]);
|
||||
assertEquals(testParse(":matches(Foo:matches(Foo), Bar)"), [[
|
||||
{
|
||||
type: PSEUDO_IS,
|
||||
selectors: [
|
||||
[
|
||||
{ type: ELEM_NODE, elem: 1, wildcard: false },
|
||||
{
|
||||
type: PSEUDO_IS,
|
||||
selectors: [
|
||||
[{ type: ELEM_NODE, elem: 1, wildcard: false }],
|
||||
],
|
||||
},
|
||||
],
|
||||
[
|
||||
{ type: ELEM_NODE, elem: 2, wildcard: false },
|
||||
],
|
||||
],
|
||||
},
|
||||
]]);
|
||||
|
||||
assertEquals(testParse("Foo:is(Bar)"), [[
|
||||
{ type: ELEM_NODE, elem: 1, wildcard: false },
|
||||
{
|
||||
type: PSEUDO_IS,
|
||||
selectors: [
|
||||
[
|
||||
{ type: ELEM_NODE, elem: 2, wildcard: false },
|
||||
],
|
||||
],
|
||||
},
|
||||
]]);
|
||||
|
||||
assertEquals(testParse("Foo :is(Bar)"), [[
|
||||
{ type: ELEM_NODE, elem: 1, wildcard: false },
|
||||
{ type: RELATION_NODE, op: BinOp.Space },
|
||||
{
|
||||
type: PSEUDO_IS,
|
||||
selectors: [
|
||||
[
|
||||
{ type: ELEM_NODE, elem: 2, wildcard: false },
|
||||
],
|
||||
],
|
||||
},
|
||||
]]);
|
||||
});
|
||||
|
||||
Deno.test("Parser - Pseudo not", () => {
|
||||
|
|
Loading…
Add table
Reference in a new issue