UNPKG

vue-eslint-parser

Version:

The ESLint custom parser for `.vue` files.

1,484 lines (1,465 loc) 319 kB
/** * @author Toru Nagashima <https://github.com/mysticatea> * See LICENSE file in root directory for full license. */ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __exportAll = (all, no_symbols) => { let target = {}; for (var name in all) { __defProp(target, name, { get: all[name], enumerable: true }); } if (!no_symbols) { __defProp(target, Symbol.toStringTag, { value: "Module" }); } return target; }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) { __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } } } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion let path = require("path"); path = __toESM(path); let eslint_visitor_keys = require("eslint-visitor-keys"); eslint_visitor_keys = __toESM(eslint_visitor_keys); let assert = require("assert"); assert = __toESM(assert); let debug = require("debug"); debug = __toESM(debug); let eslint_scope = require("eslint-scope"); eslint_scope = __toESM(eslint_scope); let semver = require("semver"); let module$1 = require("module"); let espree = require("espree"); espree = __toESM(espree); let events = require("events"); events = __toESM(events); let esquery = require("esquery"); esquery = __toESM(esquery); //#region src/ast/errors.ts /** * Check whether the given value has acorn style location information. * @param x The value to check. * @returns `true` if the value has acorn style location information. */ function isAcornStyleParseError(x) { return typeof x.message === "string" && typeof x.pos === "number" && typeof x.loc === "object" && x.loc !== null && typeof x.loc.line === "number" && typeof x.loc.column === "number"; } /** * Check whether the given value is probably a TSError. * @param x The value to check. * @returns `true` if the given value is probably a TSError. */ function isTSError(x) { return !(x instanceof ParseError) && typeof x.message === "string" && typeof x.index === "number" && typeof x.lineNumber === "number" && typeof x.column === "number" && x.name === "TSError"; } /** * HTML parse errors. */ var ParseError = class ParseError extends SyntaxError { code; index; lineNumber; column; /** * Create new parser error object. * @param code The error code. See also: https://html.spec.whatwg.org/multipage/parsing.html#parse-errors * @param offset The offset number of this error. * @param line The line number of this error. * @param column The column number of this error. */ static fromCode(code, offset, line, column) { return new ParseError(code, code, offset, line, column); } /** * Normalize the error object. * @param x The error object to normalize. */ static normalize(x) { if (isTSError(x)) return new ParseError(x.message, void 0, x.index, x.lineNumber, x.column); if (ParseError.isParseError(x)) return x; if (isAcornStyleParseError(x)) return new ParseError(x.message, void 0, x.pos, x.loc.line, x.loc.column); return null; } /** * Initialize this ParseError instance. * @param message The error message. * @param code The error code. See also: https://html.spec.whatwg.org/multipage/parsing.html#parse-errors * @param offset The offset number of this error. * @param line The line number of this error. * @param column The column number of this error. */ constructor(message, code, offset, line, column) { super(message); this.code = code; this.index = offset; this.lineNumber = line; this.column = column; } /** * Type guard for ParseError. * @param x The value to check. * @returns `true` if the value has `message`, `pos`, `loc` properties. */ static isParseError(x) { return x instanceof ParseError || typeof x.message === "string" && typeof x.index === "number" && typeof x.lineNumber === "number" && typeof x.column === "number"; } }; //#endregion //#region src/ast/nodes.ts /** * Constants of namespaces. * @see https://infra.spec.whatwg.org/#namespaces */ const NS = Object.freeze({ HTML: "http://www.w3.org/1999/xhtml", MathML: "http://www.w3.org/1998/Math/MathML", SVG: "http://www.w3.org/2000/svg", XLink: "http://www.w3.org/1999/xlink", XML: "http://www.w3.org/XML/1998/namespace", XMLNS: "http://www.w3.org/2000/xmlns/" }); //#endregion //#region src/ast/traverse.ts const KEYS = eslint_visitor_keys.unionWith({ VAttribute: ["key", "value"], VDirectiveKey: [ "name", "argument", "modifiers" ], VDocumentFragment: ["children"], VElement: [ "startTag", "children", "endTag" ], VEndTag: [], VExpressionContainer: ["expression"], VFilter: ["callee", "arguments"], VFilterSequenceExpression: ["expression", "filters"], VForExpression: ["left", "right"], VIdentifier: [], VLiteral: [], VOnExpression: ["body"], VSlotScopeExpression: ["params"], VStartTag: ["attributes"], VText: [], VGenericExpression: ["params"] }); /** * Check that the given key should be traversed or not. * @param key The key to check. * @param value The value of the key in the node. * @returns `true` if the key should be traversed. */ function fallbackKeysFilter(key, value = null) { return key !== "comments" && key !== "leadingComments" && key !== "loc" && key !== "parent" && key !== "range" && key !== "tokens" && key !== "trailingComments" && value !== null && typeof value === "object" && (typeof value.type === "string" || Array.isArray(value)); } /** * Get the keys of the given node to traverse it. * @param node The node to get. * @returns The keys to traverse. */ function getFallbackKeys(node) { return Object.keys(node).filter((key) => fallbackKeysFilter(key, node[key])); } /** * Check wheather a given value is a node. * @param x The value to check. * @returns `true` if the value is a node. */ function isNode(x) { return x !== null && typeof x === "object" && typeof x.type === "string"; } /** * Traverse the given node. * @param node The node to traverse. * @param parent The parent node. * @param visitor The node visitor. */ function traverse(node, parent, visitor) { let i = 0; let j = 0; visitor.enterNode(node, parent); const keys = (visitor.visitorKeys ?? KEYS)[node.type] ?? getFallbackKeys(node); for (i = 0; i < keys.length; ++i) { const child = node[keys[i]]; if (Array.isArray(child)) { for (j = 0; j < child.length; ++j) if (isNode(child[j])) traverse(child[j], node, visitor); } else if (isNode(child)) traverse(child, node, visitor); } visitor.leaveNode(node, parent); } /** * Traverse the given AST tree. * @param node Root node to traverse. * @param visitor Visitor. */ function traverseNodes(node, visitor) { traverse(node, null, visitor); } //#endregion //#region src/ast/index.ts var ast_exports = /* @__PURE__ */ __exportAll({ KEYS: () => KEYS, NS: () => NS, ParseError: () => ParseError, getFallbackKeys: () => getFallbackKeys, traverseNodes: () => traverseNodes }); //#endregion //#region src/utils/utils.ts /** * @see https://github.com/vuejs/vue-next/blob/48de8a42b7fed7a03f7f1ff5d53d6a704252cafe/packages/shared/src/index.ts#L109 */ function camelize(str) { return str.replace(/-(\w)/gu, (_, c) => c ? c.toUpperCase() : ""); } /** * A binary search implementation that finds the index at which `predicate` * stops returning `true` and starts returning `false` (consistently) when run * on the items of the array. It **assumes** that mapping the array via the * predicate results in the shape `[...true[], ...false[]]`. *For any other case * the result is unpredictable*. * * This is the base implementation of the `sortedIndex` functions which define * the predicate for the user, for common use-cases. * * It is similar to `findIndex`, but runs at O(logN), whereas the latter is * general purpose function which runs on any array and predicate, but runs at * O(N) time. * * MIT License | Copyright (c) 2018 remeda | https://remedajs.com/ * * The implementation is copied from remeda package: * https://github.com/remeda/remeda/blob/df5fe74841c07bc356bbaa2c89bc7ba0cafafd0a/packages/remeda/src/internal/binarySearchCutoffIndex.ts#L15 */ function binarySearchCutoffIndex(array, predicate) { let lowIndex = 0; let highIndex = array.length; while (lowIndex < highIndex) { const pivotIndex = lowIndex + highIndex >>> 1; const pivot = array[pivotIndex]; if (predicate(pivot, pivotIndex, array)) lowIndex = pivotIndex + 1; else highIndex = pivotIndex; } return highIndex; } /** * Find the insertion position (index) of an item in an array with items sorted * in ascending order; so that `splice(sortedIndex, 0, item)` would result in * maintaining the array's sort-ness. The array can contain duplicates. * If the item already exists in the array the index would be of the *last* * occurrence of the item. * * Runs in O(logN) time. * * @param item - The item to insert. * @returns Insertion index (In the range 0..data.length). * @signature * R.sortedLastIndex(item)(data) * @example * R.pipe(['a','a','b','c','c'], sortedLastIndex('c')) // => 5 * * MIT License | Copyright (c) 2018 remeda | https://remedajs.com/ * * The implementation is copied from remeda package: * https://github.com/remeda/remeda/blob/df5fe74841c07bc356bbaa2c89bc7ba0cafafd0a/packages/remeda/src/sortedLastIndex.ts#L51 */ function sortedLastIndex(array, item) { return binarySearchCutoffIndex(array, (pivot) => pivot <= item); } /** * Find the insertion position (index) of an item in an array with items sorted * in ascending order using a value function; so that * `splice(sortedIndex, 0, item)` would result in maintaining the arrays sort- * ness. The array can contain duplicates. * If the item already exists in the array the index would be of the *first* * occurrence of the item. * * Runs in O(logN) time. * * See also: * * `findIndex` - scans a possibly unsorted array in-order (linear search). * * `sortedIndex` - like this function, but doesn't take a callbackfn. * * `sortedLastIndexBy` - like this function, but finds the last suitable index. * * `sortedLastIndex` - like `sortedIndex`, but finds the last suitable index. * * `rankBy` - scans a possibly unsorted array in-order, returning the index based on a sorting criteria. * * @param data - The (ascending) sorted array. * @param item - The item to insert. * @param valueFunction - All comparisons would be performed on the result of * calling this function on each compared item. Preferably this function should * return a `number` or `string`. This function should be the same as the one * provided to sortBy to sort the array. The function is called exactly once on * each items that is compared against in the array, and once at the beginning * on `item`. When called on `item` the `index` argument is `undefined`. * @returns Insertion index (In the range 0..data.length). * @signature * R.sortedIndexBy(data, item, valueFunction) * @example * R.sortedIndexBy([{age:20},{age:22}],{age:21},prop('age')) // => 1 * * MIT License | Copyright (c) 2018 remeda | https://remedajs.com/ * * The implementation is copied from remeda package: * https://github.com/remeda/remeda/blob/df5fe74841c07bc356bbaa2c89bc7ba0cafafd0a/packages/remeda/src/sortedIndexBy.ts#L37 */ function sortedIndexBy(array, item, valueFunction) { const value = valueFunction(item, void 0, array); return binarySearchCutoffIndex(array, (pivot, index) => valueFunction(pivot, index, array) < value); } /** * Find the insertion position (index) of an item in an array with items sorted * in ascending order using a value function; so that * `splice(sortedIndex, 0, item)` would result in maintaining the arrays sort- * ness. The array can contain duplicates. * If the item already exists in the array the index would be of the *last* * occurrence of the item. * * Runs in O(logN) time. * * See also: * * `findIndex` - scans a possibly unsorted array in-order (linear search). * * `sortedLastIndex` - a simplified version of this function, without a callbackfn. * * `sortedIndexBy` - like this function, but returns the first suitable index. * * `sortedIndex` - like `sortedLastIndex` but without a callbackfn. * * `rankBy` - scans a possibly unsorted array in-order, returning the index based on a sorting criteria. * * @param data - The (ascending) sorted array. * @param item - The item to insert. * @param valueFunction - All comparisons would be performed on the result of * calling this function on each compared item. Preferably this function should * return a `number` or `string`. This function should be the same as the one * provided to sortBy to sort the array. The function is called exactly once on * each items that is compared against in the array, and once at the beginning * on `item`. When called on `item` the `index` argument is `undefined`. * @returns Insertion index (In the range 0..data.length). * @signature * R.sortedLastIndexBy(data, item, valueFunction) * @example * R.sortedLastIndexBy([{age:20},{age:22}],{age:21},prop('age')) // => 1 * * MIT License | Copyright (c) 2018 remeda | https://remedajs.com/ * * The implementation is copied from remeda package: * https://github.com/remeda/remeda/blob/df5fe74841c07bc356bbaa2c89bc7ba0cafafd0a/packages/remeda/src/sortedLastIndexBy.ts#L37 */ function sortedLastIndexBy(array, item, valueFunction) { const value = valueFunction(item, void 0, array); return binarySearchCutoffIndex(array, (pivot, index) => valueFunction(pivot, index, array) <= value); } /** * Creates a duplicate-free version of an array. * * This function takes an array and returns a new array containing only the unique values * from the original array, preserving the order of first occurrence. * * @template T - The type of elements in the array. * @param {T[]} arr - The array to process. * @returns {T[]} A new array with only unique values from the original array. * * @example * const array = [1, 2, 2, 3, 4, 4, 5]; * const result = uniq(array); * // result will be [1, 2, 3, 4, 5] * * MIT © Viva Republica, Inc. | https://es-toolkit.dev/ * * The implementation is copied from es-toolkit package: * https://github.com/toss/es-toolkit/blob/16709839f131269b84cdd96e9645df52648ccedf/src/array/uniq.ts#L16 */ function uniq(arr) { return Array.from(new Set(arr)); } /** * Returns the intersection of multiple arrays. * * This function takes multiple arrays and returns a new array containing the elements that are * present in all provided arrays. It effectively filters out any elements that are not found * in every array. * * @template T - The type of elements in the arrays. * @param {...(ArrayLike<T> | null | undefined)} arrays - The arrays to compare. * @returns {T[]} A new array containing the elements that are present in all arrays. * * @example * const array1 = [1, 2, 3, 4, 5]; * const array2 = [3, 4, 5, 6, 7]; * const result = intersection(array1, array2); * // result will be [3, 4, 5] since these elements are in both arrays. * * MIT © Viva Republica, Inc. | https://es-toolkit.dev/ * * The implementation is copied from es-toolkit package: * https://github.com/toss/es-toolkit/blob/16709839f131269b84cdd96e9645df52648ccedf/src/compat/array/intersection.ts#L22 * https://github.com/toss/es-toolkit/blob/16709839f131269b84cdd96e9645df52648ccedf/src/array/intersection.ts#L19 */ function intersection(...arrays) { if (arrays.length === 0) return []; let result = uniq(arrays[0]); for (let i = 1; i < arrays.length; i++) { const array = arrays[i]; const secondSet = new Set(array); result = result.filter((item) => secondSet.has(item)); } return result; } /** * This function takes multiple arrays and returns a new array containing only the unique values * from all input arrays, preserving the order of their first occurrence. * * @template T - The type of elements in the arrays. * @param {Array<ArrayLike<T> | null | undefined>} arrays - The arrays to inspect. * @returns {T[]} Returns the new array of combined unique values. * * @example * // Returns [2, 1] * union([2], [1, 2]); * * @example * // Returns [2, 1, 3] * union([2], [1, 2], [2, 3]); * * @example * // Returns [1, 3, 2, [5], [4]] (does not deeply flatten nested arrays) * union([1, 3, 2], [1, [5]], [2, [4]]); * * @example * // Returns [0, 2, 1] (ignores non-array values like 3 and { '0': 1 }) * union([0], 3, { '0': 1 }, null, [2, 1]); * @example * // Returns [0, 'a', 2, 1] (treats array-like object { 0: 'a', length: 1 } as a valid array) * union([0], { 0: 'a', length: 1 }, [2, 1]); * * MIT © Viva Republica, Inc. | https://es-toolkit.dev/ * * The implementation is copied from es-toolkit package: * https://github.com/toss/es-toolkit/blob/16709839f131269b84cdd96e9645df52648ccedf/src/compat/array/union.ts#L61 * https://github.com/toss/es-toolkit/blob/16709839f131269b84cdd96e9645df52648ccedf/src/compat/array/flattenDepth.ts#L21 */ function union(...arrays) { return uniq(arrays.flat()); } //#endregion //#region src/common/lines-and-columns.ts /** * A class for getting lines and columns location. */ var LinesAndColumns = class { ltOffsets; /** * Initialize. * @param ltOffsets The list of the offset of line terminators. */ constructor(ltOffsets) { this.ltOffsets = ltOffsets; } /** * Calculate the location of the given index. * @param index The index to calculate their location. * @returns The location of the index. */ getLocFromIndex(index) { const line = sortedLastIndex(this.ltOffsets, index) + 1; return { line, column: index - (line === 1 ? 0 : this.ltOffsets[line - 2]) }; } createOffsetLocationCalculator(offset) { return { getFixOffset() { return offset; }, getLocFromIndex: this.getLocFromIndex.bind(this) }; } }; //#endregion //#region src/common/location-calculator.ts /** * @author Toru Nagashima <https://github.com/mysticatea> * @copyright 2017 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ /** * Location calculators. * * HTML tokenizers remove several characters to handle HTML entities and line terminators. * Tokens have the processed text as their value, but tokens have offsets and locations in the original text. * This calculator calculates the original locations from the processed texts. * * This calculator will be used for: * * - Adjusts the locations of script ASTs. * - Creates expression containers in postprocess. */ var LocationCalculatorForHtml = class LocationCalculatorForHtml extends LinesAndColumns { gapOffsets; baseOffset; baseIndexOfGap; shiftOffset; /** * Initialize this calculator. * @param gapOffsets The list of the offset of removed characters in tokenization phase. * @param ltOffsets The list of the offset of line terminators. * @param baseOffset The base offset to calculate locations. * @param shiftOffset The shift offset to calculate locations. */ constructor(gapOffsets, ltOffsets, baseOffset, shiftOffset = 0) { super(ltOffsets); this.gapOffsets = gapOffsets; this.ltOffsets = ltOffsets; this.baseOffset = baseOffset ?? 0; this.baseIndexOfGap = this.baseOffset === 0 ? 0 : sortedLastIndex(gapOffsets, this.baseOffset); this.shiftOffset = shiftOffset; } /** * Get sub calculator which have the given base offset. * @param offset The base offset of new sub calculator. * @returns Sub calculator. */ getSubCalculatorAfter(offset) { return new LocationCalculatorForHtml(this.gapOffsets, this.ltOffsets, this.baseOffset + offset, this.shiftOffset); } /** * Get sub calculator that shifts the given offset. * @param offset The shift of new sub calculator. * @returns Sub calculator. */ getSubCalculatorShift(offset) { return new LocationCalculatorForHtml(this.gapOffsets, this.ltOffsets, this.baseOffset, this.shiftOffset + offset); } /** * Calculate gap at the given index. * @param index The index to calculate gap. */ _getGap(index) { const offsets = this.gapOffsets; let g0 = sortedLastIndex(offsets, index + this.baseOffset); let pos = index + this.baseOffset + g0 - this.baseIndexOfGap; while (g0 < offsets.length && offsets[g0] <= pos) { g0 += 1; pos += 1; } return g0 - this.baseIndexOfGap; } /** * Calculate the location of the given index. * @param index The index to calculate their location. * @returns The location of the index. */ getLocation(index) { return this.getLocFromIndex(this.getOffsetWithGap(index)); } /** * Calculate the offset of the given index. * @param index The index to calculate their location. * @returns The offset of the index. */ getOffsetWithGap(index) { return index + this.getFixOffset(index); } /** * Gets the fix location offset of the given offset with using the base offset of this calculator. * @param offset The offset to modify. */ getFixOffset(offset) { const shiftOffset = this.shiftOffset; const gap = this._getGap(offset + shiftOffset); return this.baseOffset + gap + shiftOffset; } }; //#endregion //#region src/common/debug.ts /** * @author Toru Nagashima <https://github.com/mysticatea> * @copyright 2017 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ const debug$1 = (0, debug.default)("vue-eslint-parser"); //#endregion //#region src/common/ast-utils.ts /** * Check whether the node is a `<script>` element. * @param node The node to check. * @returns `true` if the node is a `<script>` element. */ function isScriptElement(node) { return node.type === "VElement" && node.name === "script"; } /** * Checks whether the given script element is `<script setup>`. */ function isScriptSetupElement(script) { return isScriptElement(script) && script.startTag.attributes.some((attr) => !attr.directive && attr.key.name === "setup"); } /** * Check whether the node is a `<template>` element. * @param node The node to check. * @returns `true` if the node is a `<template>` element. */ function isTemplateElement(node) { return node.type === "VElement" && node.name === "template"; } /** * Check whether the node is a `<style>` element. * @param node The node to check. * @returns `true` if the node is a `<style>` element. */ function isStyleElement(node) { return node.type === "VElement" && node.name === "style"; } /** * Get the belonging document of the given node. * @param leafNode The node to get. * @returns The belonging document. */ function getOwnerDocument(leafNode) { let node = leafNode; while (node != null && node.type !== "VDocumentFragment") node = node.parent; return node; } /** * Check whether the attribute node is a `lang` attribute. * @param attribute The attribute node to check. * @returns `true` if the attribute node is a `lang` attribute. */ function isLang(attribute) { return attribute.directive === false && attribute.key.name === "lang"; } /** * Get the `lang` attribute value from a given element. * @param element The element to get. * @param defaultLang The default value of the `lang` attribute. * @returns The `lang` attribute value. */ function getLang(element) { return (element?.startTag.attributes.find(isLang))?.value?.value || null; } /** * Check whether the given script element has `lang="ts"`. * @param element The element to check. * @returns The given script element has `lang="ts"`. */ function isTSLang(element) { const lang = getLang(element); return lang === "ts" || lang === "tsx"; } /** * Find `generic` directive from given `<script>` element */ function findGenericDirective(element) { return element.startTag.attributes.find((attr) => attr.directive && attr.value?.expression?.type === "VGenericExpression") || null; } //#endregion //#region src/common/parser-object.ts function isParserObject(value) { return isEnhancedParserObject(value) || isBasicParserObject(value); } function isEnhancedParserObject(value) { return Boolean(value && typeof value.parseForESLint === "function"); } function isBasicParserObject(value) { return Boolean(value && typeof value.parse === "function"); } //#endregion //#region src/common/parser-options.ts function isSFCFile(parserOptions) { if (parserOptions.filePath === "<input>") return true; return path.extname(parserOptions.filePath || "unknown.vue") === ".vue"; } /** * Gets the script parser name from the given parser lang. */ function getScriptParser(parser, getParserLang) { if (isParserObject(parser)) return parser; if (parser && typeof parser === "object") { const parserLang = getParserLang(); const parserLangs = parserLang == null ? [] : typeof parserLang === "string" ? [parserLang] : parserLang; for (const lang of parserLangs) { const parserForLang = lang && parser[lang]; if (typeof parserForLang === "string" || isParserObject(parserForLang)) return parserForLang; } return parser.js; } return typeof parser === "string" ? parser : void 0; } function getParserLangFromSFC(doc) { if (doc) { const scripts = doc.children.filter(isScriptElement); const script = scripts.length === 2 && scripts.find(isScriptSetupElement) || scripts[0]; if (script) return getLang(script); } return null; } //#endregion //#region src/common/eslint-scope.ts let escopeCache = null; /** * Load the newest `eslint-scope` from the loaded ESLint or dependency. */ function getEslintScope() { return escopeCache ?? (escopeCache = getNewest()); } /** * Load the newest `eslint-scope` from the dependency. */ function getNewest() { let newest = eslint_scope; const userEscope = getEslintScopeFromUser(); if (userEscope.version != null && (0, semver.lte)(newest.version, userEscope.version)) newest = userEscope; return newest; } /** * Load `eslint-scope` from the user dir. */ function getEslintScopeFromUser() { try { const cwd = process.cwd(); return (0, module$1.createRequire)(path.default.join(cwd, "__placeholder__.js"))("eslint-scope"); } catch { return eslint_scope; } } //#endregion //#region src/common/espree.ts let espreeCache = null; /** * Gets the espree that the given ecmaVersion can parse. */ function getEspree() { return espreeCache ?? (espreeCache = getNewestEspree()); } function getEcmaVersionIfUseEspree(parserOptions) { if (parserOptions.parser != null && parserOptions.parser !== "espree") return; if (parserOptions.ecmaVersion === "latest" || parserOptions.ecmaVersion == null) return getDefaultEcmaVersion(); return normalizeEcmaVersion(parserOptions.ecmaVersion); } /** * Load `espree` from the user dir. */ function getEspreeFromUser() { try { const cwd = process.cwd(); return (0, module$1.createRequire)(path.default.join(cwd, "__placeholder__.js"))("espree"); } catch { return espree; } } /** * Load the newest `espree` from the dependency. */ function getNewestEspree() { let newest = espree; const userEspree = getEspreeFromUser(); if (userEspree.version != null && (0, semver.lte)(newest.version, userEspree.version)) newest = userEspree; return newest; } function getDefaultEcmaVersion() { return getLatestEcmaVersion(getEspree()); } /** * Normalize ECMAScript version */ function normalizeEcmaVersion(version) { if (version > 5 && version < 2015) return version + 2009; return version; } function getLatestEcmaVersion(espree$1) { return normalizeEcmaVersion(espree$1.latestEcmaVersion); } //#endregion //#region src/script-setup/parser-options.ts const DEFAULT_ECMA_VERSION = "latest"; const ANALYZE_SCOPE_DEFAULT_ECMA_VERSION = 2022; //#endregion //#region src/script/scope-analyzer.ts /** * Check whether the given reference is unique in the belonging array. * @param reference The current reference to check. * @param index The index of the reference. * @param references The belonging array of the reference. */ function isUnique(reference, index, references) { return index === 0 || reference.identifier !== references[index - 1].identifier; } /** * Check whether a given variable has that definition. * @param variable The variable to check. * @returns `true` if the variable has that definition. */ function hasDefinition(variable) { return variable.defs.length >= 1; } /** * Transform the given reference object. * @param reference The source reference object. * @returns The transformed reference object. */ function transformReference(reference) { const ret = { id: reference.identifier, mode: reference.isReadOnly() ? "r" : reference.isWriteOnly() ? "w" : "rw", variable: null, isValueReference: reference.isValueReference, isTypeReference: reference.isTypeReference }; Object.defineProperty(ret, "variable", { enumerable: false }); return ret; } /** * Transform the given variable object. * @param variable The source variable object. * @returns The transformed variable object. */ function transformVariable(variable, kind) { const ret = { id: variable.defs[0].name, kind, references: [] }; Object.defineProperty(ret, "references", { enumerable: false }); return ret; } /** * Get the `for` statement scope. * @param scope The global scope. * @returns The `for` statement scope. */ function getForScope(scope) { const child = scope.childScopes[0]; return child.block === scope.block ? child.childScopes[0] : child; } function analyzeScope(ast, parserOptions) { const ecmaVersion = getEcmaVersionIfUseEspree(parserOptions) ?? ANALYZE_SCOPE_DEFAULT_ECMA_VERSION; const ecmaFeatures = parserOptions.ecmaFeatures ?? {}; const sourceType = parserOptions.sourceType ?? "script"; return getEslintScope().analyze(ast, { ignoreEval: true, nodejsScope: false, impliedStrict: ecmaFeatures.impliedStrict, ecmaVersion, sourceType, fallback: getFallbackKeys }); } /** * Analyze the scope of the given AST. * @param {ParserResult} parserResult The parser result to analyze. * @param parserOptions */ function analyze(parserResult, parserOptions) { return (parserResult.scopeManager || analyzeScope(parserResult.ast, parserOptions)).globalScope; } /** * Analyze the external references of the given AST. * @param {ParserResult} parserResult The parser result to analyze. * @returns {Reference[]} The reference objects of external references. */ function analyzeExternalReferences(parserResult, parserOptions) { return analyze(parserResult, parserOptions).through.filter(isUnique).map(transformReference); } /** * Analyze the external references of the given AST. * @param {ParserResult} parserResult The parser result to analyze. * @returns {Reference[]} The reference objects of external references. */ function analyzeVariablesAndExternalReferences(parserResult, kind, parserOptions) { const scope = analyze(parserResult, parserOptions); return { variables: getForScope(scope).variables.filter(hasDefinition).map((v) => transformVariable(v, kind)), references: scope.through.filter(isUnique).map(transformReference) }; } //#endregion //#region src/common/fix-locations.ts /** * Do post-process of parsing an expression. * * 1. Set `node.parent`. * 2. Fix `node.range` and `node.loc` for HTML entities. * * @param result The parsing result to modify. * @param locationCalculator The location calculator to modify. */ function fixLocations(result, locationCalculator) { fixNodeLocations(result.ast, result.visitorKeys, locationCalculator); for (const token of result.ast.tokens ?? []) fixLocation(token, locationCalculator); for (const comment of result.ast.comments ?? []) fixLocation(comment, locationCalculator); } function fixNodeLocations(rootNode, visitorKeys, locationCalculator) { const traversed = /* @__PURE__ */ new Map(); traverseNodes(rootNode, { visitorKeys, enterNode(node, parent) { if (!traversed.has(node)) { traversed.set(node, node); node.parent = parent; if (traversed.has(node.range)) { if (!traversed.has(node.loc)) { node.loc.start = locationCalculator.getLocFromIndex(node.range[0]); node.loc.end = locationCalculator.getLocFromIndex(node.range[1]); traversed.set(node.loc, node); } else if (node.start != null || node.end != null) { const traversedNode = traversed.get(node.range); if (traversedNode.type === node.type) { node.start = traversedNode.start; node.end = traversedNode.end; } } } else { fixLocation(node, locationCalculator); traversed.set(node.range, node); traversed.set(node.loc, node); } } }, leaveNode() {} }); } /** * Modify the location information of the given node with using the base offset and gaps of this calculator. * @param node The node to modify their location. */ function fixLocation(node, locationCalculator) { const range = node.range; const loc = node.loc; const d0 = locationCalculator.getFixOffset(range[0], "start"); const d1 = locationCalculator.getFixOffset(range[1], "end"); if (d0 !== 0) { range[0] += d0; if (node.start != null) node.start += d0; loc.start = locationCalculator.getLocFromIndex(range[0]); } if (d1 !== 0) { range[1] += d1; if (node.end != null) node.end += d0; loc.end = locationCalculator.getLocFromIndex(range[1]); } return node; } /** * Modify the location information of the given error with using the base offset and gaps of this calculator. * @param error The error to modify their location. */ function fixErrorLocation(error, locationCalculator) { const diff = locationCalculator.getFixOffset(error.index, "start"); error.index += diff; const loc = locationCalculator.getLocFromIndex(error.index); error.lineNumber = loc.line; error.column = loc.column; } //#endregion //#region src/script/generic.ts function extractGeneric(element) { const genericAttr = findGenericDirective(element); if (!genericAttr) return null; const genericNode = genericAttr.value.expression; return { node: genericNode, defineTypes: genericNode.params.map((t, i) => ({ node: t, define: `type ${t.name.name} = ${getConstraint(t, genericNode.rawParams[i])}` })), postprocess({ result, getTypeBlock, isRemoveTarget, getTypeDefScope }) { removeTypeDeclarations(getTypeBlock?.(result.ast) ?? result.ast, isRemoveTarget); if (result.ast.tokens) removeTypeDeclarationTokens(result.ast.tokens, isRemoveTarget); if (result.ast.comments) removeTypeDeclarationTokens(result.ast.comments, isRemoveTarget); if (result.scopeManager) { const typeDefScope = getTypeDefScope(result.scopeManager); restoreScope(result.scopeManager, typeDefScope, isRemoveTarget); } } }; function removeTypeDeclarations(node, isRemoveTarget) { for (let index = node.body.length - 1; index >= 0; index--) if (isRemoveTarget(node.body[index])) node.body.splice(index, 1); } function removeTypeDeclarationTokens(tokens, isRemoveTarget) { for (let index = tokens.length - 1; index >= 0; index--) if (isRemoveTarget(tokens[index])) tokens.splice(index, 1); } function restoreScope(scopeManager, typeDefScope, isRemoveTarget) { for (const variable of [...typeDefScope.variables]) { let def = variable.defs.find((d) => isRemoveTarget(d.name)); while (def) { removeVariableDef(variable, def, typeDefScope); def = variable.defs.find((d) => isRemoveTarget(d.name)); } } for (const reference of [...typeDefScope.references]) if (isRemoveTarget(reference.identifier)) removeReference(reference, typeDefScope); for (const scope of [...scopeManager.scopes]) if (isRemoveTarget(scope.block)) removeScope(scopeManager, scope); } } function getConstraint(node, rawParam) { if (!node.constraint) return "unknown"; let index = rawParam.indexOf(node.name.name) + node.name.name.length; let startIndex = null; while (index < rawParam.length) { if (startIndex == null) { if (rawParam.startsWith("extends", index)) { startIndex = index = index + 7; continue; } } else if (rawParam[index] === "=") { if (rawParam[index + 1] === ">") { index += 2; continue; } return rawParam.slice(startIndex, index); } if (rawParam.startsWith("//", index)) { const lfIndex = rawParam.indexOf("\n", index); if (lfIndex >= 0) { index = lfIndex + 1; continue; } return "unknown"; } if (rawParam.startsWith("/*", index)) { const endIndex = rawParam.indexOf("*/", index); if (endIndex >= 0) { index = endIndex + 2; continue; } return "unknown"; } index++; } if (startIndex == null) return "unknown"; return rawParam.slice(startIndex); } /** Remove variable def */ function removeVariableDef(variable, def, scope) { const defIndex = variable.defs.indexOf(def); if (defIndex < 0) return; variable.defs.splice(defIndex, 1); if (variable.defs.length === 0) { referencesToThrough(variable.references, scope); variable.references.forEach((r) => { if (r.init) r.init = false; r.resolved = null; }); scope.variables.splice(scope.variables.indexOf(variable), 1); const name = variable.name; if (variable === scope.set.get(name)) scope.set.delete(name); } else { const idIndex = variable.identifiers.indexOf(def.name); if (idIndex >= 0) variable.identifiers.splice(idIndex, 1); } } /** Move reference to through */ function referencesToThrough(references, baseScope) { let scope = baseScope; while (scope) { addAllReferences(scope.through, references); scope = scope.upper; } } /** * Add all references to array */ function addAllReferences(list, elements) { list.push(...elements); list.sort((a, b) => a.identifier.range[0] - b.identifier.range[0]); } /** Remove reference */ function removeReference(reference, baseScope) { if (reference.resolved) if (reference.resolved.defs.some((d) => d.name === reference.identifier)) { const varIndex = baseScope.variables.indexOf(reference.resolved); if (varIndex >= 0) baseScope.variables.splice(varIndex, 1); const name = reference.identifier.name; if (reference.resolved === baseScope.set.get(name)) baseScope.set.delete(name); } else { const refIndex = reference.resolved.references.indexOf(reference); if (refIndex >= 0) reference.resolved.references.splice(refIndex, 1); } let scope = baseScope; while (scope) { const refIndex = scope.references.indexOf(reference); if (refIndex >= 0) scope.references.splice(refIndex, 1); const throughIndex = scope.through.indexOf(reference); if (throughIndex >= 0) scope.through.splice(throughIndex, 1); scope = scope.upper; } } /** Remove scope */ function removeScope(scopeManager, scope) { for (const childScope of scope.childScopes) removeScope(scopeManager, childScope); while (scope.references[0]) removeReference(scope.references[0], scope); const upper = scope.upper; if (upper) { const index = upper.childScopes.indexOf(scope); if (index >= 0) upper.childScopes.splice(index, 1); } const index = scopeManager.scopes.indexOf(scope); if (index >= 0) scopeManager.scopes.splice(index, 1); } //#endregion //#region src/script/index.ts /** * @author Toru Nagashima <https://github.com/mysticatea> * @copyright 2017 Toru Nagashima. All rights reserved. * See LICENSE file in root directory for full license. */ const ALIAS_ITERATOR = /^([\s\S]*?(?:\s|\)))(\bin\b|\bof\b)([\s\S]*)$/u; const PARENS = /^(\s*\()([\s\S]*?)(\)\s*)$/u; const DUMMY_PARENT$2 = {}; const IS_FUNCTION_EXPRESSION = /^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/u; const IS_SIMPLE_PATH = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?'\]|\["[^"]*?"\]|\[\d+\]|\[[A-Za-z_$][\w$]*\])*$/u; /** * Parse the alias and iterator of 'v-for' directive values. * @param code The code to parse. * @returns The parsed result. */ function processVForAliasAndIterator(code) { const match = ALIAS_ITERATOR.exec(code); if (match != null) { const aliases = match[1]; const parenMatch = PARENS.exec(aliases); return { aliases, hasParens: Boolean(parenMatch), aliasesWithBrackets: parenMatch ? `${parenMatch[1].slice(0, -1)}[${parenMatch[2]}]${parenMatch[3].slice(1)}` : `[${aliases.slice(0, -1)}]`, delimiter: match[2] || "", iterator: match[3] }; } return { aliases: "", hasParens: false, aliasesWithBrackets: "", delimiter: "", iterator: code }; } /** * Get the comma token before a given node. * @param tokens The token list. * @param node The node to get the comma before this node. * @returns The comma token. */ function getCommaTokenBeforeNode(tokens, node) { let tokenIndex = sortedIndexBy(tokens, { range: node.range }, (t) => t.range[0]); while (tokenIndex >= 0) { const token = tokens[tokenIndex]; if (token.type === "Punctuator" && token.value === ",") return token; tokenIndex -= 1; } return null; } /** * Throw syntax error for empty. * @param locationCalculator The location calculator to get line/column. */ function throwEmptyError(locationCalculator, expected) { const loc = locationCalculator.getLocation(0); const err = new ParseError(`Expected to be ${expected}, but got empty.`, void 0, 0, loc.line, loc.column); fixErrorLocation(err, locationCalculator); throw err; } /** * Throw syntax error for unexpected token. * @param locationCalculator The location calculator to get line/column. * @param name The token name. * @param token The token object to get that location. */ function throwUnexpectedTokenError(name, token) { throw new ParseError(`Unexpected token '${name}'.`, void 0, token.range[0], token.loc.start.line, token.loc.start.column); } /** * Throw syntax error of outside of code. * @param locationCalculator The location calculator to get line/column. */ function throwErrorAsAdjustingOutsideOfCode(err, code, locationCalculator) { if (ParseError.isParseError(err)) { const endOffset = locationCalculator.getOffsetWithGap(code.length); if (err.index >= endOffset) err.message = "Unexpected end of expression."; } throw err; } /** * Parse the given source code. * * @param code The source code to parse. * @param locationCalculator The location calculator for fixLocations. * @param parserOptions The parser options. * @returns The result of parsing. */ function parseScriptFragment(code, locationCalculator, parserOptions) { return parseScriptFragmentWithOption(code, locationCalculator, parserOptions); } /** * Parse the given source code. * * @param code The source code to parse. * @param locationCalculator The location calculator for fixLocations. * @param parserOptions The parser options. * @param processOptions The process options. * @returns The result of parsing. */ function parseScriptFragmentWithOption(code, locationCalculator, parserOptions, processOptions) { try { const result = parseScript$1(code, parserOptions); processOptions?.preFixLocationProcess?.(result); fixLocations(result, locationCalculator); return result; } catch (err) { const perr = ParseError.normalize(err); if (perr) { fixErrorLocation(perr, locationCalculator); throw perr; } throw err; } } const validDivisionCharRE = /[\w).+\-_$\]]/u; /** * This is a fork of https://github.com/vuejs/vue/blob/2686818beb5728e3b7aa22f47a3b3f0d39d90c8e/src/compiler/parser/filter-parser.js * @param exp the expression to process filters. */ function splitFilters(exp) { const result = []; let inSingle = false; let inDouble = false; let inTemplateString = false; let inRegex = false; let curly = 0; let square = 0; let paren = 0; let lastFilterIndex = 0; let c = 0; let prev = 0; for (let i = 0; i < exp.length; i++) { prev = c; c = exp.charCodeAt(i); if (inSingle) { if (c === 39 && prev !== 92) inSingle = false; } else if (inDouble) { if (c === 34 && prev !== 92) inDouble = false; } else if (inTemplateString) { if (c === 96 && prev !== 92) inTemplateString = false; } else if (inRegex) { if (c === 47 && prev !== 92) inRegex = false; } else if (c === 124 && exp.charCodeAt(i + 1) !== 124 && exp.charCodeAt(i - 1) !== 124 && !curly && !square && !paren) { result.push(exp.slice(lastFilterIndex, i)); lastFilterIndex = i + 1; } else { switch (c) { case 34: inDouble = true; break; case 39: inSingle = true; break; case 96: inTemplateString = true; break; case 40: paren++; break; case 41: paren--; break; case 91: square++; break; case 93: square--; break; case 123: curly++; break; case 125: curly--; break; } if (c === 47) { let j = i - 1; let p; for (; j >= 0; j--) { p = exp.charAt(j); if (p !== " ") break; } if (!p || !validDivisionCharRE.test(p)) inRegex = true; } } } result.push(exp.slice(lastFilterIndex)); return result; } /** * Parse the source code of inline scripts. * @param code The source code of inline scripts. * @param locationCalculator The location calculator for the inline script. * @param parserOptions The parser options. * @returns The result of parsing. */ function parseExpressionBody(code, locationCalculator, parserOptions, allowEmpty = false) { debug$1("[script] parse expression: \"0(%s)\"", code); try { const result = parseScriptFragment(`0(${code})`, locationCalculator.getSubCalculatorShift(-2), parserOptions); const { ast } = result; const tokens = ast.tokens ?? []; const comments = ast.comments ?? []; const references = analyzeExternalReferences(result, parserOptions); const callExpression = ast.body[0].expression; const expression = callExpression.arguments[0]; if (!allowEmpty && !expression) return throwEmptyError(locationCalculator, "an expression"); if (expression?.type === "SpreadElement") return throwUnexpectedTokenError("...", expression); if (callExpression.arguments[1]) { const node = callExpression.arguments[1]; return throwUnexpectedTokenError(",", getCommaTokenBeforeNode(tokens, node) || node); } tokens.shift(); tokens.shift(); tokens.pop(); return { expression, tokens, comments, references, variables: [] }; } catch (err) { return throwErrorAsAdjustingOutsideOfCode(err, code, locationCalculator); } } /** * Parse the source code of inline scripts. * @param code The source code of inline scripts. * @param locationCalculator The location calculator for the inline script. * @param parserOptions The parser options. * @returns The result of parsing. */ function parseFilter(code, locationCalculator, parserOptions) { debug$1("[script] parse filter: \"%s\"", code); try { const expression = { type: "VFilter", parent: null, range: [0, 0], loc: {}, callee: null, arguments: [] }; const tokens = []; const comments = []; const references = []; const paren = code.indexOf("("); const calleeCode = paren === -1 ? code : code.slice(0, paren); const argsCode = paren === -1 ? null : code.slice(paren); if (calleeCode.trim()) { const spaces = /^\s*/u.exec(calleeCode)[0]; const subCalculator = locationCalculator.getSubCalculatorShift(spaces.length); const { ast } = parseScriptFragment(`"${calleeCode.trim()}"`, subCalculator, parserOptions); const callee = ast.body[0].expression; if (callee.type !== "Literal") { const { loc, range } = ast.tokens[0]; return throwUnexpectedTokenError("\"", { range: [range[1] - 1, range[1]], loc: { start: { line: loc.end.line, column: loc.end.column - 1 }, end: loc.end } }); } expression.callee = { type: "Identifier", parent: expression, range: [callee.range[0], subCalculator.getOffsetWithGap(calleeCode.trim().length)], loc: { start: callee.loc.start, end: subCalculator.getLocation(calleeCode.trim().length) }, name: String(callee.value) }; tokens.push({ type: "Identifier", value: calleeCode.trim(), range: expression.callee.range, loc: expression.callee.loc }); } else return throwEmptyError(locationCalculator, "a filter name"); if (argsCode != null) { const result = parseScriptFragment(`0${argsCode}`, locationCalculator.getSubCalculatorAfter(paren).getSubCalculatorShift(-1), parserOptions); const { ast } = result; const callExpression = ast.body[0].expression; ast.tokens.shift(); if (callExpression.type !== "CallExpression" || callExpression.callee.type !== "Literal") { let nestCount = 1; for (const token of ast.tokens.slice(1)) { if (nestCount === 0) return throwUnexpectedTokenError(token.value, token); if (token.type === "Punctuator" && token.value === "(") nestCount += 1; if (token.type === "Punctuator" && token.value === ")") nestCount -= 1; } const token = ast.tokens.at(-1); return throwUnexpectedTokenError(token.value, token); } for (const argument of callExpression.arguments) { argument.parent = expression; expression.arguments.push(argument); } tokens.push(...ast.tokens); comments.push(...ast.comments); references.push(...analyzeExternalReferences(result, parserOptions)); } const firstToken = tokens[0]; const lastToken = tokens.at(-1); expression.range = [firstToken.range[0], lastToken.range[1]]; expression.loc = { start: firstToken.loc.start, end: lastToken.loc.end }; return { expression, tokens, comments, references, variables: [] }; } catch (err) { return throwErrorAsAdjustingOutsideOfCode(err, code, locationCalculator); } } function loadParser(parser) { if (parser !== "espree") return require(parser); return getEspree(); } /** * Parse the given source code. * * @param code The source code to parse. * @param parserOptions The parser options. * @returns The result of parsing. */ function parseScript$1(code, parserOptions) { const parser = typeof parserOptions.parser =