mirror of
https://github.com/denoland/deno.git
synced 2025-02-07 23:06:50 -05:00
integrate selctors part 1
This commit is contained in:
parent
132db5604f
commit
d9fd959820
4 changed files with 210 additions and 79 deletions
|
@ -3,9 +3,11 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
|
|
||||||
import { core } from "ext:core/mod.js";
|
import { core } from "ext:core/mod.js";
|
||||||
import { compileSelector, parseSelector } from "ext:cli/lint/selector.js";
|
import {
|
||||||
|
compileSelector,
|
||||||
console.log({ compileSelector, foo: "foo" });
|
parseSelector,
|
||||||
|
splitSelectors,
|
||||||
|
} from "ext:cli/lint/selector.js";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
op_lint_get_rule,
|
op_lint_get_rule,
|
||||||
|
@ -37,8 +39,9 @@ const PropFlags = {
|
||||||
* rootId: number,
|
* rootId: number,
|
||||||
* nodes: Map<number, Node>,
|
* nodes: Map<number, Node>,
|
||||||
* strByType: number[],
|
* strByType: number[],
|
||||||
* typeByStr: Map<string, number>,
|
|
||||||
* strByProp: number[]
|
* strByProp: number[]
|
||||||
|
* typeByStr: Map<string, number>,
|
||||||
|
* propByStr: Map<string, number>,
|
||||||
* }} AstContext
|
* }} AstContext
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -49,6 +52,19 @@ const PropFlags = {
|
||||||
* }} LintState
|
* }} LintState
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import("./40_lint_types.d.ts").VisitorFn} VisitorFn
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @typedef {import("./40_lint_types.d.ts").MatcherFn} MatcherFn
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @typedef {import("./40_lint_types.d.ts").TransformFn} TransformerFn
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @typedef {import("./40_lint_types.d.ts").CompiledVisitor} CompiledVisitor
|
||||||
|
*/
|
||||||
|
|
||||||
/** @type {LintState} */
|
/** @type {LintState} */
|
||||||
const state = {
|
const state = {
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
@ -418,12 +434,14 @@ function createAstContext(buf) {
|
||||||
const propCount = readU32(buf, offset);
|
const propCount = readU32(buf, offset);
|
||||||
offset += 4;
|
offset += 4;
|
||||||
|
|
||||||
|
const propByStr = new Map();
|
||||||
const strByProp = new Array(propCount).fill(0);
|
const strByProp = new Array(propCount).fill(0);
|
||||||
for (let i = 0; i < propCount; i++) {
|
for (let i = 0; i < propCount; i++) {
|
||||||
const v = readU32(buf, offset);
|
const v = readU32(buf, offset);
|
||||||
offset += 4;
|
offset += 4;
|
||||||
|
|
||||||
strByProp[i] = v;
|
strByProp[i] = v;
|
||||||
|
propByStr.set(strTable.get(v), i);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {AstContext} */
|
/** @type {AstContext} */
|
||||||
|
@ -436,6 +454,7 @@ function createAstContext(buf) {
|
||||||
strByProp,
|
strByProp,
|
||||||
strByType,
|
strByType,
|
||||||
typeByStr,
|
typeByStr,
|
||||||
|
propByStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
setNodeGetters(ctx);
|
setNodeGetters(ctx);
|
||||||
|
@ -445,6 +464,11 @@ function createAstContext(buf) {
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} _node
|
||||||
|
*/
|
||||||
|
const NOOP = (_node) => {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} fileName
|
* @param {string} fileName
|
||||||
* @param {Uint8Array} serializedAst
|
* @param {Uint8Array} serializedAst
|
||||||
|
@ -453,8 +477,9 @@ export function runPluginsForFile(fileName, serializedAst) {
|
||||||
const ctx = createAstContext(serializedAst);
|
const ctx = createAstContext(serializedAst);
|
||||||
// console.log(JSON.stringify(ctx, null, 2));
|
// console.log(JSON.stringify(ctx, null, 2));
|
||||||
|
|
||||||
/** @type {Record<string, (node: any) => void>} */
|
/** @type {Map<string, { enter: VisitorFn, exit: VisitorFn}>} */
|
||||||
const mergedVisitor = {};
|
const bySelector = new Map();
|
||||||
|
|
||||||
const destroyFns = [];
|
const destroyFns = [];
|
||||||
|
|
||||||
// console.log(state);
|
// console.log(state);
|
||||||
|
@ -472,23 +497,48 @@ export function runPluginsForFile(fileName, serializedAst) {
|
||||||
|
|
||||||
// console.log({ visitor });
|
// console.log({ visitor });
|
||||||
|
|
||||||
for (const name in visitor) {
|
for (let key in visitor) {
|
||||||
const prev = mergedVisitor[name];
|
const fn = visitor[key];
|
||||||
mergedVisitor[name] = (node) => {
|
|
||||||
if (typeof prev === "function") {
|
|
||||||
prev(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
let isExit = false;
|
||||||
visitor[name](node);
|
if (key.endsWith(":exit")) {
|
||||||
} catch (err) {
|
isExit = true;
|
||||||
// FIXME: console here doesn't support error cause
|
key = key.slice(0, -":exit".length);
|
||||||
console.log(err);
|
}
|
||||||
throw new Error(`Visitor "${name}" of plugin "${id}" errored`, {
|
const selectors = splitSelectors(key);
|
||||||
cause: err,
|
|
||||||
});
|
for (let j = 0; j < selectors.length; j++) {
|
||||||
|
const fnKey = selectors[j] + (isExit ? ":exit" : "");
|
||||||
|
let info = bySelector.get(fnKey);
|
||||||
|
if (info === undefined) {
|
||||||
|
info = { enter: NOOP, exit: NOOP };
|
||||||
|
bySelector.set(fnKey, info);
|
||||||
}
|
}
|
||||||
};
|
const prevFn = isExit ? info.exit : info.enter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {*} node
|
||||||
|
*/
|
||||||
|
const wrapped = (node) => {
|
||||||
|
prevFn(node);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fn(node);
|
||||||
|
} catch (err) {
|
||||||
|
// FIXME: console here doesn't support error cause
|
||||||
|
console.log(err);
|
||||||
|
throw new Error(`Visitor "${name}" of plugin "${id}" errored`, {
|
||||||
|
cause: err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isExit) {
|
||||||
|
info.exit = wrapped;
|
||||||
|
} else {
|
||||||
|
info.enter = wrapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof rule.destroy === "function") {
|
if (typeof rule.destroy === "function") {
|
||||||
|
@ -504,10 +554,36 @@ export function runPluginsForFile(fileName, serializedAst) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create selectors
|
||||||
|
/** @type {TransformerFn} */
|
||||||
|
const toElem = (str) => {
|
||||||
|
const id = ctx.typeByStr.get(str);
|
||||||
|
if (id === undefined) throw new Error(`Unknown elem: ${str}`);
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
/** @type {TransformerFn} */
|
||||||
|
const toAttr = (str) => {
|
||||||
|
const id = ctx.typeByStr.get(str);
|
||||||
|
if (id === undefined) throw new Error(`Unknown elem: ${str}`);
|
||||||
|
return id;
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {CompiledVisitor[]} */
|
||||||
|
const visitors = [];
|
||||||
|
for (const [sel, info] of bySelector.entries()) {
|
||||||
|
const compiled = parseSelector(sel, toElem, toAttr);
|
||||||
|
const matcher = compileSelector(compiled);
|
||||||
|
|
||||||
|
visitors.push({
|
||||||
|
info,
|
||||||
|
matcher,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Traverse ast with all visitors at the same time to avoid traversing
|
// Traverse ast with all visitors at the same time to avoid traversing
|
||||||
// multiple times.
|
// multiple times.
|
||||||
try {
|
try {
|
||||||
traverse(ctx, mergedVisitor);
|
traverse(ctx, visitors, ctx.rootId);
|
||||||
} finally {
|
} finally {
|
||||||
ctx.nodes.clear();
|
ctx.nodes.clear();
|
||||||
|
|
||||||
|
@ -520,76 +596,78 @@ export function runPluginsForFile(fileName, serializedAst) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AstContext} ctx
|
* @param {AstContext} ctx
|
||||||
* @param {*} visitor
|
* @param {CompiledVisitor[]} visitors
|
||||||
* @returns {void}
|
|
||||||
*/
|
|
||||||
function traverse(ctx, visitor) {
|
|
||||||
const visitTypes = new Map();
|
|
||||||
|
|
||||||
// TODO: create visiting types
|
|
||||||
for (const name in visitor) {
|
|
||||||
const id = ctx.typeByStr.get(name);
|
|
||||||
if (id === undefined) continue;
|
|
||||||
visitTypes.set(id, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("merged visitor", visitor);
|
|
||||||
console.log("visiting types", visitTypes);
|
|
||||||
|
|
||||||
traverseInner(ctx, visitTypes, visitor, ctx.rootId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {AstContext} ctx
|
|
||||||
* @param {Map<number, string>} visitTypes
|
|
||||||
* @param {Record<string, (x: any) => void>} visitor
|
|
||||||
* @param {number} offset
|
* @param {number} offset
|
||||||
*/
|
*/
|
||||||
function traverseInner(ctx, visitTypes, visitor, offset) {
|
function traverse(ctx, visitors, offset) {
|
||||||
// console.log("traversing offset", offset);
|
// console.log("traversing offset", offset);
|
||||||
|
|
||||||
// Empty id
|
// Empty id
|
||||||
if (offset === 0) return;
|
if (offset === 0) return;
|
||||||
const { buf } = ctx;
|
const { buf } = ctx;
|
||||||
const type = buf[offset];
|
|
||||||
|
|
||||||
const name = visitTypes.get(type);
|
/** @type {VisitorFn[] | null} */
|
||||||
if (name !== undefined) {
|
let exits = null;
|
||||||
// console.log("--> invoking visitor");
|
|
||||||
const node = new Node(ctx, offset);
|
for (let i = 0; i < visitors.length; i++) {
|
||||||
visitor[name](node);
|
const v = visitors[i];
|
||||||
|
|
||||||
|
if (v.info.enter === NOOP) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: add matcher context methods
|
||||||
|
if (v.matcher(ctx, offset)) {
|
||||||
|
const node = /** @type {*} */ (getNode(ctx, offset));
|
||||||
|
v.info.enter(node);
|
||||||
|
|
||||||
|
if (exits === null) {
|
||||||
|
exits = [v.info.exit];
|
||||||
|
} else {
|
||||||
|
exits.push(v.info.exit);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// type + parentId + SpanLo + SpanHi
|
try {
|
||||||
offset += 1 + 4 + 4 + 4;
|
// type + parentId + SpanLo + SpanHi
|
||||||
|
offset += 1 + 4 + 4 + 4;
|
||||||
|
|
||||||
const propCount = buf[offset];
|
const propCount = buf[offset];
|
||||||
offset += 1;
|
offset += 1;
|
||||||
// console.log({ propCount });
|
// console.log({ propCount });
|
||||||
|
|
||||||
for (let i = 0; i < propCount; i++) {
|
for (let i = 0; i < propCount; i++) {
|
||||||
const kind = buf[offset + 1];
|
const kind = buf[offset + 1];
|
||||||
offset += 2; // propId + propFlags
|
offset += 2; // propId + propFlags
|
||||||
|
|
||||||
if (kind === PropFlags.Ref) {
|
if (kind === PropFlags.Ref) {
|
||||||
const next = readU32(buf, offset);
|
const next = readU32(buf, offset);
|
||||||
offset += 4;
|
|
||||||
traverseInner(ctx, visitTypes, visitor, next);
|
|
||||||
} else if (kind === PropFlags.RefArr) {
|
|
||||||
const len = readU32(buf, offset);
|
|
||||||
offset += 4;
|
|
||||||
|
|
||||||
for (let j = 0; j < len; j++) {
|
|
||||||
const chiild = readU32(buf, offset);
|
|
||||||
offset += 4;
|
offset += 4;
|
||||||
traverseInner(ctx, visitTypes, visitor, chiild);
|
traverse(ctx, visitors, next);
|
||||||
|
} else if (kind === PropFlags.RefArr) {
|
||||||
|
const len = readU32(buf, offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
for (let j = 0; j < len; j++) {
|
||||||
|
const child = readU32(buf, offset);
|
||||||
|
offset += 4;
|
||||||
|
traverse(ctx, visitors, child);
|
||||||
|
}
|
||||||
|
} else if (kind === PropFlags.String) {
|
||||||
|
offset += 4;
|
||||||
|
} else if (kind === PropFlags.Bool) {
|
||||||
|
offset += 1;
|
||||||
|
} else if (kind === PropFlags.Null || kind === PropFlags.Undefined) {
|
||||||
|
// No value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (exits !== null) {
|
||||||
|
for (let i = 0; i < exits.length; i++) {
|
||||||
|
const node = /** @type {*} */ (getNode(ctx, offset));
|
||||||
|
exits[i](node);
|
||||||
}
|
}
|
||||||
} else if (kind === PropFlags.String) {
|
|
||||||
offset += 4;
|
|
||||||
} else if (kind === PropFlags.Bool) {
|
|
||||||
offset += 1;
|
|
||||||
} else if (kind === PropFlags.Null || kind === PropFlags.Undefined) {
|
|
||||||
// No value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
/** @typedef {import("./40_lint_types.d.ts").SelectorParseCtx} SelectorParseCtx */
|
/** @typedef {import("./40_lint_types.d.ts").SelectorParseCtx} SelectorParseCtx */
|
||||||
/** @typedef {import("./40_lint_types.d.ts").NextFn} NextFn */
|
/** @typedef {import("./40_lint_types.d.ts").NextFn} NextFn */
|
||||||
/** @typedef {import("./40_lint_types.d.ts").MatcherFn} MatcherFn */
|
/** @typedef {import("./40_lint_types.d.ts").MatcherFn} MatcherFn */
|
||||||
/** @typedef {import("./40_lint_types.d.ts").Transformer} Transformer */
|
/** @typedef {import("./40_lint_types.d.ts").TransformFn} Transformer */
|
||||||
|
|
||||||
const Char = {
|
const Char = {
|
||||||
Tab: 9,
|
Tab: 9,
|
||||||
|
@ -377,6 +377,42 @@ export const PSEUDO_NOT = 7;
|
||||||
export const PSEUDO_FIRST_CHILD = 8;
|
export const PSEUDO_FIRST_CHILD = 8;
|
||||||
export const PSEUDO_LAST_CHILD = 9;
|
export const PSEUDO_LAST_CHILD = 9;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse out all unique selectors of a selector list.
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
export function splitSelectors(input) {
|
||||||
|
/** @type {string[]} */
|
||||||
|
const out = [];
|
||||||
|
|
||||||
|
let last = 0;
|
||||||
|
let depth = 0;
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
const ch = input.charCodeAt(i);
|
||||||
|
switch (ch) {
|
||||||
|
case Char.BraceOpen:
|
||||||
|
depth++;
|
||||||
|
break;
|
||||||
|
case Char.BraceClose:
|
||||||
|
depth--;
|
||||||
|
break;
|
||||||
|
case Char.Comma:
|
||||||
|
if (depth === 0) {
|
||||||
|
out.push(input.slice(last, i).trim());
|
||||||
|
last = i + 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last < input.length - 1) {
|
||||||
|
out.push(input.slice(last).trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {string} input
|
||||||
* @param {Transformer} toElem
|
* @param {Transformer} toElem
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { splitSelectors } from "./40_lint_selector.js";
|
||||||
import {
|
import {
|
||||||
compileSelector,
|
compileSelector,
|
||||||
MatchCtx,
|
MatchCtx,
|
||||||
|
@ -469,3 +470,13 @@ Deno.test("select child: A:nth-child", () => {
|
||||||
ast.children![1],
|
ast.children![1],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Deno.test("splitSelectors", () => {
|
||||||
|
expect(splitSelectors("foo")).toEqual(["foo"]);
|
||||||
|
expect(splitSelectors("foo, bar")).toEqual(["foo", "bar"]);
|
||||||
|
expect(splitSelectors("foo:f(bar, baz)")).toEqual(["foo:f(bar, baz)"]);
|
||||||
|
expect(splitSelectors("foo:f(bar, baz), foobar")).toEqual([
|
||||||
|
"foo:f(bar, baz)",
|
||||||
|
"foobar",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
8
cli/js/40_lint_types.d.ts
vendored
8
cli/js/40_lint_types.d.ts
vendored
|
@ -102,6 +102,12 @@ export interface MatchCtx {
|
||||||
|
|
||||||
export type NextFn = (ctx: MatchCtx, id: number) => boolean;
|
export type NextFn = (ctx: MatchCtx, id: number) => boolean;
|
||||||
export type MatcherFn = (ctx: MatchCtx, id: number) => boolean;
|
export type MatcherFn = (ctx: MatchCtx, id: number) => boolean;
|
||||||
export type Transformer = (value: string) => number;
|
export type TransformFn = (value: string) => number;
|
||||||
|
export type VisitorFn = (node: Deno.AstNode) => void;
|
||||||
|
|
||||||
|
export interface CompiledVisitor {
|
||||||
|
matcher: MatcherFn;
|
||||||
|
info: { enter: VisitorFn; exit: VisitorFn };
|
||||||
|
}
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|
Loading…
Add table
Reference in a new issue