diff --git a/eslint.config.js b/eslint.config.js index 0679c9a8..9ae9a2cd 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,6 +4,13 @@ import markdown from "./src/index.js"; export default [ ...eslintConfigESLint, + { + name: "markdown/js", + files: ["**/*.js"], + rules: { + "no-undefined": "off", + }, + }, { name: "markdown/plugins", plugins: { diff --git a/package.json b/package.json index acacd982..043326b9 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "test:jsr": "npx jsr@latest publish --dry-run" }, "devDependencies": { - "@eslint/core": "^0.3.0", + "@eslint/core": "^0.6.0", "@eslint/js": "^9.4.0", "@types/eslint": "^9.6.0", "c8": "^10.1.2", @@ -77,6 +77,7 @@ "yorkie": "^2.0.0" }, "dependencies": { + "@eslint/plugin-kit": "^0.2.0", "mdast-util-from-markdown": "^2.0.1", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0" diff --git a/src/language/markdown-source-code.js b/src/language/markdown-source-code.js index a76060fc..d57f6f93 100644 --- a/src/language/markdown-source-code.js +++ b/src/language/markdown-source-code.js @@ -7,6 +7,8 @@ // Imports //----------------------------------------------------------------------------- +import { VisitNodeStep, TextSourceCodeBase } from "@eslint/plugin-kit"; + //----------------------------------------------------------------------------- // Types //----------------------------------------------------------------------------- @@ -22,74 +24,17 @@ /** @typedef {import("@eslint/core").SourceLocation} SourceLocation */ /** @typedef {import("@eslint/core").SourceRange} SourceRange */ -//----------------------------------------------------------------------------- -// Helpers -//----------------------------------------------------------------------------- - -/** - * A class to represent a step in the traversal process. - * @implements {VisitTraversalStep} - */ -class MarkdownTraversalStep { - /** - * The type of the step. - * @type {"visit"} - * @readonly - */ - type = "visit"; - - /** - * The kind of the step. Represents the same data as the `type` property - * but it's a number for performance. - * @type {1} - * @readonly - */ - kind = 1; - - /** - * The target of the step. - * @type {MarkdownNode} - */ - target; - - /** - * The phase of the step. - * @type {1|2} - */ - phase; - - /** - * The arguments of the step. - * @type {Array} - */ - args; - - /** - * Creates a new instance. - * @param {Object} options The options for the step. - * @param {MarkdownNode} options.target The target of the step. - * @param {1|2} options.phase The phase of the step. - * @param {Array} options.args The arguments of the step. - */ - constructor({ target, phase, args }) { - this.target = target; - this.phase = phase; - this.args = args; - } -} - //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- /** - * JSON Source Code Object - * @implements {TextSourceCode} + * Markdown Source Code Object */ -export class MarkdownSourceCode { +export class MarkdownSourceCode extends TextSourceCodeBase { /** * Cached traversal steps. - * @type {Array|undefined} + * @type {Array|undefined} */ #steps; @@ -99,29 +44,11 @@ export class MarkdownSourceCode { */ #parents = new WeakMap(); - /** - * The lines of text in the source code. - * @type {Array} - */ - #lines; - - /** - * Cache of ranges. - * @type {WeakMap} - */ - #ranges = new WeakMap(); - /** * The AST of the source code. * @type {RootNode} */ - ast; - - /** - * The text of the source code. - * @type {string} - */ - text; + ast = undefined; /** * Creates a new instance. @@ -130,38 +57,10 @@ export class MarkdownSourceCode { * @param {RootNode} options.ast The root AST node. */ constructor({ text, ast }) { + super({ ast, text }); this.ast = ast; - this.text = text; } - /* eslint-disable class-methods-use-this -- Required to complete interface. */ - /** - * Gets the location of the node. - * @param {MarkdownNode} node The node to get the location of. - * @returns {SourceLocation} The location of the node. - */ - getLoc(node) { - return node.position; - } - - /** - * Gets the range of the node. - * @param {MarkdownNode} node The node to get the range of. - * @returns {SourceRange} The range of the node. - */ - getRange(node) { - if (!this.#ranges.has(node)) { - this.#ranges.set(node, [ - node.position.start.offset, - node.position.end.offset, - ]); - } - - return this.#ranges.get(node); - } - - /* eslint-enable class-methods-use-this -- Required to complete interface. */ - /** * Returns the parent of the given node. * @param {MarkdownNode} node The node to get the parent of. @@ -171,63 +70,6 @@ export class MarkdownSourceCode { return this.#parents.get(node); } - /** - * Gets all the ancestors of a given node - * @param {MarkdownNode} node The node - * @returns {Array} All the ancestor nodes in the AST, not including the provided node, starting - * from the root node at index 0 and going inwards to the parent node. - * @throws {TypeError} When `node` is missing. - */ - getAncestors(node) { - if (!node) { - throw new TypeError("Missing required argument: node."); - } - - const ancestorsStartingAtParent = []; - - for ( - let ancestor = this.#parents.get(node); - ancestor; - ancestor = this.#parents.get(ancestor) - ) { - ancestorsStartingAtParent.push(ancestor); - } - - return ancestorsStartingAtParent.reverse(); - } - - /** - * Gets the source code for the given node. - * @param {MarkdownNode} [node] The AST node to get the text for. - * @param {number} [beforeCount] The number of characters before the node to retrieve. - * @param {number} [afterCount] The number of characters after the node to retrieve. - * @returns {string} The text representing the AST node. - * @public - */ - getText(node, beforeCount = 0, afterCount = 0) { - if (node) { - const range = this.getRange(node); - - return this.text.slice( - Math.max(range[0] - beforeCount, 0), - range[1] + afterCount, - ); - } - return this.text; - } - - /** - * Gets the entire source text split into an array of lines. - * @returns {Array} The source text as an array of lines. - * @public - */ - get lines() { - if (!this.#lines) { - this.#lines = this.text.split(/\r?\n/gu); - } - return this.#lines; - } - /** * Traverse the source code and return the steps that were taken. * @returns {Iterable} The steps that were taken while traversing the source code. @@ -238,6 +80,7 @@ export class MarkdownSourceCode { return this.#steps.values(); } + /** @type {Array} */ const steps = (this.#steps = []); const visit = (node, parent) => { @@ -246,7 +89,7 @@ export class MarkdownSourceCode { // then add the step steps.push( - new MarkdownTraversalStep({ + new VisitNodeStep({ target: node, phase: 1, args: [node, parent], @@ -262,7 +105,7 @@ export class MarkdownSourceCode { // then add the exit step steps.push( - new MarkdownTraversalStep({ + new VisitNodeStep({ target: node, phase: 2, args: [node, parent],