typescript-eslint-parser-for-extra-files
Version:
An experimental ESLint custom parser for Vue, Svelte, and Astro for use with TypeScript. It provides type information in combination with each framework's ESLint custom parser.
597 lines (581 loc) • 20.9 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
meta: () => meta_exports,
name: () => name,
parseForESLint: () => parseForESLint2
});
module.exports = __toCommonJS(src_exports);
// src/ts.ts
var import_path2 = __toESM(require("path"));
var import_typescript = __toESM(require("typescript"));
// src/transform/index.ts
var import_path = __toESM(require("path"));
// src/transform/vue.ts
function transformForVue(code, context) {
if (context.current) {
return code;
}
const compiler = require("vue/compiler-sfc");
const result = compiler.parse(code);
const compiled = compiler.compileScript(result.descriptor, {
id: "id",
reactivityTransform: true
});
return compiled.content;
}
// src/transform/svelte.ts
function transformForSvelte(code, context) {
if (context.current) {
return code;
}
const svelte2tsx = require("svelte2tsx");
const result = svelte2tsx.svelte2tsx(code, {
filename: context.filePath
});
return `/// <reference types="svelte2tsx/svelte-shims-v4" />
/// <reference types="svelte2tsx/svelte-shims" />
${result.code}`;
}
// src/transform/astro.ts
function transformForAstro(code, context) {
if (context.current) {
return code;
}
const compiler = require("astrojs-compiler-sync");
const result = compiler.convertToTSX(code, {
sourcefile: context.filePath
});
return result.code;
}
// src/transform/index.ts
function transformExtraFile(code, context) {
const ext = import_path.default.extname(context.filePath);
const transform = ext === ".vue" ? transformForVue : ext === ".svelte" ? transformForSvelte : ext === ".astro" ? transformForAstro : () => code;
try {
return transform(code, context);
} catch (e) {
}
return code;
}
// src/ts.ts
var TSServiceManager = class {
constructor() {
this.tsServices = /* @__PURE__ */ new Map();
}
getProgram(code, options) {
const tsconfigPath = options.project;
const extraFileExtensions = [...new Set(options.extraFileExtensions)];
let serviceList = this.tsServices.get(tsconfigPath);
if (!serviceList) {
serviceList = [];
this.tsServices.set(tsconfigPath, serviceList);
}
let service = serviceList.find(
(service2) => extraFileExtensions.every(
(ext) => service2.extraFileExtensions.includes(ext)
)
);
if (!service) {
service = new TSService(tsconfigPath, extraFileExtensions);
serviceList.unshift(service);
}
return service.getProgram(code, options.filePath);
}
};
var TSService = class {
constructor(tsconfigPath, extraFileExtensions) {
this.patchedHostSet = /* @__PURE__ */ new WeakSet();
this.currTarget = {
code: "",
filePath: "",
dirMap: /* @__PURE__ */ new Map()
};
this.fileWatchCallbacks = /* @__PURE__ */ new Map();
this.tsconfigPath = tsconfigPath;
this.extraFileExtensions = extraFileExtensions;
this.watch = this.createWatch(tsconfigPath, extraFileExtensions);
}
getProgram(code, filePath) {
var _a, _b;
const normalized = normalizeFileName(filePath);
const lastTarget = this.currTarget;
const dirMap = /* @__PURE__ */ new Map();
let childPath = normalized;
for (const dirName of iterateDirs(normalized)) {
dirMap.set(dirName, { path: childPath, name: import_path2.default.basename(childPath) });
childPath = dirName;
}
this.currTarget = {
code,
filePath: normalized,
dirMap
};
for (const { filePath: targetPath } of [this.currTarget, lastTarget]) {
if (!targetPath) continue;
if (!import_typescript.default.sys.fileExists(targetPath)) {
(_a = this.fileWatchCallbacks.get(normalizeFileName(this.tsconfigPath))) == null ? void 0 : _a.update();
}
(_b = this.fileWatchCallbacks.get(normalizeFileName(targetPath))) == null ? void 0 : _b.update();
}
const program = this.watch.getProgram().getProgram();
program.getTypeChecker();
return program;
}
createWatch(tsconfigPath, extraFileExtensions) {
const createAbstractBuilder = (...args) => {
const [
rootNames,
options,
argHost,
oldProgram,
configFileParsingDiagnostics,
projectReferences
] = args;
const host = argHost;
if (!this.patchedHostSet.has(host)) {
this.patchedHostSet.add(host);
const getTargetSourceFile = (fileName, languageVersionOrOptions) => {
var _a, _b;
if (this.currTarget.filePath === normalizeFileName(fileName) && isExtra(fileName, extraFileExtensions)) {
return (_b = (_a = this.currTarget).sourceFile) != null ? _b : _a.sourceFile = import_typescript.default.createSourceFile(
this.currTarget.filePath,
this.currTarget.code,
languageVersionOrOptions,
true,
import_typescript.default.ScriptKind.TSX
);
}
return null;
};
const original2 = {
getSourceFile: host.getSourceFile,
getSourceFileByPath: host.getSourceFileByPath
};
host.getSourceFile = (fileName, languageVersionOrOptions, ...args2) => {
var _a;
const originalSourceFile = original2.getSourceFile.call(
host,
fileName,
languageVersionOrOptions,
...args2
);
return (_a = getTargetSourceFile(fileName, languageVersionOrOptions)) != null ? _a : originalSourceFile;
};
host.getSourceFileByPath = (fileName, path5, languageVersionOrOptions, ...args2) => {
var _a;
const originalSourceFile = original2.getSourceFileByPath.call(
host,
fileName,
path5,
languageVersionOrOptions,
...args2
);
return (_a = getTargetSourceFile(fileName, languageVersionOrOptions)) != null ? _a : originalSourceFile;
};
}
return import_typescript.default.createAbstractBuilder(
rootNames,
options,
host,
oldProgram,
configFileParsingDiagnostics,
projectReferences
);
};
const watchCompilerHost = import_typescript.default.createWatchCompilerHost(
tsconfigPath,
{
noEmit: true,
jsx: import_typescript.default.JsxEmit.Preserve,
// This option is required if `includes` only includes `*.vue` files.
// However, the option is not in the documentation.
// https://github.com/microsoft/TypeScript/issues/28447
allowNonTsExtensions: true
},
import_typescript.default.sys,
createAbstractBuilder,
(diagnostic) => {
throw new Error(formatDiagnostics([diagnostic]));
},
() => {
},
void 0,
extraFileExtensions.map((extension) => ({
extension,
isMixedContent: true,
scriptKind: import_typescript.default.ScriptKind.Deferred
}))
);
const original = {
// eslint-disable-next-line @typescript-eslint/unbound-method -- Store original
readFile: watchCompilerHost.readFile,
// eslint-disable-next-line @typescript-eslint/unbound-method -- Store original
fileExists: watchCompilerHost.fileExists,
// eslint-disable-next-line @typescript-eslint/unbound-method -- Store original
readDirectory: watchCompilerHost.readDirectory,
// eslint-disable-next-line @typescript-eslint/unbound-method -- Store original
directoryExists: watchCompilerHost.directoryExists,
// eslint-disable-next-line @typescript-eslint/unbound-method -- Store original
getDirectories: watchCompilerHost.getDirectories
};
watchCompilerHost.getDirectories = (dirName, ...args) => {
var _a;
const result = distinctArray(
...original.getDirectories.call(watchCompilerHost, dirName, ...args),
// Include the path to the target file if the target file does not actually exist.
(_a = this.currTarget.dirMap.get(normalizeFileName(dirName))) == null ? void 0 : _a.name
);
return result;
};
watchCompilerHost.directoryExists = (dirName, ...args) => {
return original.directoryExists.call(watchCompilerHost, dirName, ...args) || // Include the path to the target file if the target file does not actually exist.
this.currTarget.dirMap.has(normalizeFileName(dirName));
};
watchCompilerHost.readDirectory = (dirName, ...args) => {
let results = original.readDirectory.call(
watchCompilerHost,
dirName,
...args
);
const file = this.currTarget.dirMap.get(normalizeFileName(dirName));
if (file) {
if (file.path === this.currTarget.filePath) {
results.push(file.path);
} else {
results = results.filter((f) => file.path !== f && file.name !== f);
}
}
return distinctArray(...results);
};
watchCompilerHost.readFile = (fileName, ...args) => {
const realFileName = getRealFileNameIfExist(fileName);
if (realFileName == null) {
return void 0;
}
if (this.currTarget.filePath === realFileName) {
return transformExtraFile(this.currTarget.code, {
filePath: realFileName,
current: true
});
}
const code = original.readFile.call(
watchCompilerHost,
realFileName,
...args
);
if (!code) {
return code;
}
return transformExtraFile(code, {
filePath: realFileName,
current: false
});
};
watchCompilerHost.fileExists = (fileName) => {
return getRealFileNameIfExist(fileName) != null;
};
const getRealFileNameIfExist = (fileName) => {
const normalizedFileName = normalizeFileName(fileName);
if (this.currTarget.dirMap.has(normalizedFileName)) {
return null;
}
if (this.currTarget.filePath === normalizedFileName) {
return normalizedFileName;
}
const exists = original.fileExists.call(
watchCompilerHost,
normalizedFileName
);
if (exists) {
return normalizedFileName;
}
if (isVirtualTSX(normalizedFileName, extraFileExtensions)) {
const real = normalizedFileName.slice(0, -4);
for (const dts of toExtraDtsFileNames(real, extraFileExtensions)) {
if (original.fileExists.call(watchCompilerHost, dts)) {
return null;
}
}
if (original.fileExists.call(watchCompilerHost, real)) {
return real;
}
}
return null;
};
watchCompilerHost.watchFile = (fileName, callback) => {
const normalized = normalizeFileName(fileName);
this.fileWatchCallbacks.set(normalized, {
update: () => callback(fileName, import_typescript.default.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 === import_typescript.default.DiagnosticCategory.Error && diag.code !== 18003
);
if (configFileDiagnostics.length > 0) {
throw new Error(formatDiagnostics(configFileDiagnostics));
}
};
const watch = import_typescript.default.createWatchProgram(watchCompilerHost);
return watch;
}
};
function toExtraDtsFileNames(fileName, extraFileExtensions) {
const ext = getExtIfExtra(fileName, extraFileExtensions);
if (ext != null) {
return [`${fileName}.d.ts`, `${fileName.slice(0, -ext.length)}.d${ext}.ts`];
}
return [];
}
function isExtra(fileName, extraFileExtensions) {
return getExtIfExtra(fileName, extraFileExtensions) != null;
}
function getExtIfExtra(fileName, extraFileExtensions) {
for (const extraFileExtension of extraFileExtensions) {
if (fileName.endsWith(extraFileExtension)) {
return extraFileExtension;
}
}
return null;
}
function isVirtualTSX(fileName, extraFileExtensions) {
for (const extraFileExtension of extraFileExtensions) {
if (fileName.endsWith(`${extraFileExtension}.tsx`)) {
return true;
}
}
return false;
}
function formatDiagnostics(diagnostics) {
return import_typescript.default.formatDiagnostics(diagnostics, {
getCanonicalFileName: (f) => f,
getCurrentDirectory: () => process.cwd(),
getNewLine: () => "\n"
});
}
function normalizeFileName(fileName) {
let normalized = import_path2.default.normalize(fileName);
if (normalized.endsWith(import_path2.default.sep)) {
normalized = normalized.slice(0, -1);
}
if (import_typescript.default.sys.useCaseSensitiveFileNames) {
return toAbsolutePath(normalized, null);
}
return toAbsolutePath(normalized.toLowerCase(), null);
}
function toAbsolutePath(filePath, baseDir) {
return import_path2.default.isAbsolute(filePath) ? filePath : import_path2.default.join(baseDir || process.cwd(), filePath);
}
function* iterateDirs(filePath) {
let target = filePath;
let parent;
while ((parent = import_path2.default.dirname(target)) !== target) {
yield parent;
target = parent;
}
}
function distinctArray(...list) {
return [
...new Set(
import_typescript.default.sys.useCaseSensitiveFileNames ? list : list.map((s) => s == null ? void 0 : s.toLowerCase())
)
].filter((s) => s != null);
}
// src/index.ts
var tsEslintParser = __toESM(require("@typescript-eslint/parser"));
// src/utils/get-project-config-files.ts
var import_fs = __toESM(require("fs"));
var import_path3 = __toESM(require("path"));
function getProjectConfigFiles(options) {
if (options.project !== true) {
return Array.isArray(options.project) ? options.project : [options.project];
}
let directory = import_path3.default.dirname(options.filePath);
const checkedDirectories = [directory];
do {
const tsconfigPath = import_path3.default.join(directory, "tsconfig.json");
if (import_fs.default.existsSync(tsconfigPath)) {
return [tsconfigPath];
}
directory = import_path3.default.dirname(directory);
checkedDirectories.push(directory);
} while (directory.length > 1 && directory.length >= options.tsconfigRootDir.length);
throw new Error(
`project was set to \`true\` but couldn't find any tsconfig.json relative to '${options.filePath}' within '${options.tsconfigRootDir}'.`
);
}
// src/utils/resolve-project-list.ts
var import_globby = require("globby");
var import_is_glob = __toESM(require("is-glob"));
var import_path4 = __toESM(require("path"));
var ts2 = __toESM(require("typescript"));
function resolveProjectList(options) {
var _a;
const sanitizedProjects = [];
if (options.project != null) {
for (const project of options.project) {
if (typeof project === "string") {
sanitizedProjects.push(project);
}
}
}
if (sanitizedProjects.length === 0) {
return [];
}
const projectFolderIgnoreList = ((_a = options.projectFolderIgnoreList) != null ? _a : ["**/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) => !(0, import_is_glob.default)(project)
);
const globProjects = sanitizedProjects.filter((project) => (0, import_is_glob.default)(project));
const uniqueCanonicalProjectPaths = new Set(
nonGlobProjects.concat(
globProjects.length === 0 ? [] : (0, import_globby.sync)([...globProjects, ...projectFolderIgnoreList], {
cwd: options.tsconfigRootDir
})
).map(
(project) => getCanonicalFileName(
ensureAbsolutePath(project, options.tsconfigRootDir)
)
)
);
return Array.from(uniqueCanonicalProjectPaths);
}
var useCaseSensitiveFileNames = ts2.sys !== void 0 ? ts2.sys.useCaseSensitiveFileNames : true;
var correctPathCasing = useCaseSensitiveFileNames ? (filePath) => filePath : (filePath) => filePath.toLowerCase();
function getCanonicalFileName(filePath) {
let normalized = import_path4.default.normalize(filePath);
if (normalized.endsWith(import_path4.default.sep)) {
normalized = normalized.slice(0, -1);
}
return correctPathCasing(normalized);
}
function ensureAbsolutePath(p, tsconfigRootDir) {
return import_path4.default.isAbsolute(p) ? p : import_path4.default.join(tsconfigRootDir || process.cwd(), p);
}
// src/meta.ts
var meta_exports = {};
__export(meta_exports, {
name: () => name,
version: () => version
});
// package.json
var name = "typescript-eslint-parser-for-extra-files";
var version = "0.7.0";
// src/index.ts
var DEFAULT_EXTRA_FILE_EXTENSIONS = [".vue", ".svelte", ".astro"];
var tsServiceManager = new TSServiceManager();
function parseForESLint2(code, options = {}) {
if (!options.project) {
return tsEslintParser.parseForESLint(code, options);
}
const extraFileExtensions = options.extraFileExtensions || DEFAULT_EXTRA_FILE_EXTENSIONS;
const programs = [];
for (const option of iterateOptions(options)) {
programs.push(tsServiceManager.getProgram(code, option));
}
const filePath = options.filePath;
const parserOptions = __spreadProps(__spreadValues({}, options), {
filePath,
programs,
extraFileExtensions
});
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();
for (const project of resolveProjectList({
project: getProjectConfigFiles({
project: options.project,
tsconfigRootDir,
filePath: options.filePath
}),
projectFolderIgnoreList: options.projectFolderIgnoreList,
tsconfigRootDir
})) {
yield {
project,
filePath: options.filePath,
extraFileExtensions: options.extraFileExtensions || DEFAULT_EXTRA_FILE_EXTENSIONS
};
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
meta,
name,
parseForESLint
});