vue-eslint-parser
Version:
The ESLint custom parser for `.vue` files.
1,484 lines (1,465 loc) • 319 kB
JavaScript
/**
* @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 =