0
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-02-01 12:16:11 -05:00

integrate selctors part 1

This commit is contained in:
Marvin Hagemeister 2024-12-17 01:20:21 +01:00
parent 132db5604f
commit d9fd959820
4 changed files with 210 additions and 79 deletions

View file

@ -3,9 +3,11 @@
// @ts-check
import { core } from "ext:core/mod.js";
import { compileSelector, parseSelector } from "ext:cli/lint/selector.js";
console.log({ compileSelector, foo: "foo" });
import {
compileSelector,
parseSelector,
splitSelectors,
} from "ext:cli/lint/selector.js";
const {
op_lint_get_rule,
@ -37,8 +39,9 @@ const PropFlags = {
* rootId: number,
* nodes: Map<number, Node>,
* strByType: number[],
* typeByStr: Map<string, number>,
* strByProp: number[]
* typeByStr: Map<string, number>,
* propByStr: Map<string, number>,
* }} AstContext
*/
@ -49,6 +52,19 @@ const PropFlags = {
* }} 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} */
const state = {
plugins: [],
@ -418,12 +434,14 @@ function createAstContext(buf) {
const propCount = readU32(buf, offset);
offset += 4;
const propByStr = new Map();
const strByProp = new Array(propCount).fill(0);
for (let i = 0; i < propCount; i++) {
const v = readU32(buf, offset);
offset += 4;
strByProp[i] = v;
propByStr.set(strTable.get(v), i);
}
/** @type {AstContext} */
@ -436,6 +454,7 @@ function createAstContext(buf) {
strByProp,
strByType,
typeByStr,
propByStr,
};
setNodeGetters(ctx);
@ -445,6 +464,11 @@ function createAstContext(buf) {
return ctx;
}
/**
* @param {*} _node
*/
const NOOP = (_node) => {};
/**
* @param {string} fileName
* @param {Uint8Array} serializedAst
@ -453,8 +477,9 @@ export function runPluginsForFile(fileName, serializedAst) {
const ctx = createAstContext(serializedAst);
// console.log(JSON.stringify(ctx, null, 2));
/** @type {Record<string, (node: any) => void>} */
const mergedVisitor = {};
/** @type {Map<string, { enter: VisitorFn, exit: VisitorFn}>} */
const bySelector = new Map();
const destroyFns = [];
// console.log(state);
@ -472,23 +497,48 @@ export function runPluginsForFile(fileName, serializedAst) {
// console.log({ visitor });
for (const name in visitor) {
const prev = mergedVisitor[name];
mergedVisitor[name] = (node) => {
if (typeof prev === "function") {
prev(node);
}
for (let key in visitor) {
const fn = visitor[key];
try {
visitor[name](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,
});
let isExit = false;
if (key.endsWith(":exit")) {
isExit = true;
key = key.slice(0, -":exit".length);
}
const selectors = splitSelectors(key);
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") {
@ -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
// multiple times.
try {
traverse(ctx, mergedVisitor);
traverse(ctx, visitors, ctx.rootId);
} finally {
ctx.nodes.clear();
@ -520,76 +596,78 @@ export function runPluginsForFile(fileName, serializedAst) {
/**
* @param {AstContext} ctx
* @param {*} visitor
* @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 {CompiledVisitor[]} visitors
* @param {number} offset
*/
function traverseInner(ctx, visitTypes, visitor, offset) {
function traverse(ctx, visitors, offset) {
// console.log("traversing offset", offset);
// Empty id
if (offset === 0) return;
const { buf } = ctx;
const type = buf[offset];
const name = visitTypes.get(type);
if (name !== undefined) {
// console.log("--> invoking visitor");
const node = new Node(ctx, offset);
visitor[name](node);
/** @type {VisitorFn[] | null} */
let exits = null;
for (let i = 0; i < visitors.length; i++) {
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
offset += 1 + 4 + 4 + 4;
try {
// type + parentId + SpanLo + SpanHi
offset += 1 + 4 + 4 + 4;
const propCount = buf[offset];
offset += 1;
// console.log({ propCount });
const propCount = buf[offset];
offset += 1;
// console.log({ propCount });
for (let i = 0; i < propCount; i++) {
const kind = buf[offset + 1];
offset += 2; // propId + propFlags
for (let i = 0; i < propCount; i++) {
const kind = buf[offset + 1];
offset += 2; // propId + propFlags
if (kind === PropFlags.Ref) {
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);
if (kind === PropFlags.Ref) {
const next = readU32(buf, offset);
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
}
}
}

View file

@ -17,7 +17,7 @@
/** @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").MatcherFn} MatcherFn */
/** @typedef {import("./40_lint_types.d.ts").Transformer} Transformer */
/** @typedef {import("./40_lint_types.d.ts").TransformFn} Transformer */
const Char = {
Tab: 9,
@ -377,6 +377,42 @@ export const PSEUDO_NOT = 7;
export const PSEUDO_FIRST_CHILD = 8;
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 {Transformer} toElem

View file

@ -1,3 +1,4 @@
import { splitSelectors } from "./40_lint_selector.js";
import {
compileSelector,
MatchCtx,
@ -469,3 +470,13 @@ Deno.test("select child: A:nth-child", () => {
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",
]);
});

View file

@ -102,6 +102,12 @@ export interface MatchCtx {
export type NextFn = (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 {};