UNPKG

@fimbul/wotan

Version:

Pluggable TypeScript and JavaScript linter

248 lines 10.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ProjectHost = void 0; const tslib_1 = require("tslib"); const ts = require("typescript"); const utils_1 = require("./utils"); const path = require("path"); const bind_decorator_1 = require("bind-decorator"); const debug = require("debug"); const log = debug('wotan:projectHost'); const additionalExtensions = ['.json']; // @internal class ProjectHost { constructor(cwd, config, fs, configManager, processorLoader) { this.cwd = cwd; this.config = config; this.fs = fs; this.configManager = configManager; this.processorLoader = processorLoader; this.reverseMap = new Map(); this.files = []; this.directoryEntries = new Map(); this.processedFiles = new Map(); this.sourceFileCache = new Map(); this.fileContent = new Map(); this.tsconfigCache = new Map(); this.commandLineCache = new Map(); this.parseConfigHost = { useCaseSensitiveFileNames: this.useCaseSensitiveFileNames(), readDirectory: (rootDir, extensions, excludes, includes, depth) => this.readDirectory(rootDir, extensions, excludes, includes, depth), fileExists: (f) => this.fileExists(f), readFile: (f) => this.readFile(f), }; this.getCanonicalFileName = ts.sys.useCaseSensitiveFileNames ? (f) => f : (f) => f.toLowerCase(); this.moduleResolutionCache = ts.createModuleResolutionCache(this.cwd, this.getCanonicalFileName); this.compilerOptions = {}; this.getDefaultLibFileName = ts.getDefaultLibFilePath; this.realpath = this.fs.realpath === undefined ? undefined : (fileName) => this.fs.realpath(fileName); } getProcessedFileInfo(fileName) { return this.processedFiles.get(fileName); } readDirectory(rootDir, extensions, excludes, includes, depth) { return ts.matchFiles(rootDir, extensions, excludes, includes, this.useCaseSensitiveFileNames(), this.cwd, depth, (dir) => utils_1.resolveCachedResult(this.directoryEntries, dir, this.processDirectory), (f) => this.safeRealpath(f)); } /** * Try to find and load the configuration for a file. * If it fails, just continue as if there was no config. * This may happen during project setup if there is an invalid config file anywhere in a scanned folder. */ tryFindConfig(file) { try { return this.configManager.find(file); } catch (e) { log("Error while loading configuration for '%s': %s", file, e.message); return; } } processDirectory(dir) { const files = []; const directories = []; const result = { files, directories }; let entries; try { entries = this.fs.readDirectory(dir); } catch { return result; } for (const entry of entries) { switch (entry.kind) { case 1 /* File */: { const fileName = `${dir}/${entry.name}`; if (!utils_1.hasSupportedExtension(fileName, additionalExtensions)) { const c = this.config || this.tryFindConfig(fileName); const processor = c && this.configManager.getProcessor(c, fileName); if (processor) { const ctor = this.processorLoader.loadProcessor(processor); const suffix = ctor.getSuffixForFile({ fileName, getSettings: () => this.configManager.getSettings(c, fileName), readFile: () => this.fs.readFile(fileName), }); const newName = fileName + suffix; if (utils_1.hasSupportedExtension(newName, additionalExtensions)) { files.push(entry.name + suffix); this.reverseMap.set(newName, fileName); break; } } } files.push(entry.name); this.files.push(fileName); break; } case 2 /* Directory */: directories.push(entry.name); } } return result; } fileExists(file) { switch (this.fs.getKind(file)) { case 2 /* Directory */: case 3 /* Other */: return false; case 1 /* File */: return true; default: return utils_1.hasSupportedExtension(file, additionalExtensions) && this.getFileSystemFile(file) !== undefined; } } directoryExists(dir) { return this.fs.isDirectory(dir); } getFileSystemFile(file) { if (this.files.includes(file)) return file; const reverse = this.reverseMap.get(file); if (reverse !== undefined) return reverse; const dirname = path.posix.dirname(file); if (this.directoryEntries.has(dirname)) return; if (this.fs.isFile(file)) return file; this.directoryEntries.set(dirname, this.processDirectory(dirname)); return this.getFileSystemFile(file); } readFile(file) { return utils_1.resolveCachedResult(this.fileContent, file, (f) => this.fs.readFile(f)); } readProcessedFile(file) { const realFile = this.getFileSystemFile(file); if (realFile === undefined) return; let content = this.fs.readFile(realFile); const config = this.config || this.tryFindConfig(realFile); if (config === undefined) return content; const processorPath = this.configManager.getProcessor(config, realFile); if (processorPath === undefined) return content; const ctor = this.processorLoader.loadProcessor(processorPath); const processor = new ctor({ source: content, sourceFileName: realFile, targetFileName: file, settings: this.configManager.getSettings(config, realFile), }); this.processedFiles.set(file, { processor, originalContent: content, originalName: realFile, }); content = processor.preprocess(); return content; } writeFile() { } useCaseSensitiveFileNames() { return ts.sys.useCaseSensitiveFileNames; } getNewLine() { return this.compilerOptions.newLine === ts.NewLineKind.CarriageReturnLineFeed ? '\r\n' : '\n'; } safeRealpath(f) { if (this.realpath !== undefined) { try { return this.realpath(f); } catch { } } return f; } getCurrentDirectory() { return this.cwd; } getDirectories(dir) { const cached = this.directoryEntries.get(dir); if (cached !== undefined) return cached.directories.slice(); return utils_1.mapDefined(this.fs.readDirectory(dir), (entry) => entry.kind === 2 /* Directory */ ? entry.name : undefined); } getSourceFile(fileName, languageVersion) { return utils_1.resolveCachedResult(this.sourceFileCache, fileName, () => { const content = this.readProcessedFile(fileName); return content !== undefined ? ts.createSourceFile(fileName, content, languageVersion, true) : undefined; }); } createProgram(rootNames, options, oldProgram, projectReferences) { options = { ...options, suppressOutputPathCheck: true }; this.compilerOptions = options; this.moduleResolutionCache = ts.createModuleResolutionCache(this.cwd, this.getCanonicalFileName, options); return ts.createProgram({ rootNames, options, oldProgram, projectReferences, host: this }); } updateSourceFile(sourceFile) { this.sourceFileCache.set(sourceFile.fileName, sourceFile); } updateProgram(program) { return ts.createProgram({ rootNames: program.getRootFileNames(), options: program.getCompilerOptions(), oldProgram: program, projectReferences: program.getProjectReferences(), host: this, }); } onReleaseOldSourceFile(sourceFile) { // this is only called for paths that are no longer referenced // it's safe to remove the cache entry completely because it won't be called with updated SourceFiles this.uncacheFile(sourceFile.fileName); } uncacheFile(fileName) { this.sourceFileCache.delete(fileName); this.processedFiles.delete(fileName); } getParsedCommandLine(fileName) { return utils_1.resolveCachedResult(this.commandLineCache, fileName, this.parseConfigFile); } parseConfigFile(fileName) { // Note to future self: it's highly unlikely that a tsconfig of a project reference is used as base config for another tsconfig. // Therefore it doesn't make such sense to read or write the tsconfigCache here. const sourceFile = this.getSourceFile(fileName, ts.ScriptTarget.JSON); if (sourceFile === undefined) return; return ts.parseJsonSourceFileConfigFileContent(sourceFile, this.parseConfigHost, path.dirname(fileName), undefined, fileName, undefined, undefined, this.tsconfigCache); } resolveModuleNames(names, file, _, reference, options) { const seen = new Map(); const resolve = (name) => ts.resolveModuleName(name, file, options, this, this.moduleResolutionCache, reference).resolvedModule; return names.map((name) => utils_1.resolveCachedResult(seen, name, resolve)); } } tslib_1.__decorate([ bind_decorator_1.bind, tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String]), tslib_1.__metadata("design:returntype", Object) ], ProjectHost.prototype, "processDirectory", null); tslib_1.__decorate([ bind_decorator_1.bind, tslib_1.__metadata("design:type", Function), tslib_1.__metadata("design:paramtypes", [String]), tslib_1.__metadata("design:returntype", void 0) ], ProjectHost.prototype, "parseConfigFile", null); exports.ProjectHost = ProjectHost; //# sourceMappingURL=project-host.js.map