From 6e70ba2c77e9f1d1c07064c7111f8c04615fb530 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Tue, 17 Dec 2024 12:04:12 +0100 Subject: [PATCH] get selector integration working --- cli/js/40_lint.js | 203 ++++++++++++++++++++++++++--------- cli/js/40_lint_selector.js | 16 ++- cli/js/40_lint_types.d.ts | 24 +++-- cli/tools/lint/swc.rs | 11 +- cli/tools/lint/ts_estree.rs | 6 +- cli/tsc/dts/lib.deno.ns.d.ts | 3 + 6 files changed, 194 insertions(+), 69 deletions(-) diff --git a/cli/js/40_lint.js b/cli/js/40_lint.js index dbf18489e0..463e8f2e3b 100644 --- a/cli/js/40_lint.js +++ b/cli/js/40_lint.js @@ -31,39 +31,13 @@ const PropFlags = { Undefined: 5, }; -/** - * @typedef {{ - * buf: Uint8Array, - * strTable: Map, - * strTableOffset: number, - * rootId: number, - * nodes: Map, - * strByType: number[], - * strByProp: number[] - * typeByStr: Map, - * propByStr: Map, - * }} AstContext - */ - -/** - * @typedef {{ - * plugins: Deno.LintPlugin[], - * installedPlugins: Set, - * }} 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 - */ +/** @typedef {import("./40_lint_types.d.ts").AstContext} AstContext */ +/** @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 */ +/** @typedef {import("./40_lint_types.d.ts").LintState} LintState */ +/** @typedef {import("./40_lint_types.d.ts").MatchContext} MatchContext */ /** @type {LintState} */ const state = { @@ -201,7 +175,7 @@ function readValue(ctx, offset, search) { const type = buf[offset]; if (search === AST_PROP_TYPE) { - return getString(ctx, ctx.strByType[type]); + return getString(ctx.strTable, ctx.strByType[type]); } else if (search === AST_PROP_RANGE) { const start = readU32(buf, offset + 1 + 4); const end = readU32(buf, offset + 1 + 4 + 4); @@ -233,7 +207,7 @@ function readValue(ctx, offset, search) { return buf[offset] === 1; } else if (kind === PropFlags.String) { const v = readU32(buf, offset); - return getString(ctx, v); + return getString(ctx.strTable, v); } else if (kind === PropFlags.Null) { return null; } else if (kind === PropFlags.Undefined) { @@ -264,7 +238,7 @@ function toJsValue(ctx, offset) { for (let i = 0; i < count; i++) { const prop = buf[offset++]; const kind = buf[offset++]; - const name = getString(ctx, ctx.strByProp[prop]); + const name = getString(ctx.strTable, ctx.strByProp[prop]); if (kind === PropFlags.Ref) { const v = readU32(buf, offset); @@ -287,7 +261,7 @@ function toJsValue(ctx, offset) { } else if (kind === PropFlags.String) { const v = readU32(buf, offset); offset += 4; - node[name] = getString(ctx, v); + node[name] = getString(ctx.strTable, v); } else if (kind === PropFlags.Null) { node[name] = null; } else if (kind === PropFlags.Undefined) { @@ -339,7 +313,7 @@ function setNodeGetters(ctx) { if (id === 0 || appliedGetters.has(i)) continue; appliedGetters.add(i); - const name = getString(ctx, id); + const name = getString(ctx.strTable, id); Object.defineProperty(Node.prototype, name, { get() { @@ -362,12 +336,12 @@ function readU32(buf, i) { } /** - * @param {AstContext} ctx + * @param {AstContext["strTable"]} strTable * @param {number} id * @returns {string} */ -function getString(ctx, id) { - const name = ctx.strTable.get(id); +function getString(strTable, id) { + const name = strTable.get(id); if (name === undefined) { throw new Error(`Missing string id: ${id}`); } @@ -375,6 +349,128 @@ function getString(ctx, id) { return name; } +/** @implements {MatchContext} */ +class MatchCtx { + /** + * @param {AstContext["buf"]} buf + * @param {AstContext["strTable"]} strTable + */ + constructor(buf, strTable) { + this.buf = buf; + this.strTable = strTable; + } + + /** + * @param {number} offset + * @returns {number} + */ + getParent(offset) { + return readU32(this.buf, offset + 1); + } + + /** + * @param {number} offset + * @returns {number} + */ + getType(offset) { + return this.buf[offset]; + } + + /** + * @param {number} offset + * @param {number[]} propIds + * @param {number} idx + * @returns {unknown} + */ + getAttrPathValue(offset, propIds, idx) { + const { buf } = this; + + offset = findPropOffset(buf, offset, propIds[idx]); + // console.log("attr value", offset, propIds, idx); + if (offset === -1) return undefined; + const prop = buf[offset++]; + const kind = buf[offset++]; + + if (kind === PropFlags.Ref) { + const value = readU32(buf, offset); + // Checks need to end with a value, not a node + if (idx === propIds.length - 1) return undefined; + return this.getAttrPathValue(value, propIds, idx + 1); + } else if (kind === PropFlags.RefArr) { + // FIXME + const count = readU32(buf, offset); + offset += 4; + } + + // Cannot traverse into primitives further + if (idx < propIds.length - 1) return undefined; + + if (kind === PropFlags.String) { + const s = readU32(buf, offset); + return getString(this.strTable, s); + } else if (kind === PropFlags.Bool) { + return buf[offset] === 1; + } else if (kind === PropFlags.Null) { + return null; + } else if (kind === PropFlags.Undefined) { + return undefined; + } + + return undefined; + } + + /** + * @param {number} offset + * @param {number[]} propIds + * @param {number} idx + * @returns {boolean} + */ + hasAttrPath(offset, propIds, idx) { + const { buf } = this; + + offset = findPropOffset(buf, offset, propIds[idx]); + // console.log("attr path", offset, propIds, idx); + if (offset === -1) return false; + if (idx === propIds.length - 1) return true; + + const prop = buf[offset++]; + const kind = buf[offset++]; + if (kind === PropFlags.Ref) { + const value = readU32(buf, offset); + return this.hasAttrPath(value, propIds, idx + 1); + } else if (kind === PropFlags.RefArr) { + const count = readU32(buf, offset); + offset += 4; + + // FIXME + } + + // Primitives cannot be traversed further. This means we + // didn't found the attribute. + if (idx < propIds.length - 1) return false; + + return true; + } + + getFirstChild() { + // FIXME + return 0; + } + + getLastChild() { + // FIXME + return 0; + } + getSiblingBefore() { + // FIXME + return 0; + } + getSiblings() { + // FIXME + return []; + } +} + /** * @param {Uint8Array} buf * @param {AstContext} buf @@ -455,6 +551,7 @@ function createAstContext(buf) { strByType, typeByStr, propByStr, + matcher: new MatchCtx(buf, strTable), }; setNodeGetters(ctx); @@ -499,6 +596,7 @@ export function runPluginsForFile(fileName, serializedAst) { for (let key in visitor) { const fn = visitor[key]; + if (fn === undefined) continue; let isExit = false; if (key.endsWith(":exit")) { @@ -563,7 +661,7 @@ export function runPluginsForFile(fileName, serializedAst) { }; /** @type {TransformerFn} */ const toAttr = (str) => { - const id = ctx.typeByStr.get(str); + const id = ctx.propByStr.get(str); if (id === undefined) throw new Error(`Unknown elem: ${str}`); return id; }; @@ -571,8 +669,10 @@ export function runPluginsForFile(fileName, serializedAst) { /** @type {CompiledVisitor[]} */ const visitors = []; for (const [sel, info] of bySelector.entries()) { - const compiled = parseSelector(sel, toElem, toAttr); - const matcher = compileSelector(compiled); + // Selectors are already split here. + // TODO: Avoid array allocation (not sure if that matters) + const parsed = parseSelector(sel, toElem, toAttr)[0]; + const matcher = compileSelector(parsed); visitors.push({ info, @@ -616,8 +716,7 @@ function traverse(ctx, visitors, offset) { continue; } - // FIXME: add matcher context methods - if (v.matcher(ctx, offset)) { + if (v.matcher(ctx.matcher, offset)) { const node = /** @type {*} */ (getNode(ctx, offset)); v.info.enter(node); @@ -684,14 +783,14 @@ function _dump(ctx) { for (let i = 0; i < strByType.length; i++) { const v = strByType[i]; // @ts-ignore dump fn - if (v > 0) console.log(" > type:", i, getString(ctx, v), v); + if (v > 0) console.log(" > type:", i, getString(ctx.strTable, v), v); } // @ts-ignore dump fn console.log(); for (let i = 0; i < strByProp.length; i++) { const v = strByProp[i]; // @ts-ignore dump fn - if (v > 0) console.log(" > prop:", i, getString(ctx, v), v); + if (v > 0) console.log(" > prop:", i, getString(ctx.strTable, v), v); } // @ts-ignore dump fn console.log(); @@ -700,7 +799,7 @@ function _dump(ctx) { while (offset < strTableOffset) { const type = buf[offset]; - const name = getString(ctx, ctx.strByType[type]); + const name = getString(ctx.strTable, ctx.strByType[type]); // @ts-ignore dump fn console.log(`${name}, offset: ${offset}, type: ${type}`); offset += 1; @@ -724,7 +823,7 @@ function _dump(ctx) { for (let i = 0; i < count; i++) { const prop = buf[offset++]; const kind = buf[offset++]; - const name = getString(ctx, ctx.strByProp[prop]); + const name = getString(ctx.strTable, ctx.strByProp[prop]); let kindName = "unknown"; for (const k in PropFlags) { @@ -760,7 +859,9 @@ function _dump(ctx) { const v = readU32(buf, offset); offset += 4; // @ts-ignore dump fn - console.log(` ${name}: ${getString(ctx, v)} (${kindName}, ${prop})`); + console.log( + ` ${name}: ${getString(ctx.strTable, v)} (${kindName}, ${prop})`, + ); } else if (kind === PropFlags.Null) { // @ts-ignore dump fn console.log(` ${name}: null (${kindName}, ${prop})`); diff --git a/cli/js/40_lint_selector.js b/cli/js/40_lint_selector.js index e9106ca5b8..e44529b29d 100644 --- a/cli/js/40_lint_selector.js +++ b/cli/js/40_lint_selector.js @@ -4,7 +4,7 @@ /** @typedef {import("./40_lint_types.d.ts").LintState} LintState */ /** @typedef {import("./40_lint_types.d.ts").AstContext} AstContext */ -/** @typedef {import("./40_lint_types.d.ts").MatchCtx} MatchCtx */ +/** @typedef {import("./40_lint_types.d.ts").MatchContext} MatchCtx */ /** @typedef {import("./40_lint_types.d.ts").AttrExists} AttrExists */ /** @typedef {import("./40_lint_types.d.ts").AttrBin} AttrBin */ /** @typedef {import("./40_lint_types.d.ts").AttrSelector} AttrSelector */ @@ -685,7 +685,9 @@ function popSelector(result, stack) { } } -const TRUE_FN = () => true; +const TRUE_FN = () => { + return true; +}; /** * @param {Selector} selector @@ -742,6 +744,10 @@ export function compileSelector(selector) { case PSEUDO_NOT: fn = matchNot(node.selectors, fn); break; + default: + // @ts-ignore error handling + console.log(node); + throw new Error(`Unknown selector node`); } } @@ -943,7 +949,7 @@ function matchElem(part, next) { */ function matchAttrExists(attr, next) { return (ctx, id) => { - return ctx.hasAttrPath(id, attr.prop) ? next(ctx, id) : false; + return ctx.hasAttrPath(id, attr.prop, 0) ? next(ctx, id) : false; }; } @@ -954,8 +960,8 @@ function matchAttrExists(attr, next) { */ function matchAttrBin(attr, next) { return (ctx, id) => { - if (!ctx.hasAttrPath(id, attr.prop)) return false; - const value = ctx.getAttrPathValue(id, attr.prop); + if (!ctx.hasAttrPath(id, attr.prop, 0)) return false; + const value = ctx.getAttrPathValue(id, attr.prop, 0); if (!matchAttrValue(attr, value)) return false; return next(ctx, id); }; diff --git a/cli/js/40_lint_types.d.ts b/cli/js/40_lint_types.d.ts index 03d0375617..32bf8255a1 100644 --- a/cli/js/40_lint_types.d.ts +++ b/cli/js/40_lint_types.d.ts @@ -1,11 +1,21 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +export interface NodeFacade { + readonly type: string; + readonly range: [number, number]; +} + export interface AstContext { buf: Uint8Array; strTable: Map; - idTable: number[]; + strTableOffset: number; rootId: number; - stack: number[]; + nodes: Map; + strByType: number[]; + strByProp: number[]; + typeByStr: Map; + propByStr: Map; + matcher: MatchContext; } export interface LintState { @@ -89,19 +99,19 @@ export interface ILexer { token: SelToken; } -export interface MatchCtx { +export interface MatchContext { getFirstChild(id: number): number; getLastChild(id: number): number; getSiblingBefore(parentId: number, sib: number): number; getSiblings(id: number): number[]; getParent(id: number): number; getType(id: number): number; - hasAttrPath(id: number, propIds: number[]): boolean; - getAttrPathValue(id: number, propIds: number[]): unknown; + hasAttrPath(id: number, propIds: number[], idx: number): boolean; + getAttrPathValue(id: number, propIds: number[], idx: number): unknown; } -export type NextFn = (ctx: MatchCtx, id: number) => boolean; -export type MatcherFn = (ctx: MatchCtx, id: number) => boolean; +export type NextFn = (ctx: MatchContext, id: number) => boolean; +export type MatcherFn = (ctx: MatchContext, id: number) => boolean; export type TransformFn = (value: string) => number; export type VisitorFn = (node: Deno.AstNode) => void; diff --git a/cli/tools/lint/swc.rs b/cli/tools/lint/swc.rs index 7e5d73e19a..65b8e0bb1b 100644 --- a/cli/tools/lint/swc.rs +++ b/cli/tools/lint/swc.rs @@ -169,7 +169,7 @@ fn serialize_stmt( ) -> NodeRef { match stmt { Stmt::Block(node) => { - let pos = ctx.header(AstNode::Block, parent, &node.span, 1); + let pos = ctx.header(AstNode::BlockStatement, parent, &node.span, 1); let body_pos = ctx.ref_vec_field(AstProp::Body, node.stmts.len()); let children = node @@ -1437,7 +1437,12 @@ fn serialize_decl( id } Decl::Fn(node) => { - let pos = ctx.header(AstNode::Fn, parent, &node.function.span, 8); + let pos = ctx.header( + AstNode::FunctionDeclaration, + parent, + &node.function.span, + 8, + ); let declare_pos = ctx.ref_field(AstProp::Declare); let async_pos = ctx.ref_field(AstProp::Async); let gen_pos = ctx.ref_field(AstProp::Generator); @@ -1479,7 +1484,7 @@ fn serialize_decl( pos } Decl::Var(node) => { - let id = ctx.header(AstNode::Var, parent, &node.span, 3); + let id = ctx.header(AstNode::VariableDeclaration, parent, &node.span, 3); let declare_pos = ctx.bool_field(AstProp::Declare); let kind_pos = ctx.str_field(AstProp::Kind); let decls_pos = diff --git a/cli/tools/lint/ts_estree.rs b/cli/tools/lint/ts_estree.rs index 79ec1d4126..6bdf646b12 100644 --- a/cli/tools/lint/ts_estree.rs +++ b/cli/tools/lint/ts_estree.rs @@ -25,8 +25,8 @@ pub enum AstNode { // Decls ClassDeclaration, - Fn, - Var, + FunctionDeclaration, + VariableDeclaration, Using, TSInterface, TsTypeAlias, @@ -34,7 +34,7 @@ pub enum AstNode { TsModule, // Statements - Block, + BlockStatement, Empty, Debugger, With, diff --git a/cli/tsc/dts/lib.deno.ns.d.ts b/cli/tsc/dts/lib.deno.ns.d.ts index 1f531fd84d..cd3bd23381 100644 --- a/cli/tsc/dts/lib.deno.ns.d.ts +++ b/cli/tsc/dts/lib.deno.ns.d.ts @@ -7159,6 +7159,9 @@ declare namespace Deno { TSTypeAnnotation?(node: TSTypeAnnotation): void; TSTypeReference?(node: TSTypeReference): void; TSUnknownKeyword?(node: TSUnknownKeyword): void; + + // deno-lint-ignore no-explicit-any + [selector: string]: ((node: any) => void) | undefined; } export interface LintRule {