first commit
This commit is contained in:
+73
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Markdown Language Object
|
||||
* @implements {Language}
|
||||
*/
|
||||
export class MarkdownLanguage implements Language {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options to use for this instance.
|
||||
* @param {ParserMode} [options.mode] The Markdown parser mode to use.
|
||||
*/
|
||||
constructor({ mode }?: {
|
||||
mode?: ParserMode;
|
||||
});
|
||||
/**
|
||||
* The type of file to read.
|
||||
* @type {"text"}
|
||||
*/
|
||||
fileType: "text";
|
||||
/**
|
||||
* The line number at which the parser starts counting.
|
||||
* @type {0|1}
|
||||
*/
|
||||
lineStart: 0 | 1;
|
||||
/**
|
||||
* The column number at which the parser starts counting.
|
||||
* @type {0|1}
|
||||
*/
|
||||
columnStart: 0 | 1;
|
||||
/**
|
||||
* The name of the key that holds the type of the node.
|
||||
* @type {string}
|
||||
*/
|
||||
nodeTypeKey: string;
|
||||
/**
|
||||
* Default language options. User-defined options are merged with this object.
|
||||
* @type {MarkdownLanguageOptions}
|
||||
*/
|
||||
defaultLanguageOptions: MarkdownLanguageOptions;
|
||||
/**
|
||||
* Validates the language options.
|
||||
* @param {MarkdownLanguageOptions} languageOptions The language options to validate.
|
||||
* @returns {void}
|
||||
* @throws {Error} When the language options are invalid.
|
||||
*/
|
||||
validateLanguageOptions(languageOptions: MarkdownLanguageOptions): void;
|
||||
/**
|
||||
* Parses the given file into an AST.
|
||||
* @param {File} file The virtual file to parse.
|
||||
* @param {MarkdownLanguageContext} context The options to use for parsing.
|
||||
* @returns {ParseResult<Root>} The result of parsing.
|
||||
*/
|
||||
parse(file: File, context: MarkdownLanguageContext): ParseResult<Root>;
|
||||
/**
|
||||
* Creates a new `MarkdownSourceCode` object from the given information.
|
||||
* @param {File} file The virtual file to create a `MarkdownSourceCode` object from.
|
||||
* @param {OkParseResult<Root>} parseResult The result returned from `parse()`.
|
||||
* @returns {MarkdownSourceCode} The new `MarkdownSourceCode` object.
|
||||
*/
|
||||
createSourceCode(file: File, parseResult: OkParseResult<Root>): MarkdownSourceCode;
|
||||
#private;
|
||||
}
|
||||
export type Extensions = Options["extensions"];
|
||||
export type MdastExtensions = Options["mdastExtensions"];
|
||||
export type ParserMode = "commonmark" | "gfm";
|
||||
import type { Language } from "@eslint/core";
|
||||
import type { MarkdownLanguageOptions } from "../types.js";
|
||||
import type { File } from "@eslint/core";
|
||||
import type { MarkdownLanguageContext } from "../types.js";
|
||||
import type { Root } from "mdast";
|
||||
import type { ParseResult } from "@eslint/core";
|
||||
import type { OkParseResult } from "@eslint/core";
|
||||
import { MarkdownSourceCode } from "./markdown-source-code.js";
|
||||
import type { Options } from "mdast-util-from-markdown";
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @fileoverview The MarkdownLanguage class.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
/* eslint class-methods-use-this: 0 -- Required to complete interface. */
|
||||
//------------------------------------------------------------------------------
|
||||
// Imports
|
||||
//------------------------------------------------------------------------------
|
||||
import { MarkdownSourceCode } from "./markdown-source-code.js";
|
||||
import { fromMarkdown } from "mdast-util-from-markdown";
|
||||
import { frontmatterFromMarkdown } from "mdast-util-frontmatter";
|
||||
import { gfmFromMarkdown } from "mdast-util-gfm";
|
||||
import { mathFromMarkdown } from "mdast-util-math";
|
||||
import { frontmatter } from "micromark-extension-frontmatter";
|
||||
import { gfm } from "micromark-extension-gfm";
|
||||
import { math } from "micromark-extension-math";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { Language, File, ParseResult, OkParseResult } from "@eslint/core";
|
||||
* @import { Root } from "mdast";
|
||||
* @import { Options } from "mdast-util-from-markdown";
|
||||
* @import { MarkdownLanguageOptions, MarkdownLanguageContext } from "../types.js";
|
||||
* @typedef {Options['extensions']} Extensions
|
||||
* @typedef {Options['mdastExtensions']} MdastExtensions
|
||||
* @typedef {"commonmark"|"gfm"} ParserMode
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Parser configuration for JSON frontmatter.
|
||||
* Example of supported frontmatter format:
|
||||
* ```markdown
|
||||
* ---
|
||||
* {
|
||||
* "title": "My Document",
|
||||
* "date": "2025-06-09"
|
||||
* }
|
||||
* ---
|
||||
* ```
|
||||
*/
|
||||
const jsonFrontmatterConfig = {
|
||||
type: "json",
|
||||
marker: "-",
|
||||
};
|
||||
/**
|
||||
* Create parser options based on `mode` and `languageOptions`.
|
||||
* @param {ParserMode} mode The markdown parser mode.
|
||||
* @param {MarkdownLanguageOptions} languageOptions Language options.
|
||||
* @returns {{extensions: Extensions, mdastExtensions: MdastExtensions}} Parser options for micromark and mdast.
|
||||
*/
|
||||
function createParserOptions(mode, languageOptions) {
|
||||
/** @type {Extensions} */
|
||||
const extensions = [];
|
||||
/** @type {MdastExtensions} */
|
||||
const mdastExtensions = [];
|
||||
// 1. `mode`: Add GFM extensions if mode is "gfm"
|
||||
if (mode === "gfm") {
|
||||
extensions.push(gfm());
|
||||
mdastExtensions.push(gfmFromMarkdown());
|
||||
}
|
||||
// 2. `languageOptions.frontmatter`: Handle frontmatter options
|
||||
const frontmatterOption = languageOptions?.frontmatter;
|
||||
// Skip frontmatter entirely if false
|
||||
if (frontmatterOption !== false) {
|
||||
if (frontmatterOption === "yaml") {
|
||||
extensions.push(frontmatter(["yaml"]));
|
||||
mdastExtensions.push(frontmatterFromMarkdown(["yaml"]));
|
||||
}
|
||||
else if (frontmatterOption === "toml") {
|
||||
extensions.push(frontmatter(["toml"]));
|
||||
mdastExtensions.push(frontmatterFromMarkdown(["toml"]));
|
||||
}
|
||||
else if (frontmatterOption === "json") {
|
||||
extensions.push(frontmatter(jsonFrontmatterConfig));
|
||||
mdastExtensions.push(frontmatterFromMarkdown(jsonFrontmatterConfig));
|
||||
}
|
||||
}
|
||||
// 3. `languageOptions.math`: Handle math option
|
||||
const mathOption = languageOptions?.math;
|
||||
// Skip math entirely if false
|
||||
if (mathOption === true) {
|
||||
extensions.push(math());
|
||||
mdastExtensions.push(mathFromMarkdown());
|
||||
}
|
||||
return {
|
||||
extensions,
|
||||
mdastExtensions,
|
||||
};
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Markdown Language Object
|
||||
* @implements {Language}
|
||||
*/
|
||||
export class MarkdownLanguage {
|
||||
/**
|
||||
* The type of file to read.
|
||||
* @type {"text"}
|
||||
*/
|
||||
fileType = "text";
|
||||
/**
|
||||
* The line number at which the parser starts counting.
|
||||
* @type {0|1}
|
||||
*/
|
||||
lineStart = 1;
|
||||
/**
|
||||
* The column number at which the parser starts counting.
|
||||
* @type {0|1}
|
||||
*/
|
||||
columnStart = 1;
|
||||
/**
|
||||
* The name of the key that holds the type of the node.
|
||||
* @type {string}
|
||||
*/
|
||||
nodeTypeKey = "type";
|
||||
/**
|
||||
* Default language options. User-defined options are merged with this object.
|
||||
* @type {MarkdownLanguageOptions}
|
||||
*/
|
||||
defaultLanguageOptions = {
|
||||
frontmatter: false,
|
||||
math: false,
|
||||
};
|
||||
/**
|
||||
* The Markdown parser mode.
|
||||
* @type {ParserMode}
|
||||
*/
|
||||
#mode = "commonmark";
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options to use for this instance.
|
||||
* @param {ParserMode} [options.mode] The Markdown parser mode to use.
|
||||
*/
|
||||
constructor({ mode } = {}) {
|
||||
if (mode) {
|
||||
this.#mode = mode;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Validates the language options.
|
||||
* @param {MarkdownLanguageOptions} languageOptions The language options to validate.
|
||||
* @returns {void}
|
||||
* @throws {Error} When the language options are invalid.
|
||||
*/
|
||||
validateLanguageOptions(languageOptions) {
|
||||
// `frontmatter` option validation
|
||||
const frontmatterOption = languageOptions?.frontmatter;
|
||||
const validFrontmatterOptions = new Set([
|
||||
false,
|
||||
"yaml",
|
||||
"toml",
|
||||
"json",
|
||||
]);
|
||||
if (frontmatterOption !== undefined &&
|
||||
!validFrontmatterOptions.has(frontmatterOption)) {
|
||||
throw new Error(`Invalid language option value \`${frontmatterOption}\` for frontmatter. Expected one of \`false\`, \`"yaml"\`, \`"toml"\`, or \`"json"\`.`);
|
||||
}
|
||||
// `math` option validation
|
||||
const mathOption = languageOptions?.math;
|
||||
if (mathOption !== undefined && typeof mathOption !== "boolean") {
|
||||
throw new Error(`Invalid language option value \`${mathOption}\` for math. Expected a boolean.`);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Parses the given file into an AST.
|
||||
* @param {File} file The virtual file to parse.
|
||||
* @param {MarkdownLanguageContext} context The options to use for parsing.
|
||||
* @returns {ParseResult<Root>} The result of parsing.
|
||||
*/
|
||||
parse(file, context) {
|
||||
// Note: BOM already removed
|
||||
const text = /** @type {string} */ (file.body);
|
||||
/*
|
||||
* Check for parsing errors first. If there's a parsing error, nothing
|
||||
* else can happen. However, a parsing error does not throw an error
|
||||
* from this method - it's just considered a fatal error message, a
|
||||
* problem that ESLint identified just like any other.
|
||||
*/
|
||||
try {
|
||||
const options = createParserOptions(this.#mode, context?.languageOptions);
|
||||
const root = fromMarkdown(text, options);
|
||||
return {
|
||||
ok: true,
|
||||
ast: root,
|
||||
};
|
||||
}
|
||||
catch (ex) {
|
||||
return {
|
||||
ok: false,
|
||||
errors: [ex],
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Creates a new `MarkdownSourceCode` object from the given information.
|
||||
* @param {File} file The virtual file to create a `MarkdownSourceCode` object from.
|
||||
* @param {OkParseResult<Root>} parseResult The result returned from `parse()`.
|
||||
* @returns {MarkdownSourceCode} The new `MarkdownSourceCode` object.
|
||||
*/
|
||||
createSourceCode(file, parseResult) {
|
||||
return new MarkdownSourceCode({
|
||||
text: /** @type {string} */ (file.body),
|
||||
ast: parseResult.ast,
|
||||
});
|
||||
}
|
||||
}
|
||||
Generated
Vendored
+96
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Represents an inline config comment in the source code.
|
||||
*/
|
||||
export class InlineConfigComment {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the instance.
|
||||
* @param {string} options.value The comment text.
|
||||
* @param {Position} options.position The position of the comment in the source code.
|
||||
*/
|
||||
constructor({ value, position }: {
|
||||
value: string;
|
||||
position: Position;
|
||||
});
|
||||
/**
|
||||
* The comment text.
|
||||
* @type {string}
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* The position of the comment in the source code.
|
||||
* @type {Position}
|
||||
*/
|
||||
position: Position;
|
||||
}
|
||||
/**
|
||||
* Markdown Source Code Object
|
||||
* @extends {TextSourceCodeBase<{LangOptions: MarkdownLanguageOptions, RootNode: Root, SyntaxElementWithLoc: Node, ConfigNode: { value: string; position: Position }}>}
|
||||
*/
|
||||
export class MarkdownSourceCode extends TextSourceCodeBase<{
|
||||
LangOptions: MarkdownLanguageOptions;
|
||||
RootNode: Root;
|
||||
SyntaxElementWithLoc: Node;
|
||||
ConfigNode: {
|
||||
value: string;
|
||||
position: Position;
|
||||
};
|
||||
}> {
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the instance.
|
||||
* @param {string} options.text The source code text.
|
||||
* @param {Root} options.ast The root AST node.
|
||||
*/
|
||||
constructor({ text, ast }: {
|
||||
text: string;
|
||||
ast: Root;
|
||||
});
|
||||
/**
|
||||
* Returns the parent of the given node.
|
||||
* @param {Node} node The node to get the parent of.
|
||||
* @returns {Parent|undefined} The parent of the node.
|
||||
*/
|
||||
getParent(node: Node): Parent | undefined;
|
||||
/**
|
||||
* Returns an array of all inline configuration nodes found in the
|
||||
* source code.
|
||||
* @returns {Array<InlineConfigComment>} An array of all inline configuration nodes.
|
||||
*/
|
||||
getInlineConfigNodes(): Array<InlineConfigComment>;
|
||||
/**
|
||||
* Returns an all directive nodes that enable or disable rules along with any problems
|
||||
* encountered while parsing the directives.
|
||||
* @returns {{problems:Array<FileProblem>,directives:Array<Directive>}} Information
|
||||
* that ESLint needs to further process the directives.
|
||||
*/
|
||||
getDisableDirectives(): {
|
||||
problems: Array<FileProblem>;
|
||||
directives: Array<Directive>;
|
||||
};
|
||||
/**
|
||||
* Returns inline rule configurations along with any problems
|
||||
* encountered while parsing the configurations.
|
||||
* @returns {{problems:Array<FileProblem>,configs:Array<{config:{rules:RulesConfig},loc:Position}>}} Information
|
||||
* that ESLint needs to further process the rule configurations.
|
||||
*/
|
||||
applyInlineConfig(): {
|
||||
problems: Array<FileProblem>;
|
||||
configs: Array<{
|
||||
config: {
|
||||
rules: RulesConfig;
|
||||
};
|
||||
loc: Position;
|
||||
}>;
|
||||
};
|
||||
#private;
|
||||
}
|
||||
import type { Position } from "unist";
|
||||
import type { MarkdownLanguageOptions } from "../types.js";
|
||||
import type { Root } from "mdast";
|
||||
import type { Node } from "mdast";
|
||||
import { TextSourceCodeBase } from "@eslint/plugin-kit";
|
||||
import type { Parent } from "mdast";
|
||||
import type { FileProblem } from "@eslint/core";
|
||||
import { Directive } from "@eslint/plugin-kit";
|
||||
import type { RulesConfig } from "@eslint/core";
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
/**
|
||||
* @fileoverview The MarkdownSourceCode class.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Imports
|
||||
//-----------------------------------------------------------------------------
|
||||
import { VisitNodeStep, TextSourceCodeBase, ConfigCommentParser, Directive, } from "@eslint/plugin-kit";
|
||||
import { lineEndingPattern } from "../util.js";
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* @import { Position } from "unist";
|
||||
* @import { Parent, Root, Node, Html } from "mdast";
|
||||
* @import { TraversalStep, FileProblem, DirectiveType, RulesConfig } from "@eslint/core";
|
||||
* @import { MarkdownLanguageOptions } from "../types.js";
|
||||
*/
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
const commentParser = new ConfigCommentParser();
|
||||
const configCommentStart = /<!--\s*eslint(?:-enable|-disable(?:(?:-next)?-line)?)?(?:\s|-->)/u;
|
||||
const htmlComment = /<!--(.*?)-->/gsu;
|
||||
/**
|
||||
* Represents an inline config comment in the source code.
|
||||
*/
|
||||
export class InlineConfigComment {
|
||||
/**
|
||||
* The comment text.
|
||||
* @type {string}
|
||||
*/
|
||||
value;
|
||||
/**
|
||||
* The position of the comment in the source code.
|
||||
* @type {Position}
|
||||
*/
|
||||
position;
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the instance.
|
||||
* @param {string} options.value The comment text.
|
||||
* @param {Position} options.position The position of the comment in the source code.
|
||||
*/
|
||||
constructor({ value, position }) {
|
||||
this.value = value.trim();
|
||||
this.position = position;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Extracts inline configuration comments from an HTML node.
|
||||
* @param {Html} node The HTML node to extract comments from.
|
||||
* @param {MarkdownSourceCode} sourceCode The Markdown source code object.
|
||||
* @returns {Array<InlineConfigComment>} The inline configuration comments found in the node.
|
||||
*/
|
||||
function extractInlineConfigCommentsFromHTML(node, sourceCode) {
|
||||
if (!configCommentStart.test(node.value)) {
|
||||
return [];
|
||||
}
|
||||
/** @type {Array<InlineConfigComment>} */
|
||||
const comments = [];
|
||||
/** @type {RegExpExecArray | null} */
|
||||
let match;
|
||||
while ((match = htmlComment.exec(node.value))) {
|
||||
if (configCommentStart.test(match[0])) {
|
||||
// calculate offset of the comment inside the node
|
||||
const startOffset = match.index + node.position.start.offset;
|
||||
const endOffset = startOffset + match[0].length;
|
||||
comments.push(new InlineConfigComment({
|
||||
value: match[1].trim(),
|
||||
position: {
|
||||
start: {
|
||||
...sourceCode.getLocFromIndex(startOffset),
|
||||
offset: startOffset,
|
||||
},
|
||||
end: {
|
||||
...sourceCode.getLocFromIndex(endOffset),
|
||||
offset: endOffset,
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
return comments;
|
||||
}
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
/**
|
||||
* Markdown Source Code Object
|
||||
* @extends {TextSourceCodeBase<{LangOptions: MarkdownLanguageOptions, RootNode: Root, SyntaxElementWithLoc: Node, ConfigNode: { value: string; position: Position }}>}
|
||||
*/
|
||||
export class MarkdownSourceCode extends TextSourceCodeBase {
|
||||
/**
|
||||
* Cached traversal steps.
|
||||
* @type {Array<VisitNodeStep>|undefined}
|
||||
*/
|
||||
#steps;
|
||||
/**
|
||||
* Cache of parent nodes.
|
||||
* @type {WeakMap<Node, Parent|undefined>}
|
||||
*/
|
||||
#parents = new WeakMap();
|
||||
/**
|
||||
* Collection of HTML nodes. Used to find directive comments.
|
||||
* @type {Array<Html>}
|
||||
*/
|
||||
#htmlNodes = [];
|
||||
/**
|
||||
* Collection of inline configuration comments.
|
||||
* @type {Array<InlineConfigComment>}
|
||||
*/
|
||||
#inlineConfigComments;
|
||||
/**
|
||||
* The AST of the source code.
|
||||
* @type {Root}
|
||||
*/
|
||||
ast = undefined;
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} options The options for the instance.
|
||||
* @param {string} options.text The source code text.
|
||||
* @param {Root} options.ast The root AST node.
|
||||
*/
|
||||
constructor({ text, ast }) {
|
||||
super({ ast, text, lineEndingPattern });
|
||||
this.ast = ast;
|
||||
// need to traverse the source code to get the inline config nodes
|
||||
this.traverse();
|
||||
}
|
||||
/**
|
||||
* Returns the parent of the given node.
|
||||
* @param {Node} node The node to get the parent of.
|
||||
* @returns {Parent|undefined} The parent of the node.
|
||||
*/
|
||||
getParent(node) {
|
||||
return this.#parents.get(node);
|
||||
}
|
||||
/**
|
||||
* Returns an array of all inline configuration nodes found in the
|
||||
* source code.
|
||||
* @returns {Array<InlineConfigComment>} An array of all inline configuration nodes.
|
||||
*/
|
||||
getInlineConfigNodes() {
|
||||
if (!this.#inlineConfigComments) {
|
||||
this.#inlineConfigComments = this.#htmlNodes.flatMap(htmlNode => extractInlineConfigCommentsFromHTML(htmlNode, this));
|
||||
}
|
||||
return this.#inlineConfigComments;
|
||||
}
|
||||
/**
|
||||
* Returns an all directive nodes that enable or disable rules along with any problems
|
||||
* encountered while parsing the directives.
|
||||
* @returns {{problems:Array<FileProblem>,directives:Array<Directive>}} Information
|
||||
* that ESLint needs to further process the directives.
|
||||
*/
|
||||
getDisableDirectives() {
|
||||
/** @type {Array<FileProblem>} */
|
||||
const problems = [];
|
||||
/** @type {Array<Directive>} */
|
||||
const directives = [];
|
||||
this.getInlineConfigNodes().forEach(comment => {
|
||||
// Step 1: Parse the directive
|
||||
const { label, value, justification: justificationPart, } = commentParser.parseDirective(comment.value);
|
||||
// Step 2: Validate the directive does not span multiple lines
|
||||
if (label === "eslint-disable-line" &&
|
||||
comment.position.start.line !== comment.position.end.line) {
|
||||
const message = `${label} comment should not span multiple lines.`;
|
||||
problems.push({
|
||||
ruleId: null,
|
||||
message,
|
||||
loc: comment.position,
|
||||
});
|
||||
return;
|
||||
}
|
||||
// Step 3: Extract the directive value and create the Directive object
|
||||
switch (label) {
|
||||
case "eslint-disable":
|
||||
case "eslint-enable":
|
||||
case "eslint-disable-next-line":
|
||||
case "eslint-disable-line": {
|
||||
const directiveType = label.slice("eslint-".length);
|
||||
directives.push(new Directive({
|
||||
type: /** @type {DirectiveType} */ (directiveType),
|
||||
node: comment,
|
||||
value,
|
||||
justification: justificationPart,
|
||||
}));
|
||||
}
|
||||
// no default
|
||||
}
|
||||
});
|
||||
return { problems, directives };
|
||||
}
|
||||
/**
|
||||
* Returns inline rule configurations along with any problems
|
||||
* encountered while parsing the configurations.
|
||||
* @returns {{problems:Array<FileProblem>,configs:Array<{config:{rules:RulesConfig},loc:Position}>}} Information
|
||||
* that ESLint needs to further process the rule configurations.
|
||||
*/
|
||||
applyInlineConfig() {
|
||||
/** @type {Array<FileProblem>} */
|
||||
const problems = [];
|
||||
/** @type {Array<{config:{rules:RulesConfig},loc:Position}>} */
|
||||
const configs = [];
|
||||
this.getInlineConfigNodes().forEach(comment => {
|
||||
const { label, value } = commentParser.parseDirective(comment.value);
|
||||
if (label === "eslint") {
|
||||
const parseResult = commentParser.parseJSONLikeConfig(value);
|
||||
if (parseResult.ok) {
|
||||
configs.push({
|
||||
config: {
|
||||
rules: parseResult.config,
|
||||
},
|
||||
loc: comment.position,
|
||||
});
|
||||
}
|
||||
else {
|
||||
problems.push({
|
||||
ruleId: null,
|
||||
message:
|
||||
/** @type {{ok: false, error: { message: string }}} */ (parseResult).error.message,
|
||||
loc: comment.position,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
configs,
|
||||
problems,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Traverse the source code and return the steps that were taken.
|
||||
* @returns {Iterable<TraversalStep>} The steps that were taken while traversing the source code.
|
||||
*/
|
||||
traverse() {
|
||||
// Because the AST doesn't mutate, we can cache the steps
|
||||
if (this.#steps) {
|
||||
return this.#steps.values();
|
||||
}
|
||||
/** @type {Array<VisitNodeStep>} */
|
||||
const steps = (this.#steps = []);
|
||||
/**
|
||||
* Recursively visits a node and its children.
|
||||
* @param {Node} node The node to visit.
|
||||
* @param {Parent} [parent] The parent of the node.
|
||||
* @returns {void}
|
||||
*/
|
||||
const visit = (node, parent) => {
|
||||
// first set the parent
|
||||
this.#parents.set(node, parent);
|
||||
// then add the step
|
||||
steps.push(new VisitNodeStep({
|
||||
target: node,
|
||||
phase: 1,
|
||||
args: [node, parent],
|
||||
}));
|
||||
// save HTML nodes
|
||||
if (node.type === "html") {
|
||||
this.#htmlNodes.push(/** @type {Html} */ (node));
|
||||
}
|
||||
// then visit the children
|
||||
if ("children" in node) {
|
||||
const parentNode = /** @type {Parent} */ (node);
|
||||
parentNode.children.forEach(child => {
|
||||
visit(child, parentNode);
|
||||
});
|
||||
}
|
||||
// then add the exit step
|
||||
steps.push(new VisitNodeStep({
|
||||
target: node,
|
||||
phase: 2,
|
||||
args: [node, parent],
|
||||
}));
|
||||
};
|
||||
visit(this.ast);
|
||||
return steps.values();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user