| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879 |
- // @ts-self-types="./index.d.ts"
- import levn from 'levn';
- /**
- * @fileoverview Config Comment Parser
- * @author Nicholas C. Zakas
- */
- //-----------------------------------------------------------------------------
- // Type Definitions
- //-----------------------------------------------------------------------------
- /** @import * as $eslintcore from "@eslint/core"; */
- /** @typedef {$eslintcore.RuleConfig} RuleConfig */
- /** @typedef {$eslintcore.RulesConfig} RulesConfig */
- /** @import * as $typests from "./types.ts"; */
- /** @typedef {$typests.StringConfig} StringConfig */
- /** @typedef {$typests.BooleanConfig} BooleanConfig */
- //-----------------------------------------------------------------------------
- // Helpers
- //-----------------------------------------------------------------------------
- const directivesPattern = /^([a-z]+(?:-[a-z]+)*)(?:\s|$)/u;
- const validSeverities = new Set([0, 1, 2, "off", "warn", "error"]);
- /**
- * Determines if the severity in the rule configuration is valid.
- * @param {RuleConfig} ruleConfig A rule's configuration.
- * @returns {boolean} `true` if the severity is valid, otherwise `false`.
- */
- function isSeverityValid(ruleConfig) {
- const severity = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
- return validSeverities.has(severity);
- }
- /**
- * Determines if all severities in the rules configuration are valid.
- * @param {RulesConfig} rulesConfig The rules configuration to check.
- * @returns {boolean} `true` if all severities are valid, otherwise `false`.
- */
- function isEverySeverityValid(rulesConfig) {
- return Object.values(rulesConfig).every(isSeverityValid);
- }
- /**
- * Represents a directive comment.
- */
- class DirectiveComment {
- /**
- * The label of the directive, such as "eslint", "eslint-disable", etc.
- * @type {string}
- */
- label = "";
- /**
- * The value of the directive (the string after the label).
- * @type {string}
- */
- value = "";
- /**
- * The justification of the directive (the string after the --).
- * @type {string}
- */
- justification = "";
- /**
- * Creates a new directive comment.
- * @param {string} label The label of the directive.
- * @param {string} value The value of the directive.
- * @param {string} justification The justification of the directive.
- */
- constructor(label, value, justification) {
- this.label = label;
- this.value = value;
- this.justification = justification;
- }
- }
- //------------------------------------------------------------------------------
- // Public Interface
- //------------------------------------------------------------------------------
- /**
- * Object to parse ESLint configuration comments.
- */
- class ConfigCommentParser {
- /**
- * Parses a list of "name:string_value" or/and "name" options divided by comma or
- * whitespace. Used for "global" comments.
- * @param {string} string The string to parse.
- * @returns {StringConfig} Result map object of names and string values, or null values if no value was provided.
- */
- parseStringConfig(string) {
- const items = /** @type {StringConfig} */ ({});
- // Collapse whitespace around `:` and `,` to make parsing easier
- const trimmedString = string
- .trim()
- .replace(/(?<!\s)\s*([:,])\s*/gu, "$1");
- trimmedString.split(/\s|,+/u).forEach(name => {
- if (!name) {
- return;
- }
- // value defaults to null (if not provided), e.g: "foo" => ["foo", null]
- const [key, value = null] = name.split(":");
- items[key] = value;
- });
- return items;
- }
- /**
- * Parses a JSON-like config.
- * @param {string} string The string to parse.
- * @returns {({ok: true, config: RulesConfig}|{ok: false, error: {message: string}})} Result map object
- */
- parseJSONLikeConfig(string) {
- // Parses a JSON-like comment by the same way as parsing CLI option.
- try {
- const items =
- /** @type {RulesConfig} */ (levn.parse("Object", string)) || {};
- /*
- * When the configuration has any invalid severities, it should be completely
- * ignored. This is because the configuration is not valid and should not be
- * applied.
- *
- * For example, the following configuration is invalid:
- *
- * "no-alert: 2 no-console: 2"
- *
- * This results in a configuration of { "no-alert": "2 no-console: 2" }, which is
- * not valid. In this case, the configuration should be ignored.
- */
- if (isEverySeverityValid(items)) {
- return {
- ok: true,
- config: items,
- };
- }
- } catch {
- // levn parsing error: ignore to parse the string by a fallback.
- }
- /*
- * Optionator cannot parse commaless notations.
- * But we are supporting that. So this is a fallback for that.
- */
- const normalizedString = string
- .replace(/(?<![-a-zA-Z0-9/])([-a-zA-Z0-9/]+):/gu, '"$1":')
- .replace(/([\]0-9])\s+(?=")/u, "$1,");
- try {
- const items = JSON.parse(`{${normalizedString}}`);
- return {
- ok: true,
- config: items,
- };
- } catch (ex) {
- const errorMessage = ex instanceof Error ? ex.message : String(ex);
- return {
- ok: false,
- error: {
- message: `Failed to parse JSON from '${normalizedString}': ${errorMessage}`,
- },
- };
- }
- }
- /**
- * Parses a config of values separated by comma.
- * @param {string} string The string to parse.
- * @returns {BooleanConfig} Result map of values and true values
- */
- parseListConfig(string) {
- const items = /** @type {BooleanConfig} */ ({});
- string.split(",").forEach(name => {
- const trimmedName = name
- .trim()
- .replace(
- /^(?<quote>['"]?)(?<ruleId>.*)\k<quote>$/su,
- "$<ruleId>",
- );
- if (trimmedName) {
- items[trimmedName] = true;
- }
- });
- return items;
- }
- /**
- * Extract the directive and the justification from a given directive comment and trim them.
- * @param {string} value The comment text to extract.
- * @returns {{directivePart: string, justificationPart: string}} The extracted directive and justification.
- */
- #extractDirectiveComment(value) {
- const match = /\s-{2,}\s/u.exec(value);
- if (!match) {
- return { directivePart: value.trim(), justificationPart: "" };
- }
- const directive = value.slice(0, match.index).trim();
- const justification = value.slice(match.index + match[0].length).trim();
- return { directivePart: directive, justificationPart: justification };
- }
- /**
- * Parses a directive comment into directive text and value.
- * @param {string} string The string with the directive to be parsed.
- * @returns {DirectiveComment|undefined} The parsed directive or `undefined` if the directive is invalid.
- */
- parseDirective(string) {
- const { directivePart, justificationPart } =
- this.#extractDirectiveComment(string);
- const match = directivesPattern.exec(directivePart);
- if (!match) {
- return undefined;
- }
- const directiveText = match[1];
- const directiveValue = directivePart.slice(
- match.index + directiveText.length,
- );
- return new DirectiveComment(
- directiveText,
- directiveValue.trim(),
- justificationPart,
- );
- }
- }
- /**
- * @fileoverview A collection of helper classes for implementing `SourceCode`.
- * @author Nicholas C. Zakas
- */
- /* eslint class-methods-use-this: off -- Required to complete interface. */
- //-----------------------------------------------------------------------------
- // Type Definitions
- //-----------------------------------------------------------------------------
- /** @typedef {$eslintcore.VisitTraversalStep} VisitTraversalStep */
- /** @typedef {$eslintcore.CallTraversalStep} CallTraversalStep */
- /** @typedef {$eslintcore.TraversalStep} TraversalStep */
- /** @typedef {$eslintcore.SourceLocation} SourceLocation */
- /** @typedef {$eslintcore.SourceLocationWithOffset} SourceLocationWithOffset */
- /** @typedef {$eslintcore.SourceRange} SourceRange */
- /** @typedef {$eslintcore.Directive} IDirective */
- /** @typedef {$eslintcore.DirectiveType} DirectiveType */
- /** @typedef {$eslintcore.SourceCodeBaseTypeOptions} SourceCodeBaseTypeOptions */
- /**
- * @typedef {import("@eslint/core").TextSourceCode<Options>} TextSourceCode<Options>
- * @template {SourceCodeBaseTypeOptions} [Options=SourceCodeBaseTypeOptions]
- */
- //-----------------------------------------------------------------------------
- // Helpers
- //-----------------------------------------------------------------------------
- /**
- * Determines if a node has ESTree-style loc information.
- * @param {object} node The node to check.
- * @returns {node is {loc:SourceLocation}} `true` if the node has ESTree-style loc information, `false` if not.
- */
- function hasESTreeStyleLoc(node) {
- return "loc" in node;
- }
- /**
- * Determines if a node has position-style loc information.
- * @param {object} node The node to check.
- * @returns {node is {position:SourceLocation}} `true` if the node has position-style range information, `false` if not.
- */
- function hasPosStyleLoc(node) {
- return "position" in node;
- }
- /**
- * Determines if a node has ESTree-style range information.
- * @param {object} node The node to check.
- * @returns {node is {range:SourceRange}} `true` if the node has ESTree-style range information, `false` if not.
- */
- function hasESTreeStyleRange(node) {
- return "range" in node;
- }
- /**
- * Determines if a node has position-style range information.
- * @param {object} node The node to check.
- * @returns {node is {position:SourceLocationWithOffset}} `true` if the node has position-style range information, `false` if not.
- */
- function hasPosStyleRange(node) {
- return "position" in node;
- }
- /**
- * Performs binary search to find the line number containing a given target index.
- * Returns the lower bound - the index of the first element greater than the target.
- * **Please note that the `lineStartIndices` should be sorted in ascending order**.
- * - Time Complexity: O(log n) - Significantly faster than linear search for large files.
- * @param {number[]} lineStartIndices Sorted array of line start indices.
- * @param {number} targetIndex The target index to find the line number for.
- * @returns {number} The line number for the target index.
- */
- function findLineNumberBinarySearch(lineStartIndices, targetIndex) {
- let low = 0;
- let high = lineStartIndices.length - 1;
- while (low < high) {
- const mid = ((low + high) / 2) | 0; // Use bitwise OR to floor the division.
- if (targetIndex < lineStartIndices[mid]) {
- high = mid;
- } else {
- low = mid + 1;
- }
- }
- return low;
- }
- //-----------------------------------------------------------------------------
- // Exports
- //-----------------------------------------------------------------------------
- /**
- * A class to represent a step in the traversal process where a node is visited.
- * @implements {VisitTraversalStep}
- */
- class VisitNodeStep {
- /**
- * 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 {object}
- */
- target;
- /**
- * The phase of the step.
- * @type {1|2}
- */
- phase;
- /**
- * The arguments of the step.
- * @type {Array<any>}
- */
- args;
- /**
- * Creates a new instance.
- * @param {Object} options The options for the step.
- * @param {object} options.target The target of the step.
- * @param {1|2} options.phase The phase of the step.
- * @param {Array<any>} options.args The arguments of the step.
- */
- constructor({ target, phase, args }) {
- this.target = target;
- this.phase = phase;
- this.args = args;
- }
- }
- /**
- * A class to represent a step in the traversal process where a
- * method is called.
- * @implements {CallTraversalStep}
- */
- class CallMethodStep {
- /**
- * The type of the step.
- * @type {"call"}
- * @readonly
- */
- type = "call";
- /**
- * The kind of the step. Represents the same data as the `type` property
- * but it's a number for performance.
- * @type {2}
- * @readonly
- */
- kind = 2;
- /**
- * The name of the method to call.
- * @type {string}
- */
- target;
- /**
- * The arguments to pass to the method.
- * @type {Array<any>}
- */
- args;
- /**
- * Creates a new instance.
- * @param {Object} options The options for the step.
- * @param {string} options.target The target of the step.
- * @param {Array<any>} options.args The arguments of the step.
- */
- constructor({ target, args }) {
- this.target = target;
- this.args = args;
- }
- }
- /**
- * A class to represent a directive comment.
- * @implements {IDirective}
- */
- class Directive {
- /**
- * The type of directive.
- * @type {DirectiveType}
- * @readonly
- */
- type;
- /**
- * The node representing the directive.
- * @type {unknown}
- * @readonly
- */
- node;
- /**
- * Everything after the "eslint-disable" portion of the directive,
- * but before the "--" that indicates the justification.
- * @type {string}
- * @readonly
- */
- value;
- /**
- * The justification for the directive.
- * @type {string}
- * @readonly
- */
- justification;
- /**
- * Creates a new instance.
- * @param {Object} options The options for the directive.
- * @param {"disable"|"enable"|"disable-next-line"|"disable-line"} options.type The type of directive.
- * @param {unknown} options.node The node representing the directive.
- * @param {string} options.value The value of the directive.
- * @param {string} options.justification The justification for the directive.
- */
- constructor({ type, node, value, justification }) {
- this.type = type;
- this.node = node;
- this.value = value;
- this.justification = justification;
- }
- }
- /**
- * Source Code Base Object
- * @template {SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}} [Options=SourceCodeBaseTypeOptions & {RootNode: object, SyntaxElementWithLoc: object}]
- * @implements {TextSourceCode<Options>}
- */
- class TextSourceCodeBase {
- /**
- * The lines of text in the source code.
- * @type {Array<string>}
- */
- #lines = [];
- /**
- * The indices of the start of each line in the source code.
- * @type {Array<number>}
- */
- #lineStartIndices = [0];
- /**
- * The pattern to match lineEndings in the source code.
- * @type {RegExp}
- */
- #lineEndingPattern;
- /**
- * The AST of the source code.
- * @type {Options['RootNode']}
- */
- ast;
- /**
- * The text of the source code.
- * @type {string}
- */
- text;
- /**
- * Creates a new instance.
- * @param {Object} options The options for the instance.
- * @param {string} options.text The source code text.
- * @param {Options['RootNode']} options.ast The root AST node.
- * @param {RegExp} [options.lineEndingPattern] The pattern to match lineEndings in the source code. Defaults to `/\r?\n/u`.
- */
- constructor({ text, ast, lineEndingPattern = /\r?\n/u }) {
- this.ast = ast;
- this.text = text;
- // Remove the global(`g`) and sticky(`y`) flags from the `lineEndingPattern` to avoid issues with lastIndex.
- this.#lineEndingPattern = new RegExp(
- lineEndingPattern.source,
- lineEndingPattern.flags.replace(/[gy]/gu, ""),
- );
- }
- /**
- * Finds the next line in the source text and updates `#lines` and `#lineStartIndices`.
- * @param {string} text The text to search for the next line.
- * @returns {boolean} `true` if a next line was found, `false` otherwise.
- */
- #findNextLine(text) {
- const match = this.#lineEndingPattern.exec(text);
- if (!match) {
- return false;
- }
- this.#lines.push(text.slice(0, match.index));
- this.#lineStartIndices.push(
- (this.#lineStartIndices.at(-1) ?? 0) +
- match.index +
- match[0].length,
- );
- return true;
- }
- /**
- * Ensures `#lines` is lazily calculated from the source text.
- * @returns {void}
- */
- #ensureLines() {
- // If `#lines` has already been calculated, do nothing.
- if (this.#lines.length === this.#lineStartIndices.length) {
- return;
- }
- while (
- this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
- ) {
- // Continue parsing until no more matches are found.
- }
- this.#lines.push(this.text.slice(this.#lineStartIndices.at(-1)));
- Object.freeze(this.#lines);
- }
- /**
- * Ensures `#lineStartIndices` is lazily calculated up to the specified index.
- * @param {number} index The index of a character in a file.
- * @returns {void}
- */
- #ensureLineStartIndicesFromIndex(index) {
- // If we've already parsed up to or beyond this index, do nothing.
- if (index <= (this.#lineStartIndices.at(-1) ?? 0)) {
- return;
- }
- while (
- index > (this.#lineStartIndices.at(-1) ?? 0) &&
- this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
- ) {
- // Continue parsing until no more matches are found.
- }
- }
- /**
- * Ensures `#lineStartIndices` is lazily calculated up to the specified loc.
- * @param {Object} loc A line/column location.
- * @param {number} loc.line The line number of the location. (0 or 1-indexed based on language.)
- * @param {number} lineStart The line number at which the parser starts counting.
- * @returns {void}
- */
- #ensureLineStartIndicesFromLoc(loc, lineStart) {
- // Calculate line indices up to the potentially next line, as it is needed for the follow‑up calculation.
- const nextLocLineIndex = loc.line - lineStart + 1;
- const lastCalculatedLineIndex = this.#lineStartIndices.length - 1;
- let additionalLinesNeeded = nextLocLineIndex - lastCalculatedLineIndex;
- // If we've already parsed up to or beyond this line, do nothing.
- if (additionalLinesNeeded <= 0) {
- return;
- }
- while (
- additionalLinesNeeded > 0 &&
- this.#findNextLine(this.text.slice(this.#lineStartIndices.at(-1)))
- ) {
- // Continue parsing until no more matches are found or we have enough lines.
- additionalLinesNeeded -= 1;
- }
- }
- /**
- * Returns the loc information for the given node or token.
- * @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the loc information for.
- * @returns {SourceLocation} The loc information for the node or token.
- * @throws {Error} If the node or token does not have loc information.
- */
- getLoc(nodeOrToken) {
- if (hasESTreeStyleLoc(nodeOrToken)) {
- return nodeOrToken.loc;
- }
- if (hasPosStyleLoc(nodeOrToken)) {
- return nodeOrToken.position;
- }
- throw new Error(
- "Custom getLoc() method must be implemented in the subclass.",
- );
- }
- /**
- * Converts a source text index into a `{ line: number, column: number }` pair.
- * @param {number} index The index of a character in a file.
- * @throws {TypeError|RangeError} If non-numeric index or index out of range.
- * @returns {{line: number, column: number}} A `{ line: number, column: number }` location object with 0 or 1-indexed line and 0 or 1-indexed column based on language.
- * @public
- */
- getLocFromIndex(index) {
- if (typeof index !== "number") {
- throw new TypeError("Expected `index` to be a number.");
- }
- if (index < 0 || index > this.text.length) {
- throw new RangeError(
- `Index out of range (requested index ${index}, but source text has length ${this.text.length}).`,
- );
- }
- const {
- start: { line: lineStart, column: columnStart },
- end: { line: lineEnd, column: columnEnd },
- } = this.getLoc(this.ast);
- // If the index is at the start, return the start location of the root node.
- if (index === 0) {
- return {
- line: lineStart,
- column: columnStart,
- };
- }
- // If the index is `this.text.length`, return the location one "spot" past the last character of the file.
- if (index === this.text.length) {
- return {
- line: lineEnd,
- column: columnEnd,
- };
- }
- // Ensure `#lineStartIndices` are lazily calculated.
- this.#ensureLineStartIndicesFromIndex(index);
- /*
- * To figure out which line `index` is on, determine the last place at which index could
- * be inserted into `#lineStartIndices` to keep the list sorted.
- */
- const lineNumber =
- (index >= (this.#lineStartIndices.at(-1) ?? 0)
- ? this.#lineStartIndices.length
- : findLineNumberBinarySearch(this.#lineStartIndices, index)) -
- 1 +
- lineStart;
- return {
- line: lineNumber,
- column:
- index -
- this.#lineStartIndices[lineNumber - lineStart] +
- columnStart,
- };
- }
- /**
- * Converts a `{ line: number, column: number }` pair into a source text index.
- * @param {Object} loc A line/column location.
- * @param {number} loc.line The line number of the location. (0 or 1-indexed based on language.)
- * @param {number} loc.column The column number of the location. (0 or 1-indexed based on language.)
- * @throws {TypeError|RangeError} If `loc` is not an object with a numeric
- * `line` and `column`, if the `line` is less than or equal to zero or
- * the `line` or `column` is out of the expected range.
- * @returns {number} The index of the line/column location in a file.
- * @public
- */
- getIndexFromLoc(loc) {
- if (
- loc === null ||
- typeof loc !== "object" ||
- typeof loc.line !== "number" ||
- typeof loc.column !== "number"
- ) {
- throw new TypeError(
- "Expected `loc` to be an object with numeric `line` and `column` properties.",
- );
- }
- const {
- start: { line: lineStart, column: columnStart },
- end: { line: lineEnd, column: columnEnd },
- } = this.getLoc(this.ast);
- if (loc.line < lineStart || lineEnd < loc.line) {
- throw new RangeError(
- `Line number out of range (line ${loc.line} requested). Valid range: ${lineStart}-${lineEnd}`,
- );
- }
- // If the loc is at the start, return the start index of the root node.
- if (loc.line === lineStart && loc.column === columnStart) {
- return 0;
- }
- // If the loc is at the end, return the index one "spot" past the last character of the file.
- if (loc.line === lineEnd && loc.column === columnEnd) {
- return this.text.length;
- }
- // Ensure `#lineStartIndices` are lazily calculated.
- this.#ensureLineStartIndicesFromLoc(loc, lineStart);
- const isLastLine = loc.line === lineEnd;
- const lineStartIndex = this.#lineStartIndices[loc.line - lineStart];
- const lineEndIndex = isLastLine
- ? this.text.length
- : this.#lineStartIndices[loc.line - lineStart + 1];
- const positionIndex = lineStartIndex + loc.column - columnStart;
- if (
- loc.column < columnStart ||
- (isLastLine && positionIndex > lineEndIndex) ||
- (!isLastLine && positionIndex >= lineEndIndex)
- ) {
- throw new RangeError(
- `Column number out of range (column ${loc.column} requested). Valid range for line ${loc.line}: ${columnStart}-${lineEndIndex - lineStartIndex + columnStart + (isLastLine ? 0 : -1)}`,
- );
- }
- return positionIndex;
- }
- /**
- * Returns the range information for the given node or token.
- * @param {Options['SyntaxElementWithLoc']} nodeOrToken The node or token to get the range information for.
- * @returns {SourceRange} The range information for the node or token.
- * @throws {Error} If the node or token does not have range information.
- */
- getRange(nodeOrToken) {
- if (hasESTreeStyleRange(nodeOrToken)) {
- return nodeOrToken.range;
- }
- if (hasPosStyleRange(nodeOrToken)) {
- return [
- nodeOrToken.position.start.offset,
- nodeOrToken.position.end.offset,
- ];
- }
- throw new Error(
- "Custom getRange() method must be implemented in the subclass.",
- );
- }
- /* eslint-disable no-unused-vars -- Required to complete interface. */
- /**
- * Returns the parent of the given node.
- * @param {Options['SyntaxElementWithLoc']} node The node to get the parent of.
- * @returns {Options['SyntaxElementWithLoc']|undefined} The parent of the node.
- * @throws {Error} If the method is not implemented in the subclass.
- */
- getParent(node) {
- throw new Error("Not implemented.");
- }
- /* eslint-enable no-unused-vars -- Required to complete interface. */
- /**
- * Gets all the ancestors of a given node
- * @param {Options['SyntaxElementWithLoc']} node The node
- * @returns {Array<Options['SyntaxElementWithLoc']>} 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.getParent(node);
- ancestor;
- ancestor = this.getParent(ancestor)
- ) {
- ancestorsStartingAtParent.push(ancestor);
- }
- return ancestorsStartingAtParent.reverse();
- }
- /**
- * Gets the source code for the given node.
- * @param {Options['SyntaxElementWithLoc']} [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, afterCount) {
- if (node) {
- const range = this.getRange(node);
- return this.text.slice(
- Math.max(range[0] - (beforeCount || 0), 0),
- range[1] + (afterCount || 0),
- );
- }
- return this.text;
- }
- /**
- * Gets the entire source text split into an array of lines.
- * @returns {Array<string>} The source text as an array of lines.
- * @public
- */
- get lines() {
- this.#ensureLines(); // Ensure `#lines` is lazily calculated.
- return this.#lines;
- }
- /**
- * 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() {
- throw new Error("Not implemented.");
- }
- }
- export { CallMethodStep, ConfigCommentParser, Directive, TextSourceCodeBase, VisitNodeStep };
|