1358 lines
39 KiB
JavaScript
1358 lines
39 KiB
JavaScript
import { t as __exportAll } from "./chunk-C7Uep-_p.mjs";
|
|
import { createRequire } from "node:module";
|
|
import * as Evk from "eslint-visitor-keys";
|
|
import evkPkg from "eslint-visitor-keys/package.json" with { type: "json" };
|
|
import path from "node:path";
|
|
import { lte } from "semver";
|
|
import * as acorn from "acorn";
|
|
import acornPkg from "acorn/package.json" with { type: "json" };
|
|
|
|
//#region src/parser/modules/require-utils.ts
|
|
/**
|
|
* Get NodeJS.Require from Linter
|
|
*/
|
|
function getRequireFromLinter() {
|
|
try {
|
|
const eslintPkgPath = getRequireFromCwd()?.resolve("eslint/package.json");
|
|
if (!eslintPkgPath) return null;
|
|
return createRequire(path.join(path.dirname(eslintPkgPath), "__placeholder__.js"));
|
|
} catch {}
|
|
return null;
|
|
}
|
|
/**
|
|
* Get NodeJS.Require from Cwd
|
|
*/
|
|
function getRequireFromCwd() {
|
|
try {
|
|
const cwd = process.cwd();
|
|
return createRequire(path.join(cwd, "__placeholder__.js"));
|
|
} catch {}
|
|
return null;
|
|
}
|
|
/**
|
|
* Get module from Linter
|
|
*/
|
|
function requireFromLinter(module) {
|
|
try {
|
|
return getRequireFromLinter()?.(module);
|
|
} catch {}
|
|
return null;
|
|
}
|
|
/**
|
|
* Get module path from Linter
|
|
*/
|
|
function resolveFromLinter(module) {
|
|
try {
|
|
return getRequireFromLinter()?.resolve(module) ?? null;
|
|
} catch {}
|
|
return null;
|
|
}
|
|
/**
|
|
* Get module from Cwd
|
|
*/
|
|
function requireFromCwd(module) {
|
|
try {
|
|
return getRequireFromCwd()?.(module);
|
|
} catch {}
|
|
return null;
|
|
}
|
|
/**
|
|
* Get module path from Cwd
|
|
*/
|
|
function resolveFromCwd(module) {
|
|
try {
|
|
return getRequireFromCwd()?.resolve(module) ?? null;
|
|
} catch {}
|
|
return null;
|
|
}
|
|
/**
|
|
* Get the newest `espree` kind from the loaded ESLint or dependency.
|
|
*/
|
|
function loadNewest(items) {
|
|
let target = null;
|
|
for (const item of items) {
|
|
const pkg = item.getPkg();
|
|
if (pkg != null && (!target || lte(target.version, pkg.version))) target = {
|
|
version: pkg.version,
|
|
get: item.get
|
|
};
|
|
}
|
|
return target.get();
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/visitor-keys.ts
|
|
const jsonKeys = {
|
|
Program: ["body"],
|
|
JSONExpressionStatement: ["expression"],
|
|
JSONArrayExpression: ["elements"],
|
|
JSONObjectExpression: ["properties"],
|
|
JSONProperty: ["key", "value"],
|
|
JSONIdentifier: [],
|
|
JSONLiteral: [],
|
|
JSONUnaryExpression: ["argument"],
|
|
JSONTemplateLiteral: ["quasis", "expressions"],
|
|
JSONTemplateElement: [],
|
|
JSONBinaryExpression: ["left", "right"]
|
|
};
|
|
let cache = null;
|
|
/**
|
|
* Get visitor keys
|
|
*/
|
|
function getVisitorKeys() {
|
|
if (!cache) cache = loadNewest([
|
|
{
|
|
getPkg() {
|
|
return requireFromCwd("eslint-visitor-keys/package.json");
|
|
},
|
|
get() {
|
|
return requireFromCwd("eslint-visitor-keys");
|
|
}
|
|
},
|
|
{
|
|
getPkg() {
|
|
return requireFromLinter("eslint-visitor-keys/package.json");
|
|
},
|
|
get() {
|
|
return requireFromLinter("eslint-visitor-keys");
|
|
}
|
|
},
|
|
{
|
|
getPkg() {
|
|
return evkPkg;
|
|
},
|
|
get() {
|
|
return Evk;
|
|
}
|
|
}
|
|
]).unionWith(jsonKeys);
|
|
return cache;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/utils.ts
|
|
/**
|
|
* Check if the given node is RegExpLiteral
|
|
*/
|
|
function isRegExpLiteral(node) {
|
|
return Boolean(node.regex) || node.raw.startsWith("/");
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/errors.ts
|
|
/**
|
|
* JSON parse errors.
|
|
*/
|
|
var ParseError = class extends SyntaxError {
|
|
/**
|
|
* 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, offset, line, column) {
|
|
super(message);
|
|
this.index = offset;
|
|
this.lineNumber = line;
|
|
this.column = column;
|
|
}
|
|
};
|
|
/**
|
|
* Throw syntax error for expected token.
|
|
* @param name The token name.
|
|
* @param token The token object to get that location.
|
|
*/
|
|
function throwExpectedTokenError(name, beforeToken) {
|
|
const locs = getLocation(beforeToken);
|
|
throw new ParseError(`Expected token '${name}'.`, locs.end, locs.loc.end.line, locs.loc.end.column + 1);
|
|
}
|
|
/**
|
|
* Throw syntax error for unexpected name.
|
|
* @param name The unexpected name.
|
|
* @param token The token object to get that location.
|
|
*/
|
|
function throwUnexpectedError(name, token) {
|
|
const locs = getLocation(token);
|
|
throw new ParseError(`Unexpected ${name}.`, locs.start, locs.loc.start.line, locs.loc.start.column + 1);
|
|
}
|
|
/**
|
|
* Throw syntax error for unexpected token.
|
|
* @param name The token name.
|
|
* @param token The token object to get that location.
|
|
*/
|
|
function throwUnexpectedTokenError(name, token) {
|
|
return throwUnexpectedError(`token '${name}'`, token);
|
|
}
|
|
/**
|
|
* Throw syntax error for unexpected comment.
|
|
* @param name The token name.
|
|
* @param token The token object to get that location.
|
|
*/
|
|
function throwUnexpectedCommentError(token) {
|
|
return throwUnexpectedError("comment", token);
|
|
}
|
|
/**
|
|
* Throw syntax error for unexpected whitespace.
|
|
*/
|
|
function throwUnexpectedSpaceError(beforeToken) {
|
|
const locs = getLocation(beforeToken);
|
|
throw new ParseError("Unexpected whitespace.", locs.end, locs.loc.end.line, locs.loc.end.column + 1);
|
|
}
|
|
/**
|
|
* Throw syntax error for unexpected invalid number.
|
|
*/
|
|
function throwInvalidNumberError(text, token) {
|
|
const locs = getLocation(token);
|
|
throw new ParseError(`Invalid number ${text}.`, locs.start, locs.loc.start.line, locs.loc.start.column + 1);
|
|
}
|
|
/**
|
|
* Throw syntax error for unexpected token.
|
|
* @param node The token object to get that location.
|
|
*/
|
|
function throwUnexpectedNodeError(node, tokens, offset) {
|
|
if (node.type === "Identifier" || node.type === "JSONIdentifier") {
|
|
const locs = getLocation(node);
|
|
throw new ParseError(`Unexpected identifier '${node.name}'.`, locs.start, locs.loc.start.line, locs.loc.start.column + 1);
|
|
}
|
|
if (node.type === "Literal" || node.type === "JSONLiteral") {
|
|
const type = node.bigint ? "bigint" : isRegExpLiteral(node) ? "regex" : node.value === null ? "null" : typeof node.value;
|
|
const locs = getLocation(node);
|
|
throw new ParseError(`Unexpected ${type} literal.`, locs.start, locs.loc.start.line, locs.loc.start.column + 1);
|
|
}
|
|
if (node.type === "TemplateLiteral" || node.type === "JSONTemplateLiteral") {
|
|
const locs = getLocation(node);
|
|
throw new ParseError("Unexpected template literal.", locs.start, locs.loc.start.line, locs.loc.start.column + 1);
|
|
}
|
|
if (node.type.endsWith("Expression") && node.type !== "FunctionExpression") {
|
|
const name = node.type.replace(/^JSON/u, "").replace(/\B([A-Z])/gu, " $1").toLowerCase();
|
|
const locs = getLocation(node);
|
|
throw new ParseError(`Unexpected ${name}.`, locs.start, locs.loc.start.line, locs.loc.start.column + 1);
|
|
}
|
|
const index = node.range[0] + (offset || 0);
|
|
const t = tokens.findTokenByOffset(index);
|
|
const name = t?.value || "unknown";
|
|
const locs = getLocation(t || node);
|
|
throw new ParseError(`Unexpected token '${name}'.`, locs.start, locs.loc.start.line, locs.loc.start.column + 1);
|
|
}
|
|
/** get locations */
|
|
function getLocation(token) {
|
|
return {
|
|
start: token.range?.[0] ?? token.start,
|
|
end: token.range?.[1] ?? token.end,
|
|
loc: token.loc
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/token-store.ts
|
|
var TokenStore = class {
|
|
constructor(tokens) {
|
|
this.tokens = tokens;
|
|
}
|
|
add(token) {
|
|
this.tokens.push(token);
|
|
}
|
|
findIndexByOffset(offset) {
|
|
return this.tokens.findIndex((token) => token.range[0] <= offset && offset < token.range[1]);
|
|
}
|
|
findTokenByOffset(offset) {
|
|
return this.tokens[this.findIndexByOffset(offset)];
|
|
}
|
|
/**
|
|
* Get the first token representing the given node.
|
|
*
|
|
*/
|
|
getFirstToken(nodeOrToken) {
|
|
return this.findTokenByOffset(nodeOrToken.range[0]);
|
|
}
|
|
/**
|
|
* Get the last token representing the given node.
|
|
*
|
|
*/
|
|
getLastToken(nodeOrToken) {
|
|
return this.findTokenByOffset(nodeOrToken.range[1] - 1);
|
|
}
|
|
/**
|
|
* Get the first token before the given node or token.
|
|
*/
|
|
getTokenBefore(nodeOrToken, filter) {
|
|
const tokenIndex = this.findIndexByOffset(nodeOrToken.range[0]);
|
|
for (let index = tokenIndex - 1; index >= 0; index--) {
|
|
const token = this.tokens[index];
|
|
if (!filter || filter(token)) return token;
|
|
}
|
|
return null;
|
|
}
|
|
/**
|
|
* Get the first token after the given node or token.
|
|
*/
|
|
getTokenAfter(nodeOrToken, filter) {
|
|
const tokenIndex = this.findIndexByOffset(nodeOrToken.range[0]);
|
|
for (let index = tokenIndex + 1; index < this.tokens.length; index++) {
|
|
const token = this.tokens[index];
|
|
if (!filter || filter(token)) return token;
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
/**
|
|
* Checks if given token is comma
|
|
*/
|
|
function isComma(token) {
|
|
return token.type === "Punctuator" && token.value === ",";
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/validate.ts
|
|
const lineBreakPattern = /\r\n|[\n\r\u2028\u2029]/u;
|
|
const octalNumericLiteralPattern = /^0o/iu;
|
|
const legacyOctalNumericLiteralPattern = /^0\d/u;
|
|
const binaryNumericLiteralPattern = /^0b/iu;
|
|
const unicodeCodepointEscapePattern = /\\u\{[\dA-Fa-f]+\}/uy;
|
|
/**
|
|
* Check if given string has unicode codepoint escape
|
|
*/
|
|
function hasUnicodeCodepointEscapes(code) {
|
|
let escaped = false;
|
|
for (let index = 0; index < code.length - 4; index++) {
|
|
if (escaped) {
|
|
escaped = false;
|
|
continue;
|
|
}
|
|
if (code[index] === "\\") {
|
|
unicodeCodepointEscapePattern.lastIndex = index;
|
|
if (unicodeCodepointEscapePattern.test(code)) return true;
|
|
escaped = true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
/**
|
|
* Validate ES node
|
|
*/
|
|
function validateNode(node, tokens, ctx) {
|
|
if (node.type === "ObjectExpression") {
|
|
validateObjectExpressionNode(node, tokens, ctx);
|
|
return;
|
|
}
|
|
if (node.type === "Property") {
|
|
validatePropertyNode(node, tokens, ctx);
|
|
return;
|
|
}
|
|
if (node.type === "ArrayExpression") {
|
|
validateArrayExpressionNode(node, tokens, ctx);
|
|
return;
|
|
}
|
|
if (node.type === "Literal") {
|
|
validateLiteralNode(node, tokens, ctx);
|
|
return;
|
|
}
|
|
if (node.type === "UnaryExpression") {
|
|
validateUnaryExpressionNode(node, tokens, ctx);
|
|
return;
|
|
}
|
|
if (node.type === "Identifier") {
|
|
validateIdentifierNode(node, tokens, ctx);
|
|
return;
|
|
}
|
|
if (node.type === "TemplateLiteral") {
|
|
validateTemplateLiteralNode(node, tokens, ctx);
|
|
return;
|
|
}
|
|
if (node.type === "TemplateElement") {
|
|
validateTemplateElementNode(node, tokens);
|
|
return;
|
|
}
|
|
if (node.type === "BinaryExpression") {
|
|
validateBinaryExpressionNode(node, tokens, ctx);
|
|
return;
|
|
}
|
|
throw throwUnexpectedNodeError(node, tokens);
|
|
}
|
|
/**
|
|
* Validate ObjectExpression node
|
|
*/
|
|
function validateObjectExpressionNode(node, tokens, ctx) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "ObjectExpression") throw throwUnexpectedNodeError(node, tokens);
|
|
for (const prop of node.properties) setParent$1(prop, node);
|
|
if (!ctx.trailingCommas) {
|
|
const token = tokens.getTokenBefore(tokens.getLastToken(node));
|
|
if (token && isComma(token)) throw throwUnexpectedTokenError(",", token);
|
|
}
|
|
}
|
|
/**
|
|
* Validate Property node
|
|
*/
|
|
function validatePropertyNode(node, tokens, ctx) {
|
|
if (node.type !== "Property") throw throwUnexpectedNodeError(node, tokens);
|
|
setParent$1(node.key, node);
|
|
setParent$1(node.value, node);
|
|
if (node.computed) throw throwUnexpectedNodeError(node, tokens);
|
|
if (node.method) throw throwUnexpectedNodeError(node.value, tokens);
|
|
if (node.shorthand) throw throwExpectedTokenError(":", node);
|
|
if (node.kind !== "init") throw throwExpectedTokenError(":", tokens.getFirstToken(node));
|
|
if (node.key.type === "Literal") {
|
|
const keyValueType = typeof node.key.value;
|
|
if (keyValueType === "number") {
|
|
if (!ctx.numberProperties) throw throwUnexpectedNodeError(node.key, tokens);
|
|
} else if (keyValueType !== "string") throw throwUnexpectedNodeError(node.key, tokens);
|
|
} else if (node.key.type === "Identifier") {
|
|
if (!ctx.unquoteProperties) throw throwUnexpectedNodeError(node.key, tokens);
|
|
} else throw throwUnexpectedNodeError(node.key, tokens);
|
|
if (node.value.type === "Identifier") {
|
|
if (!isStaticValueIdentifier(node.value, ctx)) throw throwUnexpectedNodeError(node.value, tokens);
|
|
}
|
|
}
|
|
/**
|
|
* Validate ArrayExpression node
|
|
*/
|
|
function validateArrayExpressionNode(node, tokens, ctx) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "ArrayExpression") throw throwUnexpectedNodeError(node, tokens);
|
|
if (!ctx.trailingCommas) {
|
|
const token = tokens.getTokenBefore(tokens.getLastToken(node));
|
|
if (token && isComma(token)) throw throwUnexpectedTokenError(",", token);
|
|
}
|
|
node.elements.forEach((child, index) => {
|
|
if (!child) {
|
|
if (ctx.sparseArrays) return;
|
|
const beforeIndex = index - 1;
|
|
const before = beforeIndex >= 0 ? tokens.getLastToken(node.elements[beforeIndex]) : tokens.getFirstToken(node);
|
|
throw throwUnexpectedTokenError(",", tokens.getTokenAfter(before, isComma));
|
|
}
|
|
if (child.type === "Identifier") {
|
|
if (!isStaticValueIdentifier(child, ctx)) throw throwUnexpectedNodeError(child, tokens);
|
|
}
|
|
setParent$1(child, node);
|
|
});
|
|
}
|
|
/**
|
|
* Validate Literal node
|
|
*/
|
|
function validateLiteralNode(node, tokens, ctx) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "Literal") throw throwUnexpectedNodeError(node, tokens);
|
|
if (isRegExpLiteral(node)) {
|
|
if (!ctx.regExpLiterals) throw throwUnexpectedNodeError(node, tokens);
|
|
} else if (node.bigint) {
|
|
if (!ctx.bigintLiterals) throw throwUnexpectedNodeError(node, tokens);
|
|
} else validateLiteral(node, ctx);
|
|
}
|
|
/**
|
|
* Validate literal
|
|
*/
|
|
function validateLiteral(node, ctx) {
|
|
const value = node.value;
|
|
if ((!ctx.invalidJsonNumbers || !ctx.leadingOrTrailingDecimalPoints || !ctx.numericSeparators) && typeof value === "number") {
|
|
const text = node.raw;
|
|
if (!ctx.leadingOrTrailingDecimalPoints) {
|
|
if (text.startsWith(".")) throw throwUnexpectedTokenError(".", node);
|
|
if (text.endsWith(".")) throw throwUnexpectedTokenError(".", {
|
|
range: [node.range[1] - 1, node.range[1]],
|
|
loc: {
|
|
start: {
|
|
line: node.loc.end.line,
|
|
column: node.loc.end.column - 1
|
|
},
|
|
end: node.loc.end
|
|
}
|
|
});
|
|
}
|
|
if (!ctx.numericSeparators) {
|
|
if (text.includes("_")) {
|
|
const index = text.indexOf("_");
|
|
throw throwUnexpectedTokenError("_", {
|
|
range: [node.range[0] + index, node.range[0] + index + 1],
|
|
loc: {
|
|
start: {
|
|
line: node.loc.start.line,
|
|
column: node.loc.start.column + index
|
|
},
|
|
end: {
|
|
line: node.loc.start.line,
|
|
column: node.loc.start.column + index + 1
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if (!ctx.octalNumericLiterals) {
|
|
if (octalNumericLiteralPattern.test(text)) throw throwUnexpectedError("octal numeric literal", node);
|
|
}
|
|
if (!ctx.legacyOctalNumericLiterals) {
|
|
if (legacyOctalNumericLiteralPattern.test(text)) throw throwUnexpectedError("legacy octal numeric literal", node);
|
|
}
|
|
if (!ctx.binaryNumericLiterals) {
|
|
if (binaryNumericLiteralPattern.test(text)) throw throwUnexpectedError("binary numeric literal", node);
|
|
}
|
|
if (!ctx.invalidJsonNumbers) try {
|
|
JSON.parse(text);
|
|
} catch {
|
|
throw throwInvalidNumberError(text, node);
|
|
}
|
|
}
|
|
if ((!ctx.multilineStrings || !ctx.singleQuotes || !ctx.unicodeCodepointEscapes) && typeof value === "string") {
|
|
if (!ctx.singleQuotes) {
|
|
if (node.raw.startsWith("'")) throw throwUnexpectedError("single quoted", node);
|
|
}
|
|
if (!ctx.multilineStrings) {
|
|
if (lineBreakPattern.test(node.raw)) throw throwUnexpectedError("multiline string", node);
|
|
}
|
|
if (!ctx.unicodeCodepointEscapes) {
|
|
if (hasUnicodeCodepointEscapes(node.raw)) throw throwUnexpectedError("unicode codepoint escape", node);
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Validate UnaryExpression node
|
|
*/
|
|
function validateUnaryExpressionNode(node, tokens, ctx) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "UnaryExpression") throw throwUnexpectedNodeError(node, tokens);
|
|
const operator = node.operator;
|
|
if (operator === "+") {
|
|
if (!ctx.plusSigns) throw throwUnexpectedTokenError("+", node);
|
|
} else if (operator !== "-") throw throwUnexpectedNodeError(node, tokens);
|
|
const argument = node.argument;
|
|
if (argument.type === "Literal") {
|
|
if (typeof argument.value !== "number") throw throwUnexpectedNodeError(argument, tokens);
|
|
} else if (argument.type === "Identifier") {
|
|
if (!isNumberIdentifier$1(argument, ctx)) throw throwUnexpectedNodeError(argument, tokens);
|
|
} else throw throwUnexpectedNodeError(argument, tokens);
|
|
if (!ctx.spacedSigns) {
|
|
if (node.range[0] + 1 < argument.range[0]) throw throwUnexpectedSpaceError(tokens.getFirstToken(node));
|
|
}
|
|
setParent$1(argument, node);
|
|
}
|
|
/**
|
|
* Validate Identifier node
|
|
*/
|
|
function validateIdentifierNode(node, tokens, ctx) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "Identifier") throw throwUnexpectedNodeError(node, tokens);
|
|
if (!ctx.escapeSequenceInIdentifier) {
|
|
if (node.name.length < node.range[1] - node.range[0]) throw throwUnexpectedError("escape sequence", node);
|
|
}
|
|
}
|
|
/**
|
|
* Validate TemplateLiteral node
|
|
*/
|
|
function validateTemplateLiteralNode(node, tokens, ctx) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "TemplateLiteral") throw throwUnexpectedNodeError(node, tokens);
|
|
if (!ctx.templateLiterals) throw throwUnexpectedNodeError(node, tokens);
|
|
if (node.expressions.length) {
|
|
const token = tokens.getFirstToken(node.quasis[0]);
|
|
throw throwUnexpectedTokenError("$", {
|
|
loc: {
|
|
start: {
|
|
line: token.loc.end.line,
|
|
column: token.loc.end.column - 2
|
|
},
|
|
end: token.loc.end
|
|
},
|
|
range: [token.range[1] - 2, token.range[1]]
|
|
});
|
|
}
|
|
if (!ctx.unicodeCodepointEscapes) {
|
|
if (hasUnicodeCodepointEscapes(node.quasis[0].value.raw)) throw throwUnexpectedError("unicode codepoint escape", node);
|
|
}
|
|
for (const q of node.quasis) setParent$1(q, node);
|
|
}
|
|
/**
|
|
* Validate TemplateElement node
|
|
*/
|
|
function validateTemplateElementNode(node, tokens) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "TemplateElement") throw throwUnexpectedNodeError(node, tokens);
|
|
const { cooked } = node.value;
|
|
if (cooked == null) throw throwUnexpectedNodeError(node, tokens);
|
|
const startOffset = -1;
|
|
const endOffset = node.tail ? 1 : 2;
|
|
node.start += startOffset;
|
|
node.end += endOffset;
|
|
node.range[0] += startOffset;
|
|
node.range[1] += endOffset;
|
|
node.loc.start.column += startOffset;
|
|
node.loc.end.column += endOffset;
|
|
}
|
|
/**
|
|
* Validate BinaryExpression node
|
|
*/
|
|
function validateBinaryExpressionNode(node, tokens, ctx) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "BinaryExpression") throw throwUnexpectedNodeError(node, tokens);
|
|
if (!ctx.staticExpressions) throw throwUnexpectedNodeError(node, tokens);
|
|
const { operator, left, right } = node;
|
|
if (operator !== "+" && operator !== "-" && operator !== "*" && operator !== "/" && operator !== "%" && operator !== "**") throw throwOperatorError();
|
|
if (left.type === "PrivateIdentifier") throw throwUnexpectedNodeError(left, tokens);
|
|
validateExpr(left, throwOperatorError);
|
|
validateExpr(right, () => throwUnexpectedNodeError(right, tokens));
|
|
/**
|
|
* Validate Expression node
|
|
*/
|
|
function validateExpr(expr, throwError) {
|
|
if (expr.type === "Literal") {
|
|
if (typeof expr.value !== "number") throw throwError();
|
|
} else if (expr.type !== "BinaryExpression" && expr.type !== "UnaryExpression") throw throwError();
|
|
setParent$1(expr, node);
|
|
}
|
|
/**
|
|
* Throw error
|
|
*/
|
|
function throwOperatorError() {
|
|
throw throwUnexpectedTokenError(operator, tokens.getTokenAfter(tokens.getFirstToken(node), (t) => t.value === operator) || node);
|
|
}
|
|
}
|
|
/**
|
|
* Check if given node is NaN or Infinity or undefined
|
|
*/
|
|
function isStaticValueIdentifier(node, ctx) {
|
|
if (isNumberIdentifier$1(node, ctx)) return true;
|
|
return node.name === "undefined" && ctx.undefinedKeywords;
|
|
}
|
|
/**
|
|
* Check if given node is NaN or Infinity
|
|
*/
|
|
function isNumberIdentifier$1(node, ctx) {
|
|
if (node.name === "Infinity" && ctx.infinities) return true;
|
|
if (node.name === "NaN" && ctx.nans) return true;
|
|
return false;
|
|
}
|
|
/** Set parent node */
|
|
function setParent$1(prop, parent) {
|
|
prop.parent = parent;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/modules/espree.ts
|
|
let espreeCache = null;
|
|
/**
|
|
* Get the path to the loaded `espree`'s package.json.
|
|
* If the loaded ESLint was not found, just returns `require.resolve("espree/package.json")`.
|
|
*/
|
|
function getEspreePath() {
|
|
const data = getEspreeData();
|
|
if (!data) return null;
|
|
return path.dirname(data.packageJsonPath);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
function getEspreeData() {
|
|
if (!espreeCache) espreeCache = loadNewest([{
|
|
getPkg() {
|
|
return requireFromCwd("espree/package.json");
|
|
},
|
|
get() {
|
|
const packageJsonPath = resolveFromCwd("espree/package.json");
|
|
if (!packageJsonPath) return null;
|
|
return {
|
|
packageJsonPath,
|
|
kind: "cwd"
|
|
};
|
|
}
|
|
}, {
|
|
getPkg() {
|
|
return requireFromLinter("espree/package.json");
|
|
},
|
|
get() {
|
|
const packageJsonPath = resolveFromLinter("espree/package.json");
|
|
if (!packageJsonPath) return null;
|
|
return {
|
|
packageJsonPath,
|
|
kind: "linter"
|
|
};
|
|
}
|
|
}]);
|
|
return espreeCache;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/modules/acorn.ts
|
|
let acornCache;
|
|
/**
|
|
* Load `acorn` from the loaded ESLint.
|
|
* If the loaded ESLint was not found, just returns `require("acorn")`.
|
|
*/
|
|
function getAcorn() {
|
|
if (!acornCache) acornCache = loadNewest([
|
|
{
|
|
getPkg() {
|
|
return requireFromCwd("acorn/package.json");
|
|
},
|
|
get() {
|
|
return requireFromCwd("acorn");
|
|
}
|
|
},
|
|
{
|
|
getPkg() {
|
|
return requireFromEspree("acorn/package.json");
|
|
},
|
|
get() {
|
|
return requireFromEspree("acorn");
|
|
}
|
|
},
|
|
{
|
|
getPkg() {
|
|
return acornPkg;
|
|
},
|
|
get() {
|
|
return acorn;
|
|
}
|
|
}
|
|
]);
|
|
return acornCache;
|
|
}
|
|
/**
|
|
* Get module from espree
|
|
*/
|
|
function requireFromEspree(module) {
|
|
try {
|
|
const espreePath = getEspreePath();
|
|
if (!espreePath) return null;
|
|
return createRequire(path.join(espreePath, "__placeholder__.js"))(module);
|
|
} catch {}
|
|
return null;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/convert.ts
|
|
var TokenConvertor = class {
|
|
constructor(ctx, code) {
|
|
this.templateBuffer = [];
|
|
this.ctx = ctx;
|
|
this.code = code;
|
|
this.tokTypes = getAcorn().tokTypes;
|
|
}
|
|
convertToken(token) {
|
|
const { tokTypes } = this;
|
|
let type, value;
|
|
const additional = {};
|
|
if (token.type === tokTypes.eof) return null;
|
|
else if (token.type === tokTypes.string) {
|
|
type = "String";
|
|
value = this.code.slice(...token.range);
|
|
} else if (token.type === tokTypes.num) {
|
|
type = "Numeric";
|
|
value = this.code.slice(...token.range);
|
|
} else if (token.type.keyword) {
|
|
if (token.type.keyword === "true" || token.type.keyword === "false") type = "Boolean";
|
|
else if (token.type.keyword === "null") type = "Null";
|
|
else type = "Keyword";
|
|
value = token.value;
|
|
} else if (token.type === tokTypes.braceL || token.type === tokTypes.braceR || token.type === tokTypes.bracketL || token.type === tokTypes.bracketR || token.type === tokTypes.colon || token.type === tokTypes.comma || token.type === tokTypes.plusMin) {
|
|
type = "Punctuator";
|
|
value = this.code.slice(...token.range);
|
|
} else if (token.type === tokTypes.name) {
|
|
type = "Identifier";
|
|
value = token.value;
|
|
} else if (token.type === tokTypes.backQuote) {
|
|
if (this.templateBuffer.length > 0) {
|
|
const first = this.templateBuffer[0];
|
|
this.templateBuffer.length = 0;
|
|
return {
|
|
type: "Template",
|
|
value: this.code.slice(first.start, token.end),
|
|
range: [first.start, token.end],
|
|
loc: {
|
|
start: first.loc.start,
|
|
end: token.loc.end
|
|
}
|
|
};
|
|
}
|
|
this.templateBuffer.push(token);
|
|
return null;
|
|
} else if (token.type === tokTypes.template) {
|
|
if (this.templateBuffer.length === 0) return throwUnexpectedTokenError(this.code.slice(...token.range), token);
|
|
this.templateBuffer.push(token);
|
|
return null;
|
|
} else if (token.type === tokTypes.regexp) {
|
|
const reValue = token.value;
|
|
type = "RegularExpression";
|
|
additional.regex = {
|
|
flags: reValue.flags,
|
|
pattern: reValue.pattern
|
|
};
|
|
value = `/${reValue.pattern}/${reValue.flags}`;
|
|
} else if (this.ctx.parentheses && (token.type === tokTypes.parenL || token.type === tokTypes.parenR)) {
|
|
type = "Punctuator";
|
|
value = this.code.slice(...token.range);
|
|
} else if (this.ctx.staticExpressions && (token.type === tokTypes.star || token.type === tokTypes.slash || token.type === tokTypes.modulo || token.type === tokTypes.starstar)) {
|
|
type = "Punctuator";
|
|
value = this.code.slice(...token.range);
|
|
} else return throwUnexpectedTokenError(this.code.slice(...token.range), token);
|
|
token.type = type;
|
|
token.value = value;
|
|
for (const k in additional) token[k] = additional[k];
|
|
return token;
|
|
}
|
|
};
|
|
/**
|
|
* Convert root expression node to JSONProgram node
|
|
*/
|
|
function convertProgramNode(node, tokens, ctx, code) {
|
|
/* istanbul ignore next */
|
|
if (node.type !== "JSONObjectExpression" && node.type !== "JSONArrayExpression" && node.type !== "JSONLiteral" && node.type !== "JSONUnaryExpression" && node.type !== "JSONIdentifier" && node.type !== "JSONTemplateLiteral" && node.type !== "JSONBinaryExpression") return throwUnexpectedNodeError(node, tokens);
|
|
if (node.type === "JSONIdentifier") {
|
|
if (!isStaticValueIdentifier(node, ctx)) return throwUnexpectedNodeError(node, tokens);
|
|
}
|
|
const body = {
|
|
type: "JSONExpressionStatement",
|
|
expression: node,
|
|
...cloneLocation(node),
|
|
parent: null
|
|
};
|
|
setParent(node, body);
|
|
const end = code.length;
|
|
const endLoc = getAcorn().getLineInfo(code, end);
|
|
const nn = {
|
|
type: "Program",
|
|
body: [body],
|
|
comments: [],
|
|
tokens: [],
|
|
range: [0, end],
|
|
loc: {
|
|
start: {
|
|
line: 1,
|
|
column: 0
|
|
},
|
|
end: {
|
|
line: endLoc.line,
|
|
column: endLoc.column
|
|
}
|
|
},
|
|
parent: null
|
|
};
|
|
setParent(body, nn);
|
|
return nn;
|
|
}
|
|
/** Clone locations */
|
|
function cloneLocation(node) {
|
|
const range = node.range;
|
|
const loc = node.loc;
|
|
return {
|
|
range: [range[0], range[1]],
|
|
loc: {
|
|
start: {
|
|
line: loc.start.line,
|
|
column: loc.start.column
|
|
},
|
|
end: {
|
|
line: loc.end.line,
|
|
column: loc.end.column
|
|
}
|
|
}
|
|
};
|
|
}
|
|
/** Set parent node */
|
|
function setParent(prop, parent) {
|
|
prop.parent = parent;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/extend-parser.ts
|
|
let parserCache;
|
|
const PRIVATE = Symbol("ExtendParser#private");
|
|
const PRIVATE_PROCESS_NODE = Symbol("ExtendParser#processNode");
|
|
/** Get extend parser */
|
|
function getParser() {
|
|
if (parserCache) return parserCache;
|
|
parserCache = class ExtendParser extends getAcorn().Parser {
|
|
constructor(options, code, pos) {
|
|
super((() => {
|
|
const tokenConvertor = new TokenConvertor(options.ctx, code);
|
|
const onToken = options.onToken || ((token) => {
|
|
const t = tokenConvertor.convertToken(token);
|
|
if (t) this[PRIVATE].tokenStore.add(t);
|
|
});
|
|
return {
|
|
ecmaVersion: options.ecmaVersion,
|
|
sourceType: options.sourceType,
|
|
ranges: true,
|
|
locations: true,
|
|
allowReserved: true,
|
|
onToken,
|
|
onComment: (block, text, start, end, startLoc, endLoc) => {
|
|
const comment = {
|
|
type: block ? "Block" : "Line",
|
|
value: text,
|
|
range: [start, end],
|
|
loc: {
|
|
start: startLoc,
|
|
end: endLoc
|
|
}
|
|
};
|
|
if (!this[PRIVATE].ctx.comments) throw throwUnexpectedCommentError(comment);
|
|
this[PRIVATE].comments.push(comment);
|
|
}
|
|
};
|
|
})(), code, pos);
|
|
this[PRIVATE] = {
|
|
code,
|
|
ctx: options.ctx,
|
|
tokenStore: options.tokenStore,
|
|
comments: options.comments,
|
|
nodes: options.nodes
|
|
};
|
|
}
|
|
/**
|
|
* Collect tokens.
|
|
*/
|
|
tokenize() {
|
|
const acornInstance = this;
|
|
const tokTypes = getAcorn().tokTypes;
|
|
do
|
|
acornInstance.next();
|
|
while (acornInstance.type !== tokTypes.eof);
|
|
acornInstance.next();
|
|
}
|
|
finishNode(...args) {
|
|
const result = super.finishNode(...args);
|
|
return this[PRIVATE_PROCESS_NODE](result);
|
|
}
|
|
finishNodeAt(...args) {
|
|
const result = super.finishNodeAt(...args);
|
|
return this[PRIVATE_PROCESS_NODE](result);
|
|
}
|
|
[PRIVATE_PROCESS_NODE](node) {
|
|
const { tokenStore, ctx, nodes } = this[PRIVATE];
|
|
validateNode(node, tokenStore, ctx);
|
|
nodes.push(node);
|
|
return node;
|
|
}
|
|
raise(pos, message) {
|
|
const loc = getAcorn().getLineInfo(this[PRIVATE].code, pos);
|
|
throw new ParseError(message, pos, loc.line, loc.column + 1);
|
|
}
|
|
raiseRecoverable(pos, message) {
|
|
this.raise(pos, message);
|
|
}
|
|
unexpected(pos) {
|
|
if (pos != null) {
|
|
this.raise(pos, "Unexpected token.");
|
|
return;
|
|
}
|
|
const start = this.start;
|
|
const end = this.end;
|
|
const token = this[PRIVATE].code.slice(start, end);
|
|
if (token) {
|
|
const message = `Unexpected token '${token}'.`;
|
|
this.raise(start, message);
|
|
} else {
|
|
if (!this[PRIVATE].nodes.length) this.raise(0, "Expected to be an expression, but got empty.");
|
|
if (this[PRIVATE].tokenStore.tokens.length) {
|
|
const last = this[PRIVATE].tokenStore.tokens[this[PRIVATE].tokenStore.tokens.length - 1];
|
|
this.raise(last.range[0], `Unexpected token '${last.value}'.`);
|
|
}
|
|
this.raise(start, "Unexpected token.");
|
|
}
|
|
}
|
|
};
|
|
return parserCache;
|
|
}
|
|
/** Get extend parser */
|
|
function getAnyTokenErrorParser() {
|
|
return class ExtendParser extends getParser() {
|
|
constructor(options, code, pos) {
|
|
super({
|
|
...options,
|
|
onToken: (token) => {
|
|
return throwUnexpectedTokenError(code.slice(...token.range), token);
|
|
}
|
|
}, code, pos);
|
|
}
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/syntax-context.ts
|
|
/**
|
|
* Normalize json syntax option
|
|
*/
|
|
function getJSONSyntaxContext(str) {
|
|
const upperCase = str?.toUpperCase();
|
|
if (upperCase === "JSON") return {
|
|
trailingCommas: false,
|
|
comments: false,
|
|
plusSigns: false,
|
|
spacedSigns: false,
|
|
leadingOrTrailingDecimalPoints: false,
|
|
infinities: false,
|
|
nans: false,
|
|
numericSeparators: false,
|
|
binaryNumericLiterals: false,
|
|
octalNumericLiterals: false,
|
|
legacyOctalNumericLiterals: false,
|
|
invalidJsonNumbers: false,
|
|
multilineStrings: false,
|
|
unquoteProperties: false,
|
|
singleQuotes: false,
|
|
numberProperties: false,
|
|
undefinedKeywords: false,
|
|
sparseArrays: false,
|
|
regExpLiterals: false,
|
|
templateLiterals: false,
|
|
bigintLiterals: false,
|
|
unicodeCodepointEscapes: false,
|
|
escapeSequenceInIdentifier: false,
|
|
parentheses: false,
|
|
staticExpressions: false
|
|
};
|
|
if (upperCase === "JSONC") return {
|
|
trailingCommas: true,
|
|
comments: true,
|
|
plusSigns: false,
|
|
spacedSigns: false,
|
|
leadingOrTrailingDecimalPoints: false,
|
|
infinities: false,
|
|
nans: false,
|
|
numericSeparators: false,
|
|
binaryNumericLiterals: false,
|
|
octalNumericLiterals: false,
|
|
legacyOctalNumericLiterals: false,
|
|
invalidJsonNumbers: false,
|
|
multilineStrings: false,
|
|
unquoteProperties: false,
|
|
singleQuotes: false,
|
|
numberProperties: false,
|
|
undefinedKeywords: false,
|
|
sparseArrays: false,
|
|
regExpLiterals: false,
|
|
templateLiterals: false,
|
|
bigintLiterals: false,
|
|
unicodeCodepointEscapes: false,
|
|
escapeSequenceInIdentifier: false,
|
|
parentheses: false,
|
|
staticExpressions: false
|
|
};
|
|
if (upperCase === "JSON5") return {
|
|
trailingCommas: true,
|
|
comments: true,
|
|
plusSigns: true,
|
|
spacedSigns: true,
|
|
leadingOrTrailingDecimalPoints: true,
|
|
infinities: true,
|
|
nans: true,
|
|
numericSeparators: false,
|
|
binaryNumericLiterals: false,
|
|
octalNumericLiterals: false,
|
|
legacyOctalNumericLiterals: false,
|
|
invalidJsonNumbers: true,
|
|
multilineStrings: true,
|
|
unquoteProperties: true,
|
|
singleQuotes: true,
|
|
numberProperties: false,
|
|
undefinedKeywords: false,
|
|
sparseArrays: false,
|
|
regExpLiterals: false,
|
|
templateLiterals: false,
|
|
bigintLiterals: false,
|
|
unicodeCodepointEscapes: false,
|
|
escapeSequenceInIdentifier: false,
|
|
parentheses: false,
|
|
staticExpressions: false
|
|
};
|
|
return {
|
|
trailingCommas: true,
|
|
comments: true,
|
|
plusSigns: true,
|
|
spacedSigns: true,
|
|
leadingOrTrailingDecimalPoints: true,
|
|
infinities: true,
|
|
nans: true,
|
|
numericSeparators: true,
|
|
binaryNumericLiterals: true,
|
|
octalNumericLiterals: true,
|
|
legacyOctalNumericLiterals: true,
|
|
invalidJsonNumbers: true,
|
|
multilineStrings: true,
|
|
unquoteProperties: true,
|
|
singleQuotes: true,
|
|
numberProperties: true,
|
|
undefinedKeywords: true,
|
|
sparseArrays: true,
|
|
regExpLiterals: true,
|
|
templateLiterals: true,
|
|
bigintLiterals: true,
|
|
unicodeCodepointEscapes: true,
|
|
escapeSequenceInIdentifier: true,
|
|
parentheses: true,
|
|
staticExpressions: true
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/parser.ts
|
|
/**
|
|
* Parse JSON source code
|
|
*/
|
|
function parseJSON(code, options) {
|
|
const parserOptions = Object.assign({ filePath: "<input>" }, options || {}, {
|
|
loc: true,
|
|
range: true,
|
|
raw: true,
|
|
tokens: true,
|
|
comment: true,
|
|
ecmaVersion: "latest"
|
|
});
|
|
const ctx = getJSONSyntaxContext(options?.jsonSyntax);
|
|
const tokens = [];
|
|
const comments = [];
|
|
const tokenStore = new TokenStore(tokens);
|
|
const nodes = [];
|
|
parserOptions.ctx = ctx;
|
|
parserOptions.tokenStore = tokenStore;
|
|
parserOptions.comments = comments;
|
|
parserOptions.nodes = nodes;
|
|
const baseAst = getParser().parseExpressionAt(code, 0, parserOptions);
|
|
for (const node of nodes) node.type = `JSON${node.type}`;
|
|
const ast = convertProgramNode(baseAst, tokenStore, ctx, code);
|
|
let lastIndex = Math.max(baseAst.range[1], tokens[tokens.length - 1]?.range[1] ?? 0, comments[comments.length - 1]?.range[1] ?? 0);
|
|
let lastChar = code[lastIndex];
|
|
while (lastChar === "\n" || lastChar === "\r" || lastChar === " " || lastChar === " ") {
|
|
lastIndex++;
|
|
lastChar = code[lastIndex];
|
|
}
|
|
if (lastIndex < code.length) getAnyTokenErrorParser().parseExpressionAt(code, lastIndex, parserOptions);
|
|
ast.tokens = tokens;
|
|
ast.comments = comments;
|
|
return ast;
|
|
}
|
|
/**
|
|
* Parse source code
|
|
*/
|
|
function parseForESLint(code, options) {
|
|
return {
|
|
ast: parseJSON(code, options),
|
|
visitorKeys: getVisitorKeys(),
|
|
services: { isJSON: true }
|
|
};
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/tokenizer.ts
|
|
/**
|
|
* Tokenizes the given code.
|
|
* @param code The code to tokenize.
|
|
* @param options The options to use for tokenization.
|
|
* @private
|
|
*/
|
|
function tokenize(code, options) {
|
|
const parserOptions = Object.assign({ filePath: "<input>" }, options || {}, {
|
|
loc: true,
|
|
range: true,
|
|
raw: true,
|
|
tokens: true,
|
|
comment: true,
|
|
ecmaVersion: "latest"
|
|
});
|
|
const ctx = getJSONSyntaxContext(options?.jsonSyntax);
|
|
const tokens = [];
|
|
const comments = [];
|
|
const tokenStore = new TokenStore(tokens);
|
|
parserOptions.ctx = ctx;
|
|
parserOptions.tokenStore = tokenStore;
|
|
parserOptions.comments = comments;
|
|
getParser().tokenizer(code, parserOptions).tokenize();
|
|
if (!options?.includeComments) return tokens;
|
|
const result = [];
|
|
let commentIndex = 0;
|
|
for (const token of tokens) {
|
|
while (commentIndex < comments.length && comments[commentIndex].range[0] < token.range[0]) {
|
|
result.push(comments[commentIndex]);
|
|
commentIndex++;
|
|
}
|
|
result.push(token);
|
|
}
|
|
while (commentIndex < comments.length) {
|
|
result.push(comments[commentIndex]);
|
|
commentIndex++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//#endregion
|
|
//#region src/parser/traverse.ts
|
|
/**
|
|
* Check that the given key should be traversed or not.
|
|
* @this {Traversable}
|
|
* @param key The key to check.
|
|
* @returns `true` if the key should be traversed.
|
|
*/
|
|
function fallbackKeysFilter(key) {
|
|
let value = null;
|
|
return key !== "comments" && key !== "leadingComments" && key !== "loc" && key !== "parent" && key !== "range" && key !== "tokens" && key !== "trailingComments" && (value = this[key]) !== 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(fallbackKeysFilter, node);
|
|
}
|
|
/**
|
|
* Get the keys of the given node to traverse it.
|
|
* @param node The node to get.
|
|
* @returns The keys to traverse.
|
|
*/
|
|
function getKeys(node, visitorKeys) {
|
|
return ((visitorKeys || getVisitorKeys())[node.type] || getFallbackKeys(node)).filter((key) => !getNodes(node, key).next().done);
|
|
}
|
|
/**
|
|
* Get the nodes of the given node.
|
|
* @param node The node to get.
|
|
*/
|
|
function* getNodes(node, key) {
|
|
const child = node[key];
|
|
if (Array.isArray(child)) {
|
|
for (const c of child) if (isNode(c)) yield c;
|
|
} else if (isNode(child)) yield child;
|
|
}
|
|
/**
|
|
* Check whether 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) {
|
|
visitor.enterNode(node, parent);
|
|
const keys = getKeys(node, visitor.visitorKeys);
|
|
for (const key of keys) for (const child of getNodes(node, key)) 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/utils/ast.ts
|
|
/**
|
|
* Checks if given node is JSONExpression
|
|
*/
|
|
function isExpression(node) {
|
|
if (node.type === "JSONIdentifier" || node.type === "JSONLiteral") {
|
|
const parent = node.parent;
|
|
if (parent.type === "JSONProperty" && parent.key === node) return false;
|
|
return true;
|
|
}
|
|
if (node.type === "JSONObjectExpression" || node.type === "JSONArrayExpression" || node.type === "JSONUnaryExpression" || node.type === "JSONTemplateLiteral" || node.type === "JSONBinaryExpression") return true;
|
|
return false;
|
|
}
|
|
/**
|
|
* Checks if given node is JSONNumberIdentifier
|
|
*/
|
|
function isNumberIdentifier(node) {
|
|
return isExpression(node) && (node.name === "Infinity" || node.name === "NaN");
|
|
}
|
|
/**
|
|
* Checks if given node is JSONUndefinedIdentifier
|
|
*/
|
|
function isUndefinedIdentifier(node) {
|
|
return isExpression(node) && node.name === "undefined";
|
|
}
|
|
const resolver = {
|
|
Program(node) {
|
|
if (node.body.length !== 1 || node.body[0].type !== "JSONExpressionStatement") throw new Error("Illegal argument");
|
|
return getStaticJSONValue(node.body[0]);
|
|
},
|
|
JSONExpressionStatement(node) {
|
|
return getStaticJSONValue(node.expression);
|
|
},
|
|
JSONObjectExpression(node) {
|
|
const object = {};
|
|
for (const prop of node.properties) Object.assign(object, getStaticJSONValue(prop));
|
|
return object;
|
|
},
|
|
JSONProperty(node) {
|
|
return { [node.key.type === "JSONLiteral" ? `${node.key.value}` : node.key.name]: getStaticJSONValue(node.value) };
|
|
},
|
|
JSONArrayExpression(node) {
|
|
const array = [];
|
|
for (let index = 0; index < node.elements.length; index++) {
|
|
const element = node.elements[index];
|
|
if (element) array[index] = getStaticJSONValue(element);
|
|
}
|
|
return array;
|
|
},
|
|
JSONLiteral(node) {
|
|
if (node.regex) try {
|
|
return new RegExp(node.regex.pattern, node.regex.flags);
|
|
} catch {
|
|
return `/${node.regex.pattern}/${node.regex.flags}`;
|
|
}
|
|
if (node.bigint != null) try {
|
|
return BigInt(node.bigint);
|
|
} catch {
|
|
return `${node.bigint}`;
|
|
}
|
|
return node.value;
|
|
},
|
|
JSONUnaryExpression(node) {
|
|
const value = getStaticJSONValue(node.argument);
|
|
return node.operator === "-" ? -value : value;
|
|
},
|
|
JSONBinaryExpression(node) {
|
|
const left = getStaticJSONValue(node.left);
|
|
const right = getStaticJSONValue(node.right);
|
|
return node.operator === "+" ? left + right : node.operator === "-" ? left - right : node.operator === "*" ? left * right : node.operator === "/" ? left / right : node.operator === "%" ? left % right : node.operator === "**" ? left ** right : (() => {
|
|
throw new Error(`Unknown operator: ${node.operator}`);
|
|
})();
|
|
},
|
|
JSONIdentifier(node) {
|
|
if (node.name === "Infinity") return Infinity;
|
|
if (node.name === "NaN") return NaN;
|
|
if (node.name === "undefined") return;
|
|
throw new Error("Illegal argument");
|
|
},
|
|
JSONTemplateLiteral(node) {
|
|
return getStaticJSONValue(node.quasis[0]);
|
|
},
|
|
JSONTemplateElement(node) {
|
|
return node.value.cooked;
|
|
}
|
|
};
|
|
/**
|
|
* Gets the static value for the given node.
|
|
*/
|
|
function getStaticJSONValue(node) {
|
|
return resolver[node.type](node);
|
|
}
|
|
|
|
//#endregion
|
|
//#region package.json
|
|
var name$1 = "jsonc-eslint-parser";
|
|
var version$1 = "3.1.0";
|
|
|
|
//#endregion
|
|
//#region src/meta.ts
|
|
var meta_exports = /* @__PURE__ */ __exportAll({
|
|
name: () => name,
|
|
version: () => version
|
|
});
|
|
const name = name$1;
|
|
const version = version$1;
|
|
|
|
//#endregion
|
|
//#region src/index.ts
|
|
const VisitorKeys = getVisitorKeys();
|
|
|
|
//#endregion
|
|
export { VisitorKeys, getStaticJSONValue, isExpression, isNumberIdentifier, isUndefinedIdentifier, meta_exports as meta, name, parseForESLint, parseJSON, tokenize, traverseNodes }; |