astro-eslint-parser
Version:
Astro component parser for ESLint
1,656 lines (1,631 loc) • 89.1 kB
JavaScript
var __defProp = Object.defineProperty;
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
}) : x)(function(x) {
if (typeof require !== "undefined") return require.apply(this, arguments);
throw Error('Dynamic require of "' + x + '" is not supported');
});
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
// src/visitor-keys.ts
import { unionWith } from "eslint-visitor-keys";
var astroKeys = {
Program: ["body"],
AstroFragment: ["children"],
AstroHTMLComment: [],
AstroDoctype: [],
AstroShorthandAttribute: ["name", "value"],
AstroTemplateLiteralAttribute: ["name", "value"],
AstroRawText: []
};
var KEYS = unionWith(
astroKeys
);
// src/parser/index.ts
import { AST_TOKEN_TYPES as AST_TOKEN_TYPES2 } from "@typescript-eslint/types";
// src/debug.ts
import debugFactory from "debug";
var debug = debugFactory("astro-eslint-parser");
// src/parser/ts-patch.ts
import { createRequire as createRequire2 } from "module";
import path4 from "path";
import fs2 from "fs";
import { satisfies } from "semver";
// src/parser/ts-for-v5/get-project-config-files.ts
import * as fs from "fs";
import * as path from "path";
function getProjectConfigFiles(parseSettings, project) {
if (project !== true) {
return project === void 0 || Array.isArray(project) ? project : [project];
}
let directory = path.dirname(parseSettings.filePath);
const checkedDirectories = [directory];
do {
const tsconfigPath = path.join(directory, "tsconfig.json");
const cached = fs.existsSync(tsconfigPath) && tsconfigPath;
if (cached) {
return [cached];
}
directory = path.dirname(directory);
checkedDirectories.push(directory);
} while (directory.length > 1 && directory.length >= parseSettings.tsconfigRootDir.length);
throw new Error(
`project was set to \`true\` but couldn't find any tsconfig.json relative to '${parseSettings.filePath}' within '${parseSettings.tsconfigRootDir}'.`
);
}
// src/parser/ts-for-v5/programs.ts
import path2 from "path";
var tsServices = /* @__PURE__ */ new Map();
function getTSProgram(code, options, ts) {
const tsconfigPath = options.project;
let service2 = tsServices.get(tsconfigPath);
if (!service2) {
service2 = new TSService(tsconfigPath, ts);
tsServices.set(tsconfigPath, service2);
}
return service2.getProgram(code, options.filePath);
}
var TSService = class {
constructor(tsconfigPath, ts) {
this.patchedHostSet = /* @__PURE__ */ new WeakSet();
this.currTarget = {
code: "",
filePath: ""
};
this.fileWatchCallbacks = /* @__PURE__ */ new Map();
this.ts = ts;
this.watch = this.createWatch(tsconfigPath);
}
getProgram(code, filePath) {
const normalized = normalizeFileName(this.ts, filePath);
const lastTarget = this.currTarget;
this.currTarget = {
code,
filePath: normalized
};
for (const { filePath: targetPath } of [this.currTarget, lastTarget]) {
if (!targetPath) continue;
this.fileWatchCallbacks.get(targetPath)?.update();
}
const program = this.watch.getProgram().getProgram();
program.getTypeChecker();
return program;
}
createWatch(tsconfigPath) {
const { ts } = this;
const createAbstractBuilder = (...buildArgs) => {
const [
rootNames,
options,
argHost,
oldProgram,
configFileParsingDiagnostics,
projectReferences
] = buildArgs;
const host = argHost;
if (!this.patchedHostSet.has(host)) {
this.patchedHostSet.add(host);
const getTargetSourceFile = (fileName, languageVersionOrOptions) => {
var _a;
if (this.currTarget.filePath === normalizeFileName(ts, fileName)) {
return (_a = this.currTarget).sourceFile ?? (_a.sourceFile = ts.createSourceFile(
this.currTarget.filePath,
this.currTarget.code,
languageVersionOrOptions,
true,
ts.ScriptKind.TSX
));
}
return null;
};
const original2 = {
getSourceFile: host.getSourceFile,
getSourceFileByPath: host.getSourceFileByPath
};
host.getSourceFile = (fileName, languageVersionOrOptions, ...args) => {
const originalSourceFile = original2.getSourceFile.call(
host,
fileName,
languageVersionOrOptions,
...args
);
return getTargetSourceFile(fileName, languageVersionOrOptions) ?? originalSourceFile;
};
host.getSourceFileByPath = (fileName, filePath, languageVersionOrOptions, ...args) => {
const originalSourceFile = original2.getSourceFileByPath.call(
host,
fileName,
filePath,
languageVersionOrOptions,
...args
);
return getTargetSourceFile(fileName, languageVersionOrOptions) ?? originalSourceFile;
};
}
return ts.createAbstractBuilder(
rootNames,
options,
host,
oldProgram,
configFileParsingDiagnostics,
projectReferences
);
};
const watchCompilerHost = ts.createWatchCompilerHost(
tsconfigPath,
{
noEmit: true,
jsx: ts.JsxEmit.Preserve,
// This option is required if `includes` only includes `*.astro` files.
// However, the option is not in the documentation.
// https://github.com/microsoft/TypeScript/issues/28447
allowNonTsExtensions: true
},
ts.sys,
createAbstractBuilder,
(diagnostic) => {
throw new Error(formatDiagnostics(ts, [diagnostic]));
},
() => {
},
void 0,
[
{
extension: ".astro",
isMixedContent: true,
scriptKind: ts.ScriptKind.Deferred
}
]
);
const original = {
readFile: watchCompilerHost.readFile
};
watchCompilerHost.readFile = (fileName, ...args) => {
const normalized = normalizeFileName(ts, fileName);
if (this.currTarget.filePath === normalized) {
return this.currTarget.code;
}
return original.readFile.call(watchCompilerHost, fileName, ...args);
};
watchCompilerHost.watchFile = (fileName, callback) => {
const normalized = normalizeFileName(ts, fileName);
this.fileWatchCallbacks.set(normalized, {
update: () => callback(fileName, ts.FileWatcherEventKind.Changed)
});
return {
close: () => {
this.fileWatchCallbacks.delete(normalized);
}
};
};
watchCompilerHost.watchDirectory = () => {
return {
close: () => {
}
};
};
watchCompilerHost.afterProgramCreate = (program) => {
const originalDiagnostics = program.getConfigFileParsingDiagnostics();
const configFileDiagnostics = originalDiagnostics.filter(
(diag) => diag.category === ts.DiagnosticCategory.Error && diag.code !== 18003
);
if (configFileDiagnostics.length > 0) {
throw new Error(formatDiagnostics(ts, configFileDiagnostics));
}
};
const watch = ts.createWatchProgram(watchCompilerHost);
return watch;
}
};
function formatDiagnostics(ts, diagnostics) {
return ts.formatDiagnostics(diagnostics, {
getCanonicalFileName: (f) => f,
getCurrentDirectory: () => process.cwd(),
getNewLine: () => "\n"
});
}
function normalizeFileName(ts, fileName) {
let normalized = path2.normalize(fileName);
if (normalized.endsWith(path2.sep)) {
normalized = normalized.slice(0, -1);
}
if (ts.sys.useCaseSensitiveFileNames) {
return toAbsolutePath(normalized, null);
}
return toAbsolutePath(normalized.toLowerCase(), null);
}
function toAbsolutePath(filePath, baseDir) {
return path2.isAbsolute(filePath) ? filePath : path2.join(baseDir || process.cwd(), filePath);
}
// src/parser/ts-for-v5/resolve-project-list.ts
import { sync as globSync } from "fast-glob";
import isGlob from "is-glob";
import * as path3 from "path";
import { createRequire } from "module";
function resolveProjectList(options) {
const sanitizedProjects = [];
if (typeof options.project === "string") {
sanitizedProjects.push(options.project);
} else if (Array.isArray(options.project)) {
for (const project of options.project) {
if (typeof project === "string") {
sanitizedProjects.push(project);
}
}
}
if (sanitizedProjects.length === 0) {
return [];
}
const projectFolderIgnoreList = (options.projectFolderIgnoreList ?? ["**/node_modules/**"]).reduce((acc, folder) => {
if (typeof folder === "string") {
acc.push(folder);
}
return acc;
}, []).map((folder) => folder.startsWith("!") ? folder : `!${folder}`);
const nonGlobProjects = sanitizedProjects.filter(
(project) => !isGlob(project)
);
const globProjects = sanitizedProjects.filter((project) => isGlob(project));
const uniqueCanonicalProjectPaths = new Set(
nonGlobProjects.concat(
globProjects.length === 0 ? [] : globSync([...globProjects, ...projectFolderIgnoreList], {
cwd: options.tsconfigRootDir
})
).map(
(project) => getCanonicalFileName(
ensureAbsolutePath(project, options.tsconfigRootDir)
)
)
);
const returnValue = Array.from(uniqueCanonicalProjectPaths);
return returnValue;
}
var _correctPathCasing;
function correctPathCasing(filePath) {
if (_correctPathCasing === void 0) {
const ts = createRequire(
path3.join(process.cwd(), "__placeholder__.js")
)("typescript");
const useCaseSensitiveFileNames = ts.sys !== void 0 ? ts.sys.useCaseSensitiveFileNames : true;
_correctPathCasing = useCaseSensitiveFileNames ? (f) => f : (f) => f.toLowerCase();
}
return _correctPathCasing(filePath);
}
function getCanonicalFileName(filePath) {
let normalized = path3.normalize(filePath);
if (normalized.endsWith(path3.sep)) {
normalized = normalized.slice(0, -1);
}
return correctPathCasing(normalized);
}
function ensureAbsolutePath(p, tsconfigRootDir) {
return path3.isAbsolute(p) ? p : path3.join(tsconfigRootDir || process.cwd(), p);
}
// src/parser/ts-for-v5/parse-tsx-for-typescript.ts
var DEFAULT_EXTRA_FILE_EXTENSIONS = [".vue", ".svelte", ".astro"];
function parseTsxForTypeScript(code, options, tsEslintParser, ts) {
const programs = [];
for (const option of iterateOptions(options)) {
programs.push(getTSProgram(code, option, ts));
}
const parserOptions = {
...options,
programs
};
return tsEslintParser.parseForESLint(code, parserOptions);
}
function* iterateOptions(options) {
if (!options) {
throw new Error("`parserOptions` is required.");
}
if (!options.filePath) {
throw new Error("`filePath` is required.");
}
if (!options.project) {
throw new Error(
"Specify `parserOptions.project`. Otherwise there is no point in using this parser."
);
}
const tsconfigRootDir = typeof options.tsconfigRootDir === "string" ? options.tsconfigRootDir : process.cwd();
const projects = resolveProjectList({
project: getProjectConfigFiles(
{ tsconfigRootDir, filePath: options.filePath },
options.project
),
projectFolderIgnoreList: options.projectFolderIgnoreList,
tsconfigRootDir
});
for (const project of projects) {
yield {
project,
filePath: options.filePath,
extraFileExtensions: options.extraFileExtensions || DEFAULT_EXTRA_FILE_EXTENSIONS
};
}
}
// src/parser/ts-patch.ts
function tsPatch(scriptParserOptions, tsParserName) {
if (tsParserName === "typescript-eslint-parser-for-extra-files") {
return {
terminate() {
}
};
}
let targetExt = ".astro";
if (scriptParserOptions.filePath) {
const ext = path4.extname(scriptParserOptions.filePath);
if (ext) {
targetExt = ext;
}
}
try {
const cwd = process.cwd();
const relativeTo = path4.join(cwd, "__placeholder__.js");
const ts = createRequire2(relativeTo)("typescript");
if (satisfies(ts.version, ">=5")) {
const result = tsPatchForV5(ts, scriptParserOptions);
if (result) {
return result;
}
} else {
const result = tsPatchForV4(ts, targetExt);
if (result) {
return result;
}
}
} catch {
}
const tsxFilePath = `${scriptParserOptions.filePath}.tsx`;
scriptParserOptions.filePath = tsxFilePath;
if (!fs2.existsSync(tsxFilePath)) {
fs2.writeFileSync(tsxFilePath, "/* temp for astro-eslint-parser */");
return {
terminate() {
fs2.unlinkSync(tsxFilePath);
}
};
}
return null;
}
function tsPatchForV5(ts, scriptParserOptions) {
return {
terminate() {
},
parse(code, parser) {
return parseTsxForTypeScript(
code,
scriptParserOptions,
parser,
ts
);
}
};
}
function tsPatchForV4(ts, targetExt) {
const { ensureScriptKind, getScriptKindFromFileName } = ts;
if (typeof ensureScriptKind !== "function" || typeof getScriptKindFromFileName !== "function") {
return null;
}
ts.ensureScriptKind = function(fileName, ...args) {
if (fileName.endsWith(targetExt)) {
return ts.ScriptKind.TSX;
}
return ensureScriptKind.call(this, fileName, ...args);
};
ts.getScriptKindFromFileName = function(fileName, ...args) {
if (fileName.endsWith(targetExt)) {
return ts.ScriptKind.TSX;
}
return getScriptKindFromFileName.call(this, fileName, ...args);
};
if (ts.ensureScriptKind === ensureScriptKind || ts.getScriptKindFromFileName === getScriptKindFromFileName) {
return null;
}
return {
terminate() {
ts.ensureScriptKind = ensureScriptKind;
ts.getScriptKindFromFileName = getScriptKindFromFileName;
}
};
}
// src/context/resolve-parser/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");
}
function maybeTSESLintParserObject(value) {
return isEnhancedParserObject(value) && isBasicParserObject(value) && typeof value.createProgram === "function" && typeof value.clearCaches === "function" && typeof value.version === "string";
}
function getTSParserNameFromObject(value) {
if (!isEnhancedParserObject(value)) {
return null;
}
if (value.name === "typescript-eslint-parser-for-extra-files")
return "typescript-eslint-parser-for-extra-files";
if (value.meta?.name === "typescript-eslint/parser")
return "@typescript-eslint/parser";
return null;
}
function isTSESLintParserObject(value) {
if (!isEnhancedParserObject(value)) return false;
if (value.name === "typescript-eslint-parser-for-extra-files")
return true;
if (value.meta?.name === "typescript-eslint/parser") return true;
try {
const result = value.parseForESLint("", {});
const services = result.services;
return Boolean(
services && services.esTreeNodeToTSNodeMap && services.tsNodeToESTreeNodeMap && services.program
);
} catch {
return false;
}
}
// src/parser/script.ts
import { AST_NODE_TYPES } from "@typescript-eslint/types";
import {
analyze as analyzeForTypeScript,
Reference
} from "@typescript-eslint/scope-manager";
import {
Referencer as BaseReferencer,
ScopeManager
} from "eslint-scope";
// src/traverse.ts
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));
}
function getFallbackKeys(node) {
return Object.keys(node).filter(fallbackKeysFilter, node);
}
function getKeys(node, visitorKeys) {
const keys = (visitorKeys || KEYS)[node.type] || getFallbackKeys(node);
return keys.filter((key) => !getNodes(node, key).next().done);
}
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;
}
}
function isNode(x) {
return x !== null && typeof x === "object" && typeof x.type === "string";
}
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);
}
function traverseNodes(node, visitor) {
traverse(node, null, visitor);
}
// src/parser/scope/index.ts
import {
Reference as ReferenceClass,
Variable as VariableClass
} from "@typescript-eslint/scope-manager";
// src/util/index.ts
function sortedLastIndex(array, compare) {
let lower = 0;
let upper = array.length;
while (lower < upper) {
const mid = Math.floor(lower + (upper - lower) / 2);
const target = compare(array[mid]);
if (target < 0) {
lower = mid + 1;
} else if (target > 0) {
upper = mid;
} else {
return mid + 1;
}
}
return upper;
}
function addElementToSortedArray(array, element, compare) {
const index = sortedLastIndex(array, (target) => compare(target, element));
array.splice(index, 0, element);
}
function addElementsToSortedArray(array, elements, compare) {
if (!elements.length) {
return;
}
let last = elements[0];
let index = sortedLastIndex(array, (target) => compare(target, last));
for (const element of elements) {
if (compare(last, element) > 0) {
index = sortedLastIndex(array, (target) => compare(target, element));
}
let e = array[index];
while (e && compare(e, element) <= 0) {
e = array[++index];
}
array.splice(index, 0, element);
last = element;
}
}
// src/parser/scope/index.ts
var READ_FLAG = 1;
var WRITE_FLAG = 2;
var READ_WRITE_FLAG = 3;
var REFERENCE_TYPE_VALUE_FLAG = 1;
var REFERENCE_TYPE_TYPE_FLAG = 2;
function getProgramScope(scopeManager) {
const globalScope = scopeManager.globalScope;
return globalScope.childScopes.find((s) => s.type === "module") || globalScope;
}
function removeAllScopeAndVariableAndReference(target, info) {
const removeTargetScopes = /* @__PURE__ */ new Set();
traverseNodes(target, {
visitorKeys: info.visitorKeys,
enterNode(node) {
const scope = info.scopeManager.acquire(node);
if (scope) {
removeTargetScopes.add(scope);
return;
}
if (node.type === "Identifier" || node.type === "JSXIdentifier") {
let targetScope = getInnermostScopeFromNode(info.scopeManager, node);
while (targetScope && targetScope.block.type !== "Program" && target.range[0] <= targetScope.block.range[0] && targetScope.block.range[1] <= target.range[1]) {
targetScope = targetScope.upper;
}
if (removeTargetScopes.has(targetScope)) {
return;
}
removeIdentifierVariable(node, targetScope);
removeIdentifierReference(node, targetScope);
}
},
leaveNode() {
}
});
for (const scope of removeTargetScopes) {
removeScope(info.scopeManager, scope);
}
}
function addVirtualReference(node, variable, scope, status) {
const reference = new ReferenceClass(
node,
scope,
status.write && status.read ? READ_WRITE_FLAG : status.write ? WRITE_FLAG : READ_FLAG,
void 0,
// writeExpr
void 0,
// maybeImplicitGlobal
void 0,
// init
status.typeRef ? REFERENCE_TYPE_TYPE_FLAG : REFERENCE_TYPE_VALUE_FLAG
);
reference.astroVirtualReference = true;
addReference(variable.references, reference);
reference.resolved = variable;
if (status.forceUsed) {
variable.eslintUsed = true;
}
return reference;
}
function addGlobalVariable(reference, scopeManager) {
const globalScope = scopeManager.globalScope;
const name2 = reference.identifier.name;
let variable = globalScope.set.get(name2);
if (!variable) {
variable = new VariableClass(name2, globalScope);
globalScope.variables.push(variable);
globalScope.set.set(name2, variable);
}
reference.resolved = variable;
variable.references.push(reference);
return variable;
}
function removeReferenceFromThrough(reference, baseScope) {
const variable = reference.resolved;
const name2 = reference.identifier.name;
let scope = baseScope;
while (scope) {
for (const ref of [...scope.through]) {
if (reference === ref) {
scope.through.splice(scope.through.indexOf(ref), 1);
} else if (ref.identifier.name === name2) {
ref.resolved = variable;
if (!variable.references.includes(ref)) {
addReference(variable.references, ref);
}
scope.through.splice(scope.through.indexOf(ref), 1);
}
}
scope = scope.upper;
}
}
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 index2 = upper.childScopes.indexOf(scope);
if (index2 >= 0) {
upper.childScopes.splice(index2, 1);
}
}
const index = scopeManager.scopes.indexOf(scope);
if (index >= 0) {
scopeManager.scopes.splice(index, 1);
}
}
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 name2 = reference.identifier.name;
if (reference.resolved === baseScope.set.get(name2)) {
baseScope.set.delete(name2);
}
} 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;
}
}
function removeIdentifierVariable(node, scope) {
for (let varIndex = 0; varIndex < scope.variables.length; varIndex++) {
const variable = scope.variables[varIndex];
const defIndex = variable.defs.findIndex((def) => def.name === node);
if (defIndex < 0) {
continue;
}
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(varIndex, 1);
const name2 = node.name;
if (variable === scope.set.get(name2)) {
scope.set.delete(name2);
}
} else {
const idIndex = variable.identifiers.indexOf(node);
if (idIndex >= 0) {
variable.identifiers.splice(idIndex, 1);
}
}
return;
}
}
function removeIdentifierReference(node, scope) {
const reference = scope.references.find((ref) => ref.identifier === node);
if (reference) {
removeReference(reference, scope);
return true;
}
const location = node.range[0];
const pendingScopes = [];
for (const childScope of scope.childScopes) {
const range = childScope.block.range;
if (range[0] <= location && location < range[1]) {
if (removeIdentifierReference(node, childScope)) {
return true;
}
} else {
pendingScopes.push(childScope);
}
}
for (const childScope of pendingScopes) {
if (removeIdentifierReference(node, childScope)) {
return true;
}
}
return false;
}
function getInnermostScopeFromNode(scopeManager, currentNode) {
return getInnermostScope(
getScopeFromNode(scopeManager, currentNode),
currentNode
);
}
function getScopeFromNode(scopeManager, currentNode) {
let node = currentNode;
for (; node; node = node.parent || null) {
const scope = scopeManager.acquire(node, false);
if (scope) {
if (scope.type === "function-expression-name") {
return scope.childScopes[0];
}
if (scope.type === "global" && node.type === "Program" && node.sourceType === "module") {
return scope.childScopes.find((s) => s.type === "module") || scope;
}
return scope;
}
}
const global = scopeManager.globalScope;
return global;
}
function getInnermostScope(initialScope, node) {
for (const childScope of initialScope.childScopes) {
const range = childScope.block.range;
if (range[0] <= node.range[0] && node.range[1] <= range[1]) {
return getInnermostScope(childScope, node);
}
}
return initialScope;
}
function referencesToThrough(references, baseScope) {
let scope = baseScope;
while (scope) {
addAllReferences(scope.through, references);
scope = scope.upper;
}
}
function addAllReferences(list, elements) {
addElementsToSortedArray(
list,
elements,
(a, b) => a.identifier.range[0] - b.identifier.range[0]
);
}
function addReference(list, reference) {
addElementToSortedArray(
list,
reference,
(a, b) => a.identifier.range[0] - b.identifier.range[0]
);
}
// src/parser/script.ts
function parseScript(code, ctx, parserOptionsCtx) {
const result = parseScriptInternal(code, ctx, parserOptionsCtx);
const parserOptions = parserOptionsCtx.parserOptions;
if (!result.scopeManager && parserOptions.eslintScopeManager) {
result.scopeManager = analyzeScope(result, parserOptions);
}
return result;
}
function analyzeScope(result, parserOptions) {
try {
return analyzeForTypeScript(result.ast, {
globalReturn: parserOptions.ecmaFeatures?.globalReturn,
jsxPragma: parserOptions.jsxPragma,
jsxFragmentName: parserOptions.jsxFragmentName,
lib: parserOptions.lib,
sourceType: parserOptions.sourceType
});
} catch {
}
const ecmaFeatures = parserOptions.ecmaFeatures || {};
return analyzeForEcmaScript(result.ast, {
ignoreEval: true,
nodejsScope: ecmaFeatures.globalReturn,
impliedStrict: ecmaFeatures.impliedStrict,
ecmaVersion: 1e8,
sourceType: parserOptions.sourceType === "commonjs" ? "script" : parserOptions.sourceType || "script",
// @ts-expect-error -- Type bug?
childVisitorKeys: result.visitorKeys || KEYS,
fallback: getKeys
});
}
function parseScriptInternal(code, _ctx, parserOptionsCtx) {
const parser = parserOptionsCtx.getParser();
let patchResult;
try {
const parserOptions = parserOptionsCtx.parserOptions;
if (parserOptionsCtx.isTypeScript() && parserOptions.filePath && parserOptions.project) {
patchResult = tsPatch(parserOptions, parserOptionsCtx.getTSParserName());
} else if (parserOptionsCtx.isTypeScript() && parserOptions.filePath && parserOptions.projectService) {
console.warn(
"`astro-eslint-parser` does not support the `projectService` option, it will parse it as `project: true` instead."
);
patchResult = tsPatch(
{ ...parserOptions, project: true, projectService: void 0 },
parserOptionsCtx.getTSParserName()
);
}
const result = isEnhancedParserObject(parser) ? patchResult?.parse ? patchResult.parse(code, parser) : parser.parseForESLint(code, parserOptions) : parser.parse(code, parserOptions);
if ("ast" in result && result.ast != null) {
return result;
}
return { ast: result };
} catch (e) {
debug(
"[script] parsing error:",
e.message,
`@ ${JSON.stringify(code)}
${code}`
);
throw e;
} finally {
patchResult?.terminate();
}
}
var Referencer = class extends BaseReferencer {
JSXAttribute(node) {
this.visit(node.value);
}
JSXClosingElement() {
}
JSXFragment(node) {
this.visitChildren(node);
}
JSXIdentifier(node) {
const scope = this.currentScope();
const ref = new Reference(
node,
scope,
READ_FLAG,
void 0,
void 0,
false,
REFERENCE_TYPE_VALUE_FLAG
);
scope.references.push(ref);
scope.__left.push(ref);
}
JSXMemberExpression(node) {
if (node.object.type !== AST_NODE_TYPES.JSXIdentifier) {
this.visit(node.object);
} else {
if (node.object.name !== "this") {
this.visit(node.object);
}
}
}
JSXOpeningElement(node) {
if (node.name.type === AST_NODE_TYPES.JSXIdentifier) {
if (node.name.name[0].toUpperCase() === node.name.name[0] || node.name.name === "this") {
this.visit(node.name);
}
} else {
this.visit(node.name);
}
for (const attr of node.attributes) {
this.visit(attr);
}
}
};
function analyzeForEcmaScript(tree, providedOptions) {
const options = Object.assign(
{
optimistic: false,
nodejsScope: false,
impliedStrict: false,
sourceType: "script",
// one of ['script', 'module', 'commonjs']
ecmaVersion: 5,
childVisitorKeys: null,
fallback: "iteration"
},
providedOptions
);
const scopeManager = new ScopeManager(
// @ts-expect-error -- No typings
options
);
const referencer = new Referencer(options, scopeManager);
referencer.visit(tree);
return scopeManager;
}
// src/parser/sort.ts
function sort(tokens) {
return tokens.sort((a, b) => {
if (a.range[0] !== b.range[0]) {
return a.range[0] - b.range[0];
}
return a.range[1] - b.range[1];
});
}
// src/parser/process-template.ts
import { AST_TOKEN_TYPES, AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/types";
// src/astro/index.ts
import { EntityDecoder, DecodingMode, htmlDecodeTree } from "entities/decode";
// src/errors.ts
var ParseError = class extends SyntaxError {
/**
* Initialize this ParseError instance.
*/
constructor(message, offset, ctx) {
super(message);
if (typeof offset === "number") {
this.index = offset;
const loc = ctx.getLocFromIndex(offset);
this.lineNumber = loc.line;
this.column = loc.column;
} else {
this.index = ctx.getIndexFromLoc(offset);
this.lineNumber = offset.line;
this.column = offset.column;
}
this.originalAST = ctx.originalAST;
}
};
// src/astro/index.ts
function isTag(node) {
return node.type === "element" || node.type === "custom-element" || node.type === "component" || node.type === "fragment";
}
function isParent(node) {
return Array.isArray(node.children);
}
function walkElements(parent, code, enter, leave, parents = []) {
const children = getSortedChildren(parent, code);
const currParents = [parent, ...parents];
for (const node of children) {
enter(node, currParents);
if (isParent(node)) {
walkElements(node, code, enter, leave, currParents);
}
leave(node, currParents);
}
}
function walk(parent, code, enter, leave) {
walkElements(
parent,
code,
(node, parents) => {
enter(node, parents);
if (isTag(node)) {
const attrParents = [node, ...parents];
for (const attr of node.attributes) {
enter(attr, attrParents);
leave(attr, attrParents);
}
}
},
leave
);
}
function calcStartTagEndOffset(node, ctx) {
const lastAttr = node.attributes[node.attributes.length - 1];
let beforeCloseIndex;
if (lastAttr) {
beforeCloseIndex = calcAttributeEndOffset(lastAttr, ctx);
} else {
const info2 = getTokenInfo(
ctx,
[`<${node.name}`],
node.position.start.offset
);
beforeCloseIndex = info2.index + info2.match.length;
}
const info = getTokenInfo(ctx, [[">", "/>"]], beforeCloseIndex);
return info.index + info.match.length;
}
function calcAttributeEndOffset(node, ctx) {
let info;
if (node.kind === "empty") {
info = getTokenInfo(ctx, [node.name], node.position.start.offset);
} else if (node.kind === "quoted") {
info = getTokenInfo(
ctx,
[
[
{
token: `"${node.value}"`,
htmlEntityDecode: true
},
{
token: `'${node.value}'`,
htmlEntityDecode: true
},
{
token: node.value,
htmlEntityDecode: true
}
]
],
calcAttributeValueStartOffset(node, ctx)
);
} else if (node.kind === "expression") {
info = getTokenInfo(
ctx,
["{", node.value, "}"],
calcAttributeValueStartOffset(node, ctx)
);
} else if (node.kind === "shorthand") {
info = getTokenInfo(
ctx,
["{", node.name, "}"],
node.position.start.offset
);
} else if (node.kind === "spread") {
info = getTokenInfo(
ctx,
["{", "...", node.name, "}"],
node.position.start.offset
);
} else if (node.kind === "template-literal") {
info = getTokenInfo(
ctx,
[`\`${node.value}\``],
calcAttributeValueStartOffset(node, ctx)
);
} else {
throw new ParseError(
`Unknown attr kind: ${node.kind}`,
node.position.start.offset,
ctx
);
}
return info.index + info.match.length;
}
function calcAttributeValueStartOffset(node, ctx) {
let info;
if (node.kind === "quoted") {
info = getTokenInfo(
ctx,
[
node.name,
"=",
[`"`, `'`, { token: node.value, htmlEntityDecode: true }]
],
node.position.start.offset
);
} else if (node.kind === "expression") {
info = getTokenInfo(
ctx,
[node.name, "=", "{"],
node.position.start.offset
);
} else if (node.kind === "template-literal") {
info = getTokenInfo(
ctx,
[node.name, "=", "`"],
node.position.start.offset
);
} else {
throw new ParseError(
`Unknown attr kind: ${node.kind}`,
node.position.start.offset,
ctx
);
}
return info.index;
}
function getEndOffset(node, ctx) {
if (node.position.end?.offset != null) {
return node.position.end.offset;
}
if (isTag(node)) return calcTagEndOffset(node, ctx);
if (node.type === "expression") return calcExpressionEndOffset(node, ctx);
if (node.type === "comment") return calcCommentEndOffset(node, ctx);
if (node.type === "frontmatter") {
const start = node.position.start.offset;
return ctx.code.indexOf("---", start + 3) + 3;
}
if (node.type === "doctype") {
const start = node.position.start.offset;
return ctx.code.indexOf(">", start) + 1;
}
if (node.type === "text") {
const start = node.position.start.offset;
return start + node.value.length;
}
if (node.type === "root") {
return ctx.code.length;
}
throw new Error(`unknown type: ${node.type}`);
}
function calcContentEndOffset(parent, ctx) {
const code = ctx.code;
if (isTag(parent)) {
const end = getEndOffset(parent, ctx);
if (code[end - 1] !== ">") {
return end;
}
const index = code.lastIndexOf("</", end - 1);
if (index >= 0 && code.slice(index + 2, end - 1).trim() === parent.name) {
return index;
}
return end;
} else if (parent.type === "expression") {
const end = getEndOffset(parent, ctx);
return code.lastIndexOf("}", end);
} else if (parent.type === "root") {
return code.length;
}
throw new Error(`unknown type: ${parent.type}`);
}
function getSelfClosingTag(node, ctx) {
if (node.children.length > 0) {
return null;
}
const code = ctx.code;
const startTagEndOffset = calcStartTagEndOffset(node, ctx);
if (code.startsWith("/>", startTagEndOffset - 2)) {
return {
offset: startTagEndOffset,
end: "/>"
};
}
if (code.startsWith(`</${node.name}`, startTagEndOffset)) {
return null;
}
return {
offset: startTagEndOffset,
end: ">"
};
}
function getEndTag(node, ctx) {
let beforeIndex;
if (node.children.length) {
const lastChild = node.children[node.children.length - 1];
beforeIndex = getEndOffset(lastChild, ctx);
} else {
beforeIndex = calcStartTagEndOffset(node, ctx);
}
beforeIndex = skipSpaces(ctx.code, beforeIndex);
if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
const offset = beforeIndex;
beforeIndex = beforeIndex + 2 + node.name.length;
const info = getTokenInfo(ctx, [">"], beforeIndex);
const end = info.index + info.match.length;
return {
offset,
tag: ctx.code.slice(offset, end)
};
}
return null;
}
function calcCommentEndOffset(node, ctx) {
const info = getTokenInfo(
ctx,
["<!--", node.value, "-->"],
node.position.start.offset
);
return info.index + info.match.length;
}
function calcTagEndOffset(node, ctx) {
let beforeIndex;
if (node.children.length) {
const lastChild = node.children[node.children.length - 1];
beforeIndex = getEndOffset(lastChild, ctx);
} else {
beforeIndex = calcStartTagEndOffset(node, ctx);
}
beforeIndex = skipSpaces(ctx.code, beforeIndex);
if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
beforeIndex = beforeIndex + 2 + node.name.length;
const info = getTokenInfo(ctx, [">"], beforeIndex);
return info.index + info.match.length;
}
return beforeIndex;
}
function calcExpressionEndOffset(node, ctx) {
if (node.children.length) {
const lastChild = node.children[node.children.length - 1];
const beforeIndex = getEndOffset(lastChild, ctx);
const info2 = getTokenInfo(ctx, ["}"], beforeIndex);
return info2.index + info2.match.length;
}
const info = getTokenInfo(ctx, ["{", "}"], node.position.start.offset);
return info.index + info.match.length;
}
function getTokenInfo(ctx, tokens, position) {
let lastMatch;
for (const t of tokens) {
const index = lastMatch ? lastMatch.index + lastMatch.match.length : position;
const m = Array.isArray(t) ? matchOfForMulti(t, index) : match(t, index);
if (m == null) {
throw new ParseError(
`Unknown token at ${index}, expected: ${JSON.stringify(
t
)}, actual: ${JSON.stringify(ctx.code.slice(index, index + 10))}`,
index,
ctx
);
}
lastMatch = m;
}
return lastMatch;
function match(token, position2) {
const search = typeof token === "string" ? token : token.token;
const index = search.trim() === search ? skipSpaces(ctx.code, position2) : position2;
if (ctx.code.startsWith(search, index)) {
return {
match: search,
index
};
}
if (typeof token !== "string") {
return matchWithHTMLEntity(token, index);
}
return null;
}
function matchOfForMulti(search, position2) {
for (const s of search) {
const m = match(s, position2);
if (m) {
return m;
}
}
return null;
}
function matchWithHTMLEntity(token, position2) {
const search = token.token;
let codeOffset = position2;
let searchOffset = 0;
while (searchOffset < search.length) {
const searchChar = search[searchOffset];
if (ctx.code[codeOffset] === searchChar) {
if (searchChar === "&") {
const entityCandidate = ctx.code.slice(codeOffset, codeOffset + 5);
if (entityCandidate === "&" || entityCandidate === "&") {
codeOffset += 5;
searchOffset++;
continue;
}
}
codeOffset++;
searchOffset++;
continue;
}
const entity = getHTMLEntity(codeOffset);
if (entity?.entity === searchChar) {
codeOffset += entity.length;
searchOffset++;
continue;
}
return null;
}
return {
match: ctx.code.slice(position2, codeOffset),
index: position2
};
function getHTMLEntity(position3) {
let codeOffset2 = position3;
if (ctx.code[codeOffset2++] !== "&") return null;
let entity = "";
const entityDecoder = new EntityDecoder(
htmlDecodeTree,
(cp) => entity += String.fromCodePoint(cp)
);
entityDecoder.startEntity(DecodingMode.Attribute);
const length = entityDecoder.write(ctx.code, codeOffset2);
if (length < 0) {
return null;
}
if (length === 0) {
return null;
}
return {
entity,
length
};
}
}
}
function skipSpaces(string, position) {
const re = /\s*/g;
re.lastIndex = position;
const match = re.exec(string);
if (match) {
return match.index + match[0].length;
}
return position;
}
function getSortedChildren(parent, code) {
if (parent.type === "root" && parent.children[0]?.type === "frontmatter") {
const children = [...parent.children];
if (children.every((n) => n.position)) {
return children.sort(
(a, b) => a.position.start.offset - b.position.start.offset
);
}
let start = skipSpaces(code, 0);
if (code.startsWith("<!", start)) {
const frontmatter = children.shift();
const before = [];
let first;
while (first = children.shift()) {
start = skipSpaces(code, start);
if (first.type === "comment" && code.startsWith("<!--", start)) {
start = code.indexOf("-->", start + 4) + 3;
before.push(first);
} else if (first.type === "doctype" && code.startsWith("<!", start)) {
start = code.indexOf(">", start + 2) + 1;
before.push(first);
} else {
children.unshift(first);
break;
}
}
return [...before, frontmatter, ...children];
}
}
return parent.children;
}
// src/context/restore.ts
var RestoreNodeProcessContext = class {
constructor(result, nodeMap) {
this.removeTokens = /* @__PURE__ */ new Set();
this.result = result;
this.nodeMap = nodeMap;
}
addRemoveToken(test) {
this.removeTokens.add(test);
}
getParent(node) {
return this.nodeMap.get(node) || null;
}
findToken(startIndex) {
const tokens = this.result.ast.tokens || [];
return tokens.find((t) => t.range[0] === startIndex) || null;
}
};
var RestoreContext = class {
constructor(ctx) {
this.offsets = [];
this.virtualFragments = [];
this.restoreNodeProcesses = [];
this.tokens = [];
this.ctx = ctx;
}
addRestoreNodeProcess(process2) {
this.restoreNodeProcesses.push(process2);
}
addOffset(offset) {
this.offsets.push(offset);
}
addVirtualFragmentRange(start, end) {
const peek = this.virtualFragments[this.virtualFragments.length - 1];
if (peek && peek.end === start) {
peek.end = end;
return;
}
this.virtualFragments.push({ start, end });
}
addToken(type, range) {
if (range[0] >= range[1]) {
return;
}
this.tokens.push(this.ctx.buildToken(type, range));
}
/**
* Restore AST nodes
*/
restore(result) {
const nodeMap = remapLocationsAndGetNodeMap(result, this.tokens, {
remapLocation: (n) => this.remapLocation(n),
removeToken: (token) => this.virtualFragments.some(
(f) => f.start <= token.range[0] && token.range[1] <= f.end
)
});
restoreNodes(result, nodeMap, this.restoreNodeProcesses);
const firstOffset = Math.min(
...[result.ast.body[0], result.ast.tokens?.[0], result.ast.comments?.[0]].filter((t) => Boolean(t)).map((t) => t.range[0])
);
if (firstOffset < result.ast.range[0]) {
result.ast.range[0] = firstOffset;
result.ast.loc.start = this.ctx.getLocFromIndex(firstOffset);
}
}
remapLocation(node) {
let [start, end] = node.range;
const startFragment = this.virtualFragments.find(
(f) => f.start <= start && start < f.end
);
if (startFragment) {
start = startFragment.end;
}
const endFragment = this.virtualFragments.find(
(f) => f.start < end && end <= f.end
);
if (endFragment) {
end = endFragment.start;
if (startFragment === endFragment) {
start = startFragment.start;
}
}
if (end < start) {
const w = start;
start = end;
end = w;
}
const locs = this.ctx.getLocations(...this.getRemapRange(start, end));
node.loc = locs.loc;
node.range = locs.range;
if (node.start != null) {
delete node.start;
}
if (node.end != null) {
delete node.end;
}
}
getRemapRange(start, end) {
if (!this.offsets.length) {
return [start, end];
}
let lastStart = this.offsets[0];
let lastEnd = this.offsets[0];
for (const offset of this.offsets) {
if (offset.dist <= start) {
lastStart = offset;
}
if (offset.dist < end) {
lastEnd = offset;
} else {
if (offset.dist === end && start === end) {
lastEnd = offset;
}
break;
}
}
const remapStart = lastStart.original + (start - lastStart.dist);
const remapEnd = lastEnd.original + (end - lastEnd.dist);
if (remapEnd < remapStart) {
return [remapEnd, remapStart];
}
return [remapStart, remapEnd];
}
};
function remapLocationsAndGetNodeMap(result, restoreTokens, {
remapLocation,
removeToken
}) {
const traversed = /* @__PURE__ */ new Map();
traverseNodes(result.ast, {
visitorKeys: result.visitorKeys,
enterNode: (node, p) => {
if (!traversed.has(node)) {
traversed.set(node, p);
remapLocation(node);
}
},
leaveNode: (_node) => {
}
});
const tokens = [...restoreTokens];
for (const token of result.ast.tokens || []) {
if (removeToken(token)) {
continue;
}
remapLocation(token);
tokens.push(token);
}
result.ast.tokens = tokens;
for (const token of result.ast.comments || []) {
remapLocation(token);
}
return traversed;
}
function restoreNodes(result, nodeMap, restoreNodeProcesses) {
const context = new RestoreNodeProcessContext(result, nodeMap);
const restoreNodeProcessesSet = new Set(restoreNodeProcesses);
for (const [node] of nodeMap) {
if (!restoreNodeProcessesSet.size) {
break;
}
for (const proc of [...restoreNodeProcessesSet]) {
if (proc(node, context)) {
restoreNodeProcessesSet.delete(proc);
}
}
}
if (context.removeTokens.size) {
const tokens = result.ast.tokens || [];
for (let index = tokens.length - 1; index >= 0; index--) {
const token = tokens[index];
for (const rt of context.removeTokens) {
if (rt(token)) {
tokens.splice(index, 1);
context.removeTokens.delete(rt);
if (!context.removeTokens.size) {
break;
}
}
}
}
}
}
// src/context/script.ts
var VirtualScriptContext = class {
constructor(ctx) {
this.script = "";
this.consumedIndex = 0;
this.originalCode = ctx.code;
this.restoreContext = new RestoreContext(ctx);
}
skipOriginalOffset(offset) {
this.consumedIndex += offset;
}
skipUntilOriginalOffset(offset) {
this.consumedIndex = Math.max(offset, this.consumedIndex);
}
appendOriginal(index) {
if (this.consumedIndex >= index) {
return;
}
this.restoreContext.addOffset({
original: this.consumedIndex,
dist: this.script.length
});
this.script += this.originalCode.slice(this.consumedIndex, index);
this.consumedIndex = index;
}
appendVirtualScript(virtualFragment) {
const start = this.script.length;
this.script += virtualFragment;
this.restoreContext.addVirtualFragmentRange(start, this.script.length);
}
};
// src/parser/process-template.ts
function processTemplate(ctx, resultTemplate) {
let uniqueIdSeq = 0;
const usedUniqueIds = /* @__PURE__ */ new Set();
const script = new VirtualScriptContext(ctx);
let fragmentOpened = false;
function openRootFragment(startOffset) {
script.appendVirtualScript("<>");
fragmentOpened = true;
script.restoreContext.addRestoreNodeProcess((scriptNode, { result }) => {
if (scriptNode.type === AST_NODE_TYPES2.ExpressionStatement && scriptNode.expression.type === AST_NODE_TYPES2.JSXFragment && scriptNode.range[0] === startOffset && result.ast.body.includes(scriptNode)) {
const index = result.ast.body.indexOf(scriptNode);
const rootFragment = result.ast.body[index] = scriptNode.expression;
delete rootFragment.closingFragment;
delete rootFragment.openingFragment;
rootFragment.type = "AstroFragment";
return true;
}
return false;
});
}
walkElements(
resultTemplate.ast,
ctx.code,
// eslint-disable-next-line complexity -- X(
(node, [parent]) => {
if (node.type === "frontmatter") {
const start = node.position.start.offset;
if (fragmentOpened) {
script.appendVirtualScript("</>;");
fragmentOpened = false;
}
script.appendOriginal(start);
script.skipOriginalOffset(3);
const end = getEndOffset(node, ctx);
const scriptStart = start + 3;
let scriptEnd = end - 3;
let endChar;
while (scriptStart < scriptEnd - 1 && (endChar = ctx.code[scriptEnd - 1]) && !endChar.trim()) {
scriptEnd--;
}
script.appendOriginal(scriptEnd);
script.appendVirtualScript("\n;");
script.skipOriginalOffset(end - scriptEnd);
script.restoreContext.add