UNPKG

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.

571 lines (556 loc) 19 kB
var __defProp = Object.defineProperty; var __defProps = Object.defineProperties; var __getOwnPropDescs = Object.getOwnPropertyDescriptors; var __getOwnPropSymbols = Object.getOwnPropertySymbols; 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 __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/ts.ts import path2 from "path"; import ts from "typescript"; // src/transform/index.ts import path from "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 = path.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: path2.basename(childPath) }); childPath = dirName; } this.currTarget = { code, filePath: normalized, dirMap }; for (const { filePath: targetPath } of [this.currTarget, lastTarget]) { if (!targetPath) continue; if (!ts.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 = 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, ...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 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 `*.vue` 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([diagnostic])); }, () => { }, void 0, extraFileExtensions.map((extension) => ({ extension, isMixedContent: true, scriptKind: ts.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, 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(configFileDiagnostics)); } }; const watch = ts.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 ts.formatDiagnostics(diagnostics, { getCanonicalFileName: (f) => f, getCurrentDirectory: () => process.cwd(), getNewLine: () => "\n" }); } function normalizeFileName(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); } function* iterateDirs(filePath) { let target = filePath; let parent; while ((parent = path2.dirname(target)) !== target) { yield parent; target = parent; } } function distinctArray(...list) { return [ ...new Set( ts.sys.useCaseSensitiveFileNames ? list : list.map((s) => s == null ? void 0 : s.toLowerCase()) ) ].filter((s) => s != null); } // src/index.ts import * as tsEslintParser from "@typescript-eslint/parser"; // src/utils/get-project-config-files.ts import fs from "fs"; import path3 from "path"; function getProjectConfigFiles(options) { if (options.project !== true) { return Array.isArray(options.project) ? options.project : [options.project]; } let directory = path3.dirname(options.filePath); const checkedDirectories = [directory]; do { const tsconfigPath = path3.join(directory, "tsconfig.json"); if (fs.existsSync(tsconfigPath)) { return [tsconfigPath]; } directory = path3.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 import { sync as globSync } from "globby"; import isGlob from "is-glob"; import path4 from "path"; import * as ts2 from "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) => !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) ) ) ); 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 = path4.normalize(filePath); if (normalized.endsWith(path4.sep)) { normalized = normalized.slice(0, -1); } return correctPathCasing(normalized); } function ensureAbsolutePath(p, tsconfigRootDir) { return path4.isAbsolute(p) ? p : path4.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 }; } } export { meta_exports as meta, name, parseForESLint2 as parseForESLint };