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:
parent
132db5604f
commit
d9fd959820
4 changed files with 210 additions and 79 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
]);
|
||||
});
|
||||
|
|
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 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 {};
|
||||
|
|
Loading…
Add table
Reference in a new issue