0
0
Fork 0
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:
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 // @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
} }
} }
} }

View file

@ -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

View file

@ -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",
]);
});

View file

@ -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 {};