// Copyright 2018-2025 the Deno authors. MIT license. // @ts-check import { compileSelector, parseSelector, splitSelectors, } from "ext:cli/40_lint_selector.js"; import { core, internals } from "ext:core/mod.js"; const { op_lint_create_serialized_ast, } = core.ops; // Keep these in sync with Rust const AST_IDX_INVALID = 0; const AST_GROUP_TYPE = 1; /// /// /// /// /// const NODE_SIZE = 1 + 4 + 4 + 4 + 4; const PROP_OFFSET = 1; const CHILD_OFFSET = 1 + 4; const NEXT_OFFSET = 1 + 4 + 4; const PARENT_OFFSET = 1 + 4 + 4 + 4; // Span size in buffer: u32 + u32 const SPAN_SIZE = 4 + 4; // Keep in sync with Rust // These types are expected to be present on every node. Note that this // isn't set in stone. We could revise this at a future point. const AST_PROP_TYPE = 1; const AST_PROP_PARENT = 2; const AST_PROP_RANGE = 3; const AST_PROP_LENGTH = 4; // Keep in sync with Rust // Each node property is tagged with this enum to denote // what kind of value it holds. /** @enum {number} */ const PropFlags = { /** This is an offset to another node */ Ref: 0, /** This is an array of offsets to other nodes (like children of a BlockStatement) */ RefArr: 1, /** * This is a string id. The actual string needs to be looked up in * the string table that was included in the message. */ String: 2, /** * A numnber field. Numbers are represented as strings internally. */ Number: 3, /** This value is either 0 = false, or 1 = true */ Bool: 4, /** No value, it's null */ Null: 5, /** No value, it's undefined */ Undefined: 6, /** An object */ Obj: 7, Regex: 8, BigInt: 9, Array: 10, }; /** @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").CompiledVisitor} CompiledVisitor */ /** @typedef {import("./40_lint_types.d.ts").LintState} LintState */ /** @typedef {import("./40_lint_types.d.ts").RuleContext} RuleContext */ /** @typedef {import("./40_lint_types.d.ts").NodeFacade} NodeFacade */ /** @typedef {import("./40_lint_types.d.ts").LintPlugin} LintPlugin */ /** @typedef {import("./40_lint_types.d.ts").TransformFn} TransformFn */ /** @typedef {import("./40_lint_types.d.ts").MatchContext} MatchContext */ /** @typedef {import("./40_lint_types.d.ts").Node} Node */ /** @type {LintState} */ const state = { plugins: [], installedPlugins: new Set(), }; /** * Every rule gets their own instance of this class. This is the main * API lint rules interact with. * @implements {RuleContext} */ export class Context { id; fileName; /** * @param {string} id * @param {string} fileName */ constructor(id, fileName) { this.id = id; this.fileName = fileName; } } /** * @param {LintPlugin} plugin */ export function installPlugin(plugin) { if (typeof plugin !== "object") { throw new Error("Linter plugin must be an object"); } if (typeof plugin.name !== "string") { throw new Error("Linter plugin name must be a string"); } if (typeof plugin.rules !== "object") { throw new Error("Linter plugin rules must be an object"); } if (state.installedPlugins.has(plugin.name)) { throw new Error(`Linter plugin ${plugin.name} has already been registered`); } state.plugins.push(plugin); state.installedPlugins.add(plugin.name); } /** * @param {AstContext} ctx * @param {number} idx * @returns {FacadeNode | null} */ function getNode(ctx, idx) { if (idx === AST_IDX_INVALID) return null; const cached = ctx.nodes.get(idx); if (cached !== undefined) return /** @type {*} */ (cached); const node = new FacadeNode(ctx, idx); ctx.nodes.set(idx, /** @type {*} */ (node)); return /** @type {*} */ (node); } /** * Find the offset of a specific property of a specific node. This will * be used later a lot more for selectors. * @param {Uint8Array} buf * @param {number} search * @param {number} offset * @returns {number} */ function findPropOffset(buf, offset, search) { const count = buf[offset]; offset += 1; for (let i = 0; i < count; i++) { const maybe = offset; const prop = buf[offset++]; const kind = buf[offset++]; if (prop === search) return maybe; if (kind === PropFlags.Obj) { const len = readU32(buf, offset); offset += 4; // prop + kind + value offset += len * (1 + 1 + 4); } else { offset += 4; } } return -1; } const INTERNAL_CTX = Symbol("ctx"); const INTERNAL_IDX = Symbol("offset"); // This class is a facade for all materialized nodes. Instead of creating a // unique class per AST node, we have one class with getters for every // possible node property. This allows us to lazily materialize child node // only when they are needed. class FacadeNode { [INTERNAL_CTX]; [INTERNAL_IDX]; /** * @param {AstContext} ctx * @param {number} idx */ constructor(ctx, idx) { this[INTERNAL_CTX] = ctx; this[INTERNAL_IDX] = idx; } /** * Logging a class with only getters prints just the class name. This * makes debugging difficult because you don't see any of the properties. * For that reason we'll intercept inspection and serialize the node to * a plain JSON structure which can be logged and allows users to see all * properties and their values. * * This is only expected to be used during development of a rule. * @param {*} _ * @param {Deno.InspectOptions} options * @returns {string} */ [Symbol.for("Deno.customInspect")](_, options) { const json = nodeToJson(this[INTERNAL_CTX], this[INTERNAL_IDX]); return Deno.inspect(json, options); } [Symbol.for("Deno.lint.toJsValue")]() { return nodeToJson(this[INTERNAL_CTX], this[INTERNAL_IDX]); } } /** @type {Set} */ const appliedGetters = new Set(); /** * Add getters for all potential properties found in the message. * @param {AstContext} ctx */ function setNodeGetters(ctx) { if (appliedGetters.size === ctx.strByProp.length) return; for (let i = 0; i < ctx.strByProp.length; i++) { const id = ctx.strByProp[i]; if (id === 0 || appliedGetters.has(i)) continue; appliedGetters.add(i); const name = getString(ctx.strTable, id); Object.defineProperty(FacadeNode.prototype, name, { get() { return readValue( this[INTERNAL_CTX], this[INTERNAL_IDX], i, getNode, ); }, }); } } /** * @param {AstContext} ctx * @param {number} idx */ function nodeToJson(ctx, idx) { /** @type {Record} */ const node = { type: readValue(ctx, idx, AST_PROP_TYPE, nodeToJson), range: readValue(ctx, idx, AST_PROP_RANGE, nodeToJson), }; const { buf } = ctx; let offset = readPropOffset(ctx, idx); const count = buf[offset++]; for (let i = 0; i < count; i++) { const prop = buf[offset]; const _kind = buf[offset + 1]; const name = getString(ctx.strTable, ctx.strByProp[prop]); node[name] = readProperty(ctx, offset, nodeToJson); // prop + type + value offset += 1 + 1 + 4; } return node; } /** * @param {AstContext["buf"]} buf * @param {number} idx * @returns {number} */ function readType(buf, idx) { return buf[idx * NODE_SIZE]; } /** * @param {AstContext} ctx * @param {number} idx * @returns {Node["range"]} */ function readSpan(ctx, idx) { let offset = ctx.spansOffset + (idx * SPAN_SIZE); const start = readU32(ctx.buf, offset); offset += 4; const end = readU32(ctx.buf, offset); return [start, end]; } /** * @param {AstContext["buf"]} buf * @param {number} idx * @returns {number} */ function readRawPropOffset(buf, idx) { const offset = (idx * NODE_SIZE) + PROP_OFFSET; return readU32(buf, offset); } /** * @param {AstContext} ctx * @param {number} idx * @returns {number} */ function readPropOffset(ctx, idx) { return readRawPropOffset(ctx.buf, idx) + ctx.propsOffset; } /** * @param {AstContext["buf"]} buf * @param {number} idx * @returns {number} */ function readChild(buf, idx) { const offset = (idx * NODE_SIZE) + CHILD_OFFSET; return readU32(buf, offset); } /** * @param {AstContext["buf"]} buf * @param {number} idx * @returns {number} */ function readNext(buf, idx) { const offset = (idx * NODE_SIZE) + NEXT_OFFSET; return readU32(buf, offset); } /** * @param {AstContext["buf"]} buf * @param {number} idx * @returns {number} */ function readParent(buf, idx) { const offset = (idx * NODE_SIZE) + PARENT_OFFSET; return readU32(buf, offset); } /** * @param {AstContext["strTable"]} strTable * @param {number} strId * @returns {RegExp} */ function readRegex(strTable, strId) { const raw = getString(strTable, strId); const idx = raw.lastIndexOf("/"); const pattern = raw.slice(1, idx); const flags = idx < raw.length - 1 ? raw.slice(idx + 1) : undefined; return new RegExp(pattern, flags); } /** * @param {AstContext} ctx * @param {number} offset * @param {(ctx: AstContext, idx: number) => any} parseNode * @returns {Record} */ function readObject(ctx, offset, parseNode) { const { buf, strTable, strByProp } = ctx; /** @type {Record} */ const obj = {}; const count = readU32(buf, offset); offset += 4; for (let i = 0; i < count; i++) { const prop = buf[offset]; const name = getString(strTable, strByProp[prop]); obj[name] = readProperty(ctx, offset, parseNode); // name + kind + value offset += 1 + 1 + 4; } return obj; } /** * @param {AstContext} ctx * @param {number} offset * @param {(ctx: AstContext, idx: number) => any} parseNode * @returns {any} */ function readProperty(ctx, offset, parseNode) { const { buf } = ctx; // skip over name const _name = buf[offset++]; const kind = buf[offset++]; if (kind === PropFlags.Ref) { const value = readU32(buf, offset); return parseNode(ctx, value); } else if (kind === PropFlags.RefArr) { const groupId = readU32(buf, offset); const nodes = []; let next = readChild(buf, groupId); while (next > AST_IDX_INVALID) { nodes.push(parseNode(ctx, next)); next = readNext(buf, next); } return nodes; } else if (kind === PropFlags.Bool) { const v = readU32(buf, offset); return v === 1; } else if (kind === PropFlags.String) { const v = readU32(buf, offset); return getString(ctx.strTable, v); } else if (kind === PropFlags.Number) { const v = readU32(buf, offset); return Number(getString(ctx.strTable, v)); } else if (kind === PropFlags.BigInt) { const v = readU32(buf, offset); return BigInt(getString(ctx.strTable, v)); } else if (kind === PropFlags.Regex) { const v = readU32(buf, offset); return readRegex(ctx.strTable, v); } else if (kind === PropFlags.Null) { return null; } else if (kind === PropFlags.Undefined) { return undefined; } else if (kind === PropFlags.Obj) { const objOffset = readU32(buf, offset) + ctx.propsOffset; return readObject(ctx, objOffset, parseNode); } throw new Error(`Unknown prop kind: ${kind}`); } /** * Read a specific property from a node * @param {AstContext} ctx * @param {number} idx * @param {number} search * @param {(ctx: AstContext, idx: number) => any} parseNode * @returns {*} */ function readValue(ctx, idx, search, parseNode) { const { buf } = ctx; if (search === AST_PROP_TYPE) { const type = readType(buf, idx); return getString(ctx.strTable, ctx.strByType[type]); } else if (search === AST_PROP_RANGE) { return readSpan(ctx, idx); } else if (search === AST_PROP_PARENT) { const parent = readParent(buf, idx); return getNode(ctx, parent); } const propOffset = readPropOffset(ctx, idx); const offset = findPropOffset(ctx.buf, propOffset, search); if (offset === -1) return undefined; return readProperty(ctx, offset, parseNode); } const DECODER = new TextDecoder(); /** * TODO: Check if it's faster to use the `ArrayView` API instead. * @param {Uint8Array} buf * @param {number} i * @returns {number} */ function readU32(buf, i) { return (buf[i] << 24) + (buf[i + 1] << 16) + (buf[i + 2] << 8) + buf[i + 3]; } /** * Get a string by id and error if it wasn't found * @param {AstContext["strTable"]} strTable * @param {number} id * @returns {string} */ function getString(strTable, id) { const name = strTable.get(id); if (name === undefined) { throw new Error(`Missing string id: ${id}`); } return name; } /** @implements {MatchContext} */ class MatchCtx { /** * @param {AstContext} ctx */ constructor(ctx) { this.ctx = ctx; } /** * @param {number} idx * @returns {number} */ getParent(idx) { return readParent(this.ctx.buf, idx); } /** * @param {number} idx * @returns {number} */ getType(idx) { return readType(this.ctx.buf, idx); } /** * @param {number} idx - Node idx * @param {number[]} propIds * @param {number} propIdx * @returns {unknown} */ getAttrPathValue(idx, propIds, propIdx) { if (idx === 0) throw -1; const { buf, strTable, strByType } = this.ctx; const propId = propIds[propIdx]; switch (propId) { case AST_PROP_TYPE: { const type = readType(buf, idx); return getString(strTable, strByType[type]); } case AST_PROP_PARENT: case AST_PROP_RANGE: throw -1; } let offset = readPropOffset(this.ctx, idx); offset = findPropOffset(buf, offset, propId); if (offset === -1) throw -1; 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 (propIdx === propIds.length - 1) throw -1; return this.getAttrPathValue(value, propIds, propIdx + 1); } else if (kind === PropFlags.RefArr) { const arrIdx = readU32(buf, offset); offset += 4; let count = 0; let child = readChild(buf, arrIdx); while (child > AST_IDX_INVALID) { count++; child = readNext(buf, child); } if ( propIdx < propIds.length - 1 && propIds[propIdx + 1] === AST_PROP_LENGTH ) { return count; } // TODO(@marvinhagemeister): Allow traversing into array children? throw -1; } else if (kind === PropFlags.Obj) { // TODO(@marvinhagemeister) } // Cannot traverse into primitives further if (propIdx < propIds.length - 1) throw -1; if (kind === PropFlags.String) { const s = readU32(buf, offset); return getString(strTable, s); } else if (kind === PropFlags.Number) { const s = readU32(buf, offset); return Number(getString(strTable, s)); } else if (kind === PropFlags.Regex) { const v = readU32(buf, offset); return readRegex(strTable, v); } else if (kind === PropFlags.Bool) { return readU32(buf, offset) === 1; } else if (kind === PropFlags.Null) { return null; } else if (kind === PropFlags.Undefined) { return undefined; } throw -1; } /** * @param {number} idx * @returns {number} */ getFirstChild(idx) { const siblings = this.getSiblings(idx); return siblings[0] ?? -1; } /** * @param {number} idx * @returns {number} */ getLastChild(idx) { const siblings = this.getSiblings(idx); return siblings.at(-1) ?? -1; } /** * @param {number} idx * @returns {number[]} */ getSiblings(idx) { const { buf } = this.ctx; const parent = readParent(buf, idx); // Only RefArrays have siblings const parentType = readType(buf, parent); if (parentType !== AST_GROUP_TYPE) { return []; } const out = []; let child = readChild(buf, parent); while (child > AST_IDX_INVALID) { out.push(child); child = readNext(buf, child); } return out; } } /** * @param {Uint8Array} buf * @returns {AstContext} */ function createAstContext(buf) { /** @type {Map} */ const strTable = new Map(); // The buffer has a few offsets at the end which allows us to easily // jump to the relevant sections of the message. const propsOffset = readU32(buf, buf.length - 24); const spansOffset = readU32(buf, buf.length - 20); const typeMapOffset = readU32(buf, buf.length - 16); const propMapOffset = readU32(buf, buf.length - 12); const strTableOffset = readU32(buf, buf.length - 8); // Offset of the topmost node in the AST Tree. const rootOffset = readU32(buf, buf.length - 4); let offset = strTableOffset; const stringCount = readU32(buf, offset); offset += 4; let strId = 0; for (let i = 0; i < stringCount; i++) { const len = readU32(buf, offset); offset += 4; const strBytes = buf.slice(offset, offset + len); offset += len; const s = DECODER.decode(strBytes); strTable.set(strId, s); strId++; } if (strTable.size !== stringCount) { throw new Error( `Could not deserialize string table. Expected ${stringCount} items, but got ${strTable.size}`, ); } offset = typeMapOffset; const typeCount = readU32(buf, offset); offset += 4; const typeByStr = new Map(); const strByType = new Array(typeCount).fill(0); for (let i = 0; i < typeCount; i++) { const v = readU32(buf, offset); offset += 4; strByType[i] = v; typeByStr.set(strTable.get(v), i); } offset = propMapOffset; 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} */ const ctx = { buf, strTable, rootOffset, spansOffset, propsOffset, nodes: new Map(), strTableOffset, strByProp, strByType, typeByStr, propByStr, matcher: /** @type {*} */ (null), }; ctx.matcher = new MatchCtx(ctx); setNodeGetters(ctx); // DEV ONLY: Enable this to inspect the buffer message // _dump(ctx); return ctx; } /** * @param {*} _node */ const NOOP = (_node) => {}; /** * Kick off the actual linting process of JS plugins. * @param {string} fileName * @param {Uint8Array} serializedAst */ export function runPluginsForFile(fileName, serializedAst) { const ctx = createAstContext(serializedAst); /** @type {Map}>} */ const bySelector = new Map(); const destroyFns = []; // Instantiate and merge visitors. This allows us to only traverse // the AST once instead of per plugin. When ever we enter or exit a // node we'll call all visitors that match. for (let i = 0; i < state.plugins.length; i++) { const plugin = state.plugins[i]; for (const name of Object.keys(plugin.rules)) { const rule = plugin.rules[name]; const id = `${plugin.name}/${name}`; const ctx = new Context(id, fileName); const visitor = rule.create(ctx); // deno-lint-ignore guard-for-in for (let key in visitor) { const fn = visitor[key]; if (fn === undefined) continue; // Support enter and exit callbacks on a visitor. // Exit callbacks are marked by having `:exit` at the end. 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 key = selectors[j]; let info = bySelector.get(key); if (info === undefined) { info = { enter: NOOP, exit: NOOP }; bySelector.set(key, info); } const prevFn = isExit ? info.exit : info.enter; /** * @param {*} node */ const wrapped = (node) => { prevFn(node); try { fn(node); } catch (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") { const destroyFn = rule.destroy.bind(rule); destroyFns.push(() => { try { destroyFn(ctx); } catch (err) { throw new Error(`Destroy hook of "${id}" errored`, { cause: err }); } }); } } } // Create selectors /** @type {TransformFn} */ const toElem = (str) => { const id = ctx.typeByStr.get(str); return id === undefined ? 0 : id; }; /** @type {TransformFn} */ const toAttr = (str) => { const id = ctx.propByStr.get(str); return id === undefined ? 0 : id; }; /** @type {CompiledVisitor[]} */ const visitors = []; for (const [sel, info] of bySelector.entries()) { // Selectors are already split here. // TODO(@marvinhagemeister): Avoid array allocation (not sure if that matters) const parsed = parseSelector(sel, toElem, toAttr)[0]; const matcher = compileSelector(parsed); visitors.push({ info, matcher }); } // Traverse ast with all visitors at the same time to avoid traversing // multiple times. try { traverse(ctx, visitors, ctx.rootOffset); } finally { ctx.nodes.clear(); // Optional: Destroy rules for (let i = 0; i < destroyFns.length; i++) { destroyFns[i](); } } } /** * @param {AstContext} ctx * @param {CompiledVisitor[]} visitors * @param {number} idx */ function traverse(ctx, visitors, idx) { if (idx === AST_IDX_INVALID) return; const { buf } = ctx; const nodeType = readType(ctx.buf, idx); /** @type {VisitorFn[] | null} */ let exits = null; // Only visit if it's an actual node if (nodeType !== AST_GROUP_TYPE) { // Loop over visitors and check if any selector matches for (let i = 0; i < visitors.length; i++) { const v = visitors[i]; if (v.matcher(ctx.matcher, idx)) { if (v.info.exit !== NOOP) { if (exits === null) { exits = [v.info.exit]; } else { exits.push(v.info.exit); } } if (v.info.enter !== NOOP) { const node = /** @type {*} */ (getNode(ctx, idx)); v.info.enter(node); } } } } try { const childIdx = readChild(buf, idx); if (childIdx > AST_IDX_INVALID) { traverse(ctx, visitors, childIdx); } const nextIdx = readNext(buf, idx); if (nextIdx > AST_IDX_INVALID) { traverse(ctx, visitors, nextIdx); } } finally { if (exits !== null) { for (let i = 0; i < exits.length; i++) { const node = /** @type {*} */ (getNode(ctx, idx)); exits[i](node); } } } } /** * This is useful debugging helper to display the buffer's contents. * @param {AstContext} ctx */ function _dump(ctx) { const { buf, strTableOffset, strTable, strByType, strByProp } = ctx; // @ts-ignore dump fn // deno-lint-ignore no-console console.log(strTable); for (let i = 0; i < strByType.length; i++) { const v = strByType[i]; // @ts-ignore dump fn // deno-lint-ignore no-console if (v > 0) console.log(" > type:", i, getString(ctx.strTable, v), v); } // @ts-ignore dump fn // deno-lint-ignore no-console console.log(); for (let i = 0; i < strByProp.length; i++) { const v = strByProp[i]; // @ts-ignore dump fn // deno-lint-ignore no-console if (v > 0) console.log(" > prop:", i, getString(ctx.strTable, v), v); } // @ts-ignore dump fn // deno-lint-ignore no-console console.log(); // @ts-ignore dump fn // deno-lint-ignore no-console console.log(); let idx = 0; while (idx < (strTableOffset / NODE_SIZE)) { const type = readType(buf, idx); const child = readChild(buf, idx); const next = readNext(buf, idx); const parent = readParent(buf, idx); const range = readSpan(ctx, idx); const name = type === AST_IDX_INVALID ? "" : type === AST_GROUP_TYPE ? "" : getString(ctx.strTable, ctx.strByType[type]); // @ts-ignore dump fn // deno-lint-ignore no-console console.log(`${name}, idx: ${idx}, type: ${type}`); // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` child: ${child}, next: ${next}, parent: ${parent}`); // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` range: ${range[0]}, ${range[1]}`); const rawOffset = readRawPropOffset(ctx.buf, idx); let propOffset = readPropOffset(ctx, idx); const count = buf[propOffset++]; // @ts-ignore dump fn // deno-lint-ignore no-console console.log( ` prop count: ${count}, prop offset: ${propOffset} raw offset: ${rawOffset}`, ); for (let i = 0; i < count; i++) { const prop = buf[propOffset++]; const kind = buf[propOffset++]; const name = getString(ctx.strTable, ctx.strByProp[prop]); let kindName = "unknown"; for (const k in PropFlags) { // @ts-ignore dump fn if (kind === PropFlags[k]) { kindName = k; } } const v = readU32(buf, propOffset); propOffset += 4; if (kind === PropFlags.Ref) { // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: ${v} (${kindName}, ${prop})`); } else if (kind === PropFlags.RefArr) { // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: RefArray: ${v}, (${kindName}, ${prop})`); } else if (kind === PropFlags.Bool) { // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: ${v} (${kindName}, ${prop})`); } else if (kind === PropFlags.String) { const raw = getString(ctx.strTable, v); // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: ${raw} (${kindName}, ${prop})`); } else if (kind === PropFlags.Number) { const raw = getString(ctx.strTable, v); // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: ${raw} (${kindName}, ${prop})`); } else if (kind === PropFlags.Regex) { const raw = getString(ctx.strTable, v); // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: ${raw} (${kindName}, ${prop})`); } else if (kind === PropFlags.Null) { // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: null (${kindName}, ${prop})`); } else if (kind === PropFlags.Undefined) { // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: undefined (${kindName}, ${prop})`); } else if (kind === PropFlags.BigInt) { const raw = getString(ctx.strTable, v); // @ts-ignore dump fn // deno-lint-ignore no-console console.log(` ${name}: ${raw} (${kindName}, ${prop})`); } else if (kind === PropFlags.Obj) { let offset = v + ctx.propsOffset; const count = readU32(ctx.buf, offset); offset += 4; // @ts-ignore dump fn // deno-lint-ignore no-console console.log( ` ${name}: Object (${count}) (${kindName}, ${prop}), raw offset ${v}`, ); // TODO(@marvinhagemeister): Show object } } idx++; } } // TODO(bartlomieju): this is temporary, until we get plugins plumbed through // the CLI linter /** * @param {LintPlugin} plugin * @param {string} fileName * @param {string} sourceText */ function runLintPlugin(plugin, fileName, sourceText) { installPlugin(plugin); try { const serializedAst = op_lint_create_serialized_ast(fileName, sourceText); runPluginsForFile(fileName, serializedAst); } finally { // During testing we don't want to keep plugins around state.installedPlugins.clear(); } } // TODO(bartlomieju): this is temporary, until we get plugins plumbed through // the CLI linter internals.runLintPlugin = runLintPlugin;