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.

597 lines (581 loc) 20.9 kB
"use strict"; 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 });