@fimbul/wotan
Version:
Pluggable TypeScript and JavaScript linter
248 lines • 10.6 kB
JavaScript
"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