mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Thread fshost through server
Project, editor services, session, etc all take an optional fshost and preferentially use it for filesystem operations.
This commit is contained in:
@@ -224,6 +224,7 @@ namespace fakes {
|
||||
private readonly _outputsMap: collections.SortedMap<string, number>;
|
||||
public readonly traces: string[] = [];
|
||||
public readonly shouldAssertInvariants = !Harness.lightMode;
|
||||
|
||||
private _setParentNodes: boolean;
|
||||
private _sourceFiles: collections.SortedMap<string, ts.SourceFile>;
|
||||
private _parseConfigHost: ParseConfigHost | undefined;
|
||||
|
||||
@@ -991,6 +991,7 @@ namespace Harness.LanguageService {
|
||||
const serverHost = new SessionServerHost(clientHost);
|
||||
const opts: ts.server.SessionOptions = {
|
||||
host: serverHost,
|
||||
fshost: undefined, // TODO: fshost tests will want to provide this for testing language service?
|
||||
cancellationToken: ts.server.nullCancellationToken,
|
||||
useSingleInferredProject: false,
|
||||
useInferredProjectPerProjectRoot: false,
|
||||
|
||||
@@ -395,6 +395,7 @@ namespace ts.server {
|
||||
|
||||
export interface ProjectServiceOptions {
|
||||
host: ServerHost;
|
||||
fshost: ServerHost | undefined;
|
||||
logger: Logger;
|
||||
cancellationToken: HostCancellationToken;
|
||||
useSingleInferredProject: boolean;
|
||||
@@ -758,6 +759,7 @@ namespace ts.server {
|
||||
readonly toCanonicalFileName: (f: string) => string;
|
||||
|
||||
public readonly host: ServerHost;
|
||||
public readonly fshost: ServerHost | undefined; // TODOO: Should be TestFSWithWatch.VirtualServerHost | undefined
|
||||
public readonly logger: Logger;
|
||||
public readonly cancellationToken: HostCancellationToken;
|
||||
public readonly useSingleInferredProject: boolean;
|
||||
@@ -804,6 +806,7 @@ namespace ts.server {
|
||||
|
||||
constructor(opts: ProjectServiceOptions) {
|
||||
this.host = opts.host;
|
||||
this.fshost = opts.fshost;
|
||||
this.logger = opts.logger;
|
||||
this.cancellationToken = opts.cancellationToken;
|
||||
this.useSingleInferredProject = opts.useSingleInferredProject;
|
||||
@@ -834,8 +837,9 @@ namespace ts.server {
|
||||
if (this.host.realpath) {
|
||||
this.realpathToScriptInfos = createMultiMap();
|
||||
}
|
||||
this.currentDirectory = toNormalizedPath(this.host.getCurrentDirectory());
|
||||
this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
|
||||
const fshost = this.fshost || this.host
|
||||
this.currentDirectory = toNormalizedPath(fshost.getCurrentDirectory());
|
||||
this.toCanonicalFileName = createGetCanonicalFileName(fshost.useCaseSensitiveFileNames);
|
||||
this.globalCacheLocationDirectoryPath = this.typingsInstaller.globalTypingsCacheLocation
|
||||
? ensureTrailingDirectorySeparator(this.toPath(this.typingsInstaller.globalTypingsCacheLocation))
|
||||
: undefined;
|
||||
@@ -859,7 +863,7 @@ namespace ts.server {
|
||||
extraFileExtensions: [],
|
||||
};
|
||||
|
||||
this.documentRegistry = createDocumentRegistryInternal(this.host.useCaseSensitiveFileNames, this.currentDirectory, this);
|
||||
this.documentRegistry = createDocumentRegistryInternal(fshost.useCaseSensitiveFileNames, this.currentDirectory, this);
|
||||
const watchLogLevel = this.logger.hasLevel(LogLevel.verbose) ? WatchLogLevel.Verbose :
|
||||
this.logger.loggingEnabled() ? WatchLogLevel.TriggerOnly : WatchLogLevel.None;
|
||||
const log: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => this.logger.info(s)) : noop;
|
||||
@@ -869,7 +873,7 @@ namespace ts.server {
|
||||
watchFile: returnNoopFileWatcher,
|
||||
watchDirectory: returnNoopFileWatcher,
|
||||
} :
|
||||
getWatchFactory(this.host, watchLogLevel, log, getDetailWatchInfo);
|
||||
getWatchFactory(fshost, watchLogLevel, log, getDetailWatchInfo);
|
||||
}
|
||||
|
||||
toPath(fileName: string) {
|
||||
@@ -878,12 +882,12 @@ namespace ts.server {
|
||||
|
||||
/*@internal*/
|
||||
getExecutingFilePath() {
|
||||
return this.getNormalizedAbsolutePath(this.host.getExecutingFilePath());
|
||||
return this.getNormalizedAbsolutePath((this.fshost || this.host).getExecutingFilePath());
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
getNormalizedAbsolutePath(fileName: string) {
|
||||
return getNormalizedAbsolutePath(fileName, this.host.getCurrentDirectory());
|
||||
return getNormalizedAbsolutePath(fileName, (this.fshost || this.host).getCurrentDirectory());
|
||||
}
|
||||
|
||||
/*@internal*/
|
||||
@@ -922,7 +926,7 @@ namespace ts.server {
|
||||
|
||||
private loadTypesMap() {
|
||||
try {
|
||||
const fileContent = this.host.readFile(this.typesMapLocation!); // TODO: GH#18217
|
||||
const fileContent = (this.fshost || this.host).readFile(this.typesMapLocation!); // TODO: GH#18217
|
||||
if (fileContent === undefined) {
|
||||
this.logger.info(`Provided types map file "${this.typesMapLocation}" doesn't exist`);
|
||||
return;
|
||||
@@ -1302,7 +1306,7 @@ namespace ts.server {
|
||||
const fileOrDirectoryPath = this.toPath(fileOrDirectory);
|
||||
const fsResult = config.cachedDirectoryStructureHost.addOrDeleteFileOrDirectory(fileOrDirectory, fileOrDirectoryPath);
|
||||
if (getBaseFileName(fileOrDirectoryPath) === "package.json" && !isInsideNodeModules(fileOrDirectoryPath) &&
|
||||
(fsResult && fsResult.fileExists || !fsResult && this.host.fileExists(fileOrDirectoryPath))
|
||||
(fsResult && fsResult.fileExists || !fsResult && (this.fshost || this.host).fileExists(fileOrDirectoryPath))
|
||||
) {
|
||||
this.logger.info(`Config: ${configFileName} Detected new package.json: ${fileOrDirectory}`);
|
||||
this.onAddPackageJson(fileOrDirectoryPath);
|
||||
@@ -1318,7 +1322,7 @@ namespace ts.server {
|
||||
currentDirectory: this.currentDirectory,
|
||||
options: config.parsedCommandLine!.options,
|
||||
program: configuredProjectForConfig?.getCurrentProgram() || config.parsedCommandLine!.fileNames,
|
||||
useCaseSensitiveFileNames: this.host.useCaseSensitiveFileNames,
|
||||
useCaseSensitiveFileNames: (this.fshost || this.host).useCaseSensitiveFileNames,
|
||||
writeLog: s => this.logger.info(s),
|
||||
toPath: s => this.toPath(s)
|
||||
})) return;
|
||||
@@ -1547,7 +1551,7 @@ namespace ts.server {
|
||||
// Closing file should trigger re-reading the file content from disk. This is
|
||||
// because the user may chose to discard the buffer content before saving
|
||||
// to the disk, and the server's version of the file can be out of sync.
|
||||
const fileExists = info.isDynamic ? false : this.host.fileExists(info.fileName);
|
||||
const fileExists = info.isDynamic ? false : (this.fshost || this.host).fileExists(info.fileName);
|
||||
info.close(fileExists);
|
||||
this.stopWatchingConfigFilesForClosedScriptInfo(info);
|
||||
|
||||
@@ -1649,7 +1653,7 @@ namespace ts.server {
|
||||
// Or the whole chain of config files for the roots of the inferred projects
|
||||
|
||||
// Cache the host value of file exists and add the info to map of open files impacted by this config file
|
||||
const exists = this.host.fileExists(configFileName);
|
||||
const exists = (this.fshost || this.host).fileExists(configFileName);
|
||||
let openFilesImpactedByConfigFile: ESMap<Path, boolean> | undefined;
|
||||
if (isOpenScriptInfo(info)) {
|
||||
(openFilesImpactedByConfigFile ||= new Map()).set(info.path, false);
|
||||
@@ -1779,7 +1783,7 @@ namespace ts.server {
|
||||
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
|
||||
if (!configFileExistenceInfo) {
|
||||
// Create the cache
|
||||
configFileExistenceInfo = { exists: this.host.fileExists(configFileName) };
|
||||
configFileExistenceInfo = { exists: (this.fshost || this.host).fileExists(configFileName) };
|
||||
this.configFileExistenceInfoCache.set(canonicalConfigFilePath, configFileExistenceInfo);
|
||||
}
|
||||
|
||||
@@ -1836,7 +1840,7 @@ namespace ts.server {
|
||||
if (scriptInfo.isDynamic) return undefined;
|
||||
|
||||
let searchPath = asNormalizedPath(getDirectoryPath(info.fileName));
|
||||
const isSearchPathInProjectRoot = () => containsPath(projectRootPath!, searchPath, this.currentDirectory, !this.host.useCaseSensitiveFileNames);
|
||||
const isSearchPathInProjectRoot = () => containsPath(projectRootPath!, searchPath, this.currentDirectory, !(this.fshost || this.host).useCaseSensitiveFileNames);
|
||||
|
||||
// If projectRootPath doesn't contain info.path, then do normal search for config file
|
||||
const anySearchPathOk = !projectRootPath || !isSearchPathInProjectRoot();
|
||||
@@ -1951,7 +1955,8 @@ namespace ts.server {
|
||||
|
||||
/** Get a filename if the language service exceeds the maximum allowed program size; otherwise returns undefined. */
|
||||
private getFilenameForExceededTotalSizeLimitForNonTsFiles<T>(name: string, options: CompilerOptions | undefined, fileNames: T[], propertyReader: FilePropertyReader<T>): string | undefined {
|
||||
if (options && options.disableSizeLimit || !this.host.getFileSize) {
|
||||
const fshost = this.fshost || this.host
|
||||
if (options && options.disableSizeLimit || !fshost.getFileSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1967,12 +1972,12 @@ namespace ts.server {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalNonTsFileSize += this.host.getFileSize(fileName);
|
||||
totalNonTsFileSize += fshost.getFileSize(fileName);
|
||||
|
||||
if (totalNonTsFileSize > maxProgramSizeForNonTsFiles || totalNonTsFileSize > availableSpace) {
|
||||
const top5LargestFiles = fileNames.map(f => propertyReader.getFileName(f))
|
||||
.filter(name => !hasTSFileExtension(name))
|
||||
.map(name => ({ name, size: this.host.getFileSize!(name) }))
|
||||
.map(name => ({ name, size: fshost.getFileSize!(name) }))
|
||||
.sort((a, b) => b.size - a.size)
|
||||
.slice(0, 5);
|
||||
this.logger.info(`Non TS file size exceeded limit (${totalNonTsFileSize}). Largest files: ${top5LargestFiles.map(file => `${file.name}:${file.size}`).join(", ")}`);
|
||||
@@ -2074,8 +2079,9 @@ namespace ts.server {
|
||||
configFileExistenceInfo.exists = true;
|
||||
}
|
||||
if (!configFileExistenceInfo.config) {
|
||||
const fshost = this.fshost || this.host;
|
||||
configFileExistenceInfo.config = {
|
||||
cachedDirectoryStructureHost: createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames)!,
|
||||
cachedDirectoryStructureHost: createCachedDirectoryStructureHost(fshost, fshost.getCurrentDirectory(), fshost.useCaseSensitiveFileNames)!,
|
||||
projects: new Map(),
|
||||
reloadLevel: ConfigFileProgramReloadLevel.Full
|
||||
};
|
||||
@@ -2175,11 +2181,12 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
// Parse the config file and ensure its cached
|
||||
const fshost = this.fshost || this.host;
|
||||
const cachedDirectoryStructureHost = configFileExistenceInfo.config?.cachedDirectoryStructureHost ||
|
||||
createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames)!;
|
||||
createCachedDirectoryStructureHost(fshost, fshost.getCurrentDirectory(), fshost.useCaseSensitiveFileNames)!;
|
||||
|
||||
// Read updated contents from disk
|
||||
const configFileContent = tryReadFile(configFilename, fileName => this.host.readFile(fileName));
|
||||
const configFileContent = tryReadFile(configFilename, fileName => fshost.readFile(fileName));
|
||||
const configFile = parseJsonText(configFilename, isString(configFileContent) ? configFileContent : "") as TsConfigSourceFile;
|
||||
const configFileErrors = configFile.parseDiagnostics as Diagnostic[];
|
||||
if (!isString(configFileContent)) configFileErrors.push(configFileContent);
|
||||
@@ -2474,11 +2481,12 @@ namespace ts.server {
|
||||
// we don't have an explicit root path, so we should try to find an inferred project
|
||||
// that more closely contains the file.
|
||||
let bestMatch: InferredProject | undefined;
|
||||
const fshost = this.fshost || this.host;
|
||||
for (const project of this.inferredProjects) {
|
||||
// ignore single inferred projects (handled elsewhere)
|
||||
if (!project.projectRootPath) continue;
|
||||
// ignore inferred projects that don't contain the root's path
|
||||
if (!containsPath(project.projectRootPath, info.path, this.host.getCurrentDirectory(), !this.host.useCaseSensitiveFileNames)) continue;
|
||||
if (!containsPath(project.projectRootPath, info.path, fshost.getCurrentDirectory(), !fshost.useCaseSensitiveFileNames)) continue;
|
||||
// ignore inferred projects that are higher up in the project root.
|
||||
// TODO(rbuckton): Should we add the file as a root to these as well?
|
||||
if (bestMatch && bestMatch.projectRootPath!.length > project.projectRootPath.length) continue;
|
||||
@@ -2623,7 +2631,7 @@ namespace ts.server {
|
||||
(!this.globalCacheLocationDirectoryPath ||
|
||||
!startsWith(info.path, this.globalCacheLocationDirectoryPath))) {
|
||||
const indexOfNodeModules = info.path.indexOf("/node_modules/");
|
||||
if (!this.host.getModifiedTime || indexOfNodeModules === -1) {
|
||||
if (!(this.fshost || this.host).getModifiedTime || indexOfNodeModules === -1) {
|
||||
info.fileWatcher = this.watchFactory.watchFile(
|
||||
info.fileName,
|
||||
(_fileName, eventKind) => this.onSourceFileChanged(info, eventKind),
|
||||
@@ -2721,7 +2729,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
private getModifiedTime(info: ScriptInfo) {
|
||||
return (this.host.getModifiedTime!(info.path) || missingFileModifiedTime).getTime();
|
||||
return ((this.fshost || this.host).getModifiedTime!(info.path) || missingFileModifiedTime).getTime();
|
||||
}
|
||||
|
||||
private refreshScriptInfo(info: ScriptInfo) {
|
||||
@@ -2784,10 +2792,10 @@ namespace ts.server {
|
||||
Debug.assert(!isRootedDiskPath(fileName) || this.currentDirectory === currentDirectory || !this.openFilesWithNonRootedDiskPath.has(this.toCanonicalFileName(fileName)), "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nOpen script files with non rooted disk path opened with current directory context cannot have same canonical names`);
|
||||
Debug.assert(!isDynamic || this.currentDirectory === currentDirectory || this.useInferredProjectPerProjectRoot, "", () => `${JSON.stringify({ fileName, currentDirectory, hostCurrentDirectory: this.currentDirectory, openKeys: arrayFrom(this.openFilesWithNonRootedDiskPath.keys()) })}\nDynamic files must always be opened with service's current directory or service should support inferred project per projectRootPath.`);
|
||||
// If the file is not opened by client and the file doesnot exist on the disk, return
|
||||
if (!openedByClient && !isDynamic && !(hostToQueryFileExistsOn || this.host).fileExists(fileName)) {
|
||||
if (!openedByClient && !isDynamic && !(hostToQueryFileExistsOn || this.fshost || this.host).fileExists(fileName)) {
|
||||
return;
|
||||
}
|
||||
info = new ScriptInfo(this.host, fileName, scriptKind!, !!hasMixedContent, path, this.filenameToScriptInfoVersion.get(path)); // TODO: GH#18217
|
||||
info = new ScriptInfo(this.fshost || this.host, fileName, scriptKind!, !!hasMixedContent, path, this.filenameToScriptInfoVersion.get(path)); // TODO: GH#18217
|
||||
this.filenameToScriptInfo.set(info.path, info);
|
||||
this.filenameToScriptInfoVersion.delete(info.path);
|
||||
if (!openedByClient) {
|
||||
@@ -2825,7 +2833,7 @@ namespace ts.server {
|
||||
/*@internal*/
|
||||
getDocumentPositionMapper(project: Project, generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined {
|
||||
// Since declaration info and map file watches arent updating project's directory structure host (which can cache file structure) use host
|
||||
const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.host);
|
||||
const declarationInfo = this.getOrCreateScriptInfoNotOpenedByClient(generatedFileName, project.currentDirectory, this.fshost || this.host);
|
||||
if (!declarationInfo) {
|
||||
if (sourceFileName) {
|
||||
// Project contains source file and it generates the generated file name
|
||||
@@ -2862,7 +2870,7 @@ namespace ts.server {
|
||||
let mapFileNameFromDeclarationInfo: string | undefined;
|
||||
|
||||
let readMapFile: ReadMapFile | undefined = (mapFileName, mapFileNameFromDts) => {
|
||||
const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, this.host);
|
||||
const mapInfo = this.getOrCreateScriptInfoNotOpenedByClient(mapFileName, project.currentDirectory, this.fshost || this.host);
|
||||
if (!mapInfo) {
|
||||
mapFileNameFromDeclarationInfo = mapFileNameFromDts;
|
||||
return undefined;
|
||||
@@ -2941,7 +2949,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
// Need to look for other files.
|
||||
const info = this.getOrCreateScriptInfoNotOpenedByClient(fileName, (project || this).currentDirectory, project ? project.directoryStructureHost : this.host);
|
||||
const info = this.getOrCreateScriptInfoNotOpenedByClient(fileName, (project || this).currentDirectory, project ? project.directoryStructureHost : (this.fshost || this.host));
|
||||
if (!info) return undefined;
|
||||
|
||||
// Attach as source
|
||||
@@ -3062,7 +3070,7 @@ namespace ts.server {
|
||||
if (this.openFiles.has(info.path)) return; // Skip open files
|
||||
if (!info.fileWatcher) return; // not watched file
|
||||
// Handle as if file is changed or deleted
|
||||
this.onSourceFileChanged(info, this.host.fileExists(info.fileName) ? FileWatcherEventKind.Changed : FileWatcherEventKind.Deleted);
|
||||
this.onSourceFileChanged(info, (this.fshost || this.host).fileExists(info.fileName) ? FileWatcherEventKind.Changed : FileWatcherEventKind.Deleted);
|
||||
});
|
||||
// Cancel all project updates since we will be updating them now
|
||||
this.pendingProjectUpdates.forEach((_project, projectName) => {
|
||||
@@ -3235,7 +3243,7 @@ namespace ts.server {
|
||||
|
||||
const { fileName } = originalLocation;
|
||||
const scriptInfo = this.getScriptInfo(fileName);
|
||||
if (!scriptInfo && !this.host.fileExists(fileName)) return undefined;
|
||||
if (!scriptInfo && !(this.fshost || this.host).fileExists(fileName)) return undefined;
|
||||
|
||||
const originalFileInfo: OriginalFileInfo = { fileName: toNormalizedPath(fileName), path: this.toPath(fileName) };
|
||||
const configFileName = this.getConfigFileNameForFile(originalFileInfo);
|
||||
@@ -3307,7 +3315,7 @@ namespace ts.server {
|
||||
|
||||
/** @internal */
|
||||
fileExists(fileName: NormalizedPath): boolean {
|
||||
return !!this.getScriptInfoForNormalizedPath(fileName) || this.host.fileExists(fileName);
|
||||
return !!this.getScriptInfoForNormalizedPath(fileName) || (this.fshost || this.host).fileExists(fileName);
|
||||
}
|
||||
|
||||
private findExternalProjectContainingOpenScriptInfo(info: ScriptInfo): ExternalProject | undefined {
|
||||
@@ -3689,8 +3697,8 @@ namespace ts.server {
|
||||
|
||||
/* @internal */
|
||||
updateFileSystem(createdFiles: Iterator<protocol.FileSystemRequestArgs> | undefined, updatedFiles?: Iterator<protocol.FileSystemRequestArgs>, deletedFiles?: string[]) {
|
||||
// TODO: Not sure it's OK to use protocol.FileSystemRequestArgs here
|
||||
const fs = this.host as TestFSWithWatch.VirtualServerHost;
|
||||
Debug.assert(this.fshost);
|
||||
const fs = this.fshost as TestFSWithWatch.VirtualServerHost;
|
||||
if (createdFiles) {
|
||||
let it;
|
||||
while (!(it = createdFiles.next()).done) {
|
||||
@@ -3987,7 +3995,7 @@ namespace ts.server {
|
||||
for (const file of proj.rootFiles) {
|
||||
const normalized = toNormalizedPath(file.fileName);
|
||||
if (getBaseConfigFileName(normalized)) {
|
||||
if (this.serverMode === LanguageServiceMode.Semantic && this.host.fileExists(normalized)) {
|
||||
if (this.serverMode === LanguageServiceMode.Semantic && (this.fshost || this.host).fileExists(normalized)) {
|
||||
(tsConfigFiles || (tsConfigFiles = [])).push(normalized);
|
||||
}
|
||||
}
|
||||
@@ -4142,7 +4150,7 @@ namespace ts.server {
|
||||
case Ternary.True: return directory;
|
||||
case Ternary.False: return undefined;
|
||||
case Ternary.Maybe:
|
||||
return this.host.fileExists(combinePaths(directory, "package.json"))
|
||||
return (this.fshost || this.host).fileExists(combinePaths(directory, "package.json"))
|
||||
? directory
|
||||
: undefined;
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace ts.server {
|
||||
};
|
||||
|
||||
function addOrUpdate(fileName: Path) {
|
||||
const packageJsonInfo = Debug.checkDefined(createPackageJsonInfo(fileName, host.host));
|
||||
const packageJsonInfo = Debug.checkDefined(createPackageJsonInfo(fileName, (host.fshost || host.host)));
|
||||
packageJsons.set(fileName, packageJsonInfo);
|
||||
directoriesWithoutPackageJson.delete(getDirectoryPath(fileName));
|
||||
}
|
||||
|
||||
+19
-16
@@ -229,8 +229,8 @@ namespace ts.server {
|
||||
return hasOneOrMoreJsAndNoTsFiles(this);
|
||||
}
|
||||
|
||||
public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): {} | undefined {
|
||||
const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules")));
|
||||
public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, fshost: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): {} | undefined {
|
||||
const resolvedPath = normalizeSlashes(fshost.resolvePath(combinePaths(initialDir, "node_modules")));
|
||||
log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
|
||||
const result = host.require!(resolvedPath, moduleName); // TODO: GH#18217
|
||||
if (result.error) {
|
||||
@@ -312,13 +312,14 @@ namespace ts.server {
|
||||
|
||||
this.setInternalCompilerOptionsForEmittingJsFiles();
|
||||
const host = this.projectService.host;
|
||||
const fshost = this.projectService.fshost || this.projectService.host;
|
||||
if (this.projectService.logger.loggingEnabled()) {
|
||||
this.trace = s => this.writeLog(s);
|
||||
}
|
||||
else if (host.trace) {
|
||||
this.trace = s => host.trace!(s);
|
||||
}
|
||||
this.realpath = maybeBind(host, host.realpath);
|
||||
this.realpath = maybeBind(fshost, fshost.realpath);
|
||||
|
||||
// Use the current directory as resolution root only if the project created using current directory string
|
||||
this.resolutionCache = createResolutionCache(
|
||||
@@ -450,19 +451,19 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
useCaseSensitiveFileNames() {
|
||||
return this.projectService.host.useCaseSensitiveFileNames;
|
||||
return (this.projectService.fshost || this.projectService.host).useCaseSensitiveFileNames;
|
||||
}
|
||||
|
||||
readDirectory(path: string, extensions?: readonly string[], exclude?: readonly string[], include?: readonly string[], depth?: number): string[] {
|
||||
return this.directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth);
|
||||
return this.directoryStructureHost.readDirectory!(path, extensions, exclude, include, depth)
|
||||
}
|
||||
|
||||
readFile(fileName: string): string | undefined {
|
||||
return this.projectService.host.readFile(fileName);
|
||||
return (this.projectService.fshost || this.projectService.host).readFile(fileName);
|
||||
}
|
||||
|
||||
writeFile(fileName: string, content: string): void {
|
||||
return this.projectService.host.writeFile(fileName, content);
|
||||
return (this.projectService.fshost || this.projectService.host).writeFile(fileName, content);
|
||||
}
|
||||
|
||||
fileExists(file: string): boolean {
|
||||
@@ -1598,7 +1599,7 @@ namespace ts.server {
|
||||
(errorLogs || (errorLogs = [])).push(message);
|
||||
};
|
||||
const resolvedModule = firstDefined(searchPaths, searchPath =>
|
||||
Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined);
|
||||
Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, this.projectService.fshost || this.projectService.host, log, logError) as PluginModuleFactory | undefined);
|
||||
if (resolvedModule) {
|
||||
const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name);
|
||||
if (configurationOverride) {
|
||||
@@ -1622,13 +1623,13 @@ namespace ts.server {
|
||||
this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did not expose a proper factory function`);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: serverHost very likely needs to be fshost, or an object that defaults to fshost
|
||||
const info: PluginCreateInfo = {
|
||||
config: configEntry,
|
||||
project: this,
|
||||
languageService: this.languageService,
|
||||
languageServiceHost: this,
|
||||
serverHost: this.projectService.host,
|
||||
serverHost: this.projectService.fshost || this.projectService.host,
|
||||
session: this.projectService.session
|
||||
};
|
||||
|
||||
@@ -1716,17 +1717,19 @@ namespace ts.server {
|
||||
/*@internal*/
|
||||
getModuleResolutionHostForAutoImportProvider(): ModuleResolutionHost {
|
||||
if (this.program) {
|
||||
const fshost = this.projectService.fshost || this.projectService.host;
|
||||
return {
|
||||
fileExists: this.program.fileExists,
|
||||
directoryExists: this.program.directoryExists,
|
||||
realpath: this.program.realpath || this.projectService.host.realpath?.bind(this.projectService.host),
|
||||
realpath: this.program.realpath || fshost.realpath?.bind(fshost),
|
||||
getCurrentDirectory: this.getCurrentDirectory.bind(this),
|
||||
readFile: this.projectService.host.readFile.bind(this.projectService.host),
|
||||
getDirectories: this.projectService.host.getDirectories.bind(this.projectService.host),
|
||||
readFile: fshost.readFile.bind(fshost),
|
||||
getDirectories: fshost.getDirectories.bind(fshost),
|
||||
trace: this.projectService.host.trace?.bind(this.projectService.host),
|
||||
useCaseSensitiveFileNames: this.program.useCaseSensitiveFileNames(),
|
||||
};
|
||||
}
|
||||
// TODO: Not sure whether this needs to be fshost or host or an object that can fall back fshost->host
|
||||
return this.projectService.host;
|
||||
}
|
||||
|
||||
@@ -1864,7 +1867,7 @@ namespace ts.server {
|
||||
compilerOptions,
|
||||
/*compileOnSaveEnabled*/ false,
|
||||
watchOptions,
|
||||
projectService.host,
|
||||
projectService.fshost || projectService.host,
|
||||
currentDirectory);
|
||||
this.typeAcquisition = typeAcquisition;
|
||||
this.projectRootPath = projectRootPath && projectService.toCanonicalFileName(projectRootPath);
|
||||
@@ -2092,7 +2095,7 @@ namespace ts.server {
|
||||
compilerOptions,
|
||||
/*compileOnSaveEnabled*/ false,
|
||||
hostProject.getWatchOptions(),
|
||||
hostProject.projectService.host,
|
||||
hostProject.projectService.fshost || hostProject.projectService.host,
|
||||
hostProject.currentDirectory);
|
||||
|
||||
this.rootFileNames = initialRootNames;
|
||||
@@ -2517,7 +2520,7 @@ namespace ts.server {
|
||||
compilerOptions,
|
||||
compileOnSaveEnabled,
|
||||
watchOptions,
|
||||
projectService.host,
|
||||
projectService.fshost || projectService.host,
|
||||
getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
|
||||
this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);
|
||||
}
|
||||
|
||||
@@ -641,6 +641,7 @@ namespace ts.server {
|
||||
|
||||
export interface SessionOptions {
|
||||
host: ServerHost;
|
||||
fshost: ServerHost | undefined;
|
||||
cancellationToken: ServerCancellationToken;
|
||||
useSingleInferredProject: boolean;
|
||||
useInferredProjectPerProjectRoot: boolean;
|
||||
@@ -688,9 +689,12 @@ namespace ts.server {
|
||||
private suppressDiagnosticEvents?: boolean;
|
||||
private eventHandler: ProjectServiceEventHandler | undefined;
|
||||
private readonly noGetErrOnBackgroundUpdate?: boolean;
|
||||
// TODO: Also need to do this in all of project too
|
||||
private fshost: ServerHost | undefined
|
||||
|
||||
constructor(opts: SessionOptions) {
|
||||
this.host = opts.host;
|
||||
this.fshost = opts.fshost;
|
||||
this.cancellationToken = opts.cancellationToken;
|
||||
this.typingsInstaller = opts.typingsInstaller;
|
||||
this.byteLength = opts.byteLength;
|
||||
@@ -716,6 +720,7 @@ namespace ts.server {
|
||||
this.errorCheck = new MultistepOperation(multistepOperationHost);
|
||||
const settings: ProjectServiceOptions = {
|
||||
host: this.host,
|
||||
fshost: this.fshost,
|
||||
logger: this.logger,
|
||||
cancellationToken: this.cancellationToken,
|
||||
useSingleInferredProject: opts.useSingleInferredProject,
|
||||
@@ -1954,7 +1959,8 @@ namespace ts.server {
|
||||
return args.richResponse ? { emitSkipped: true, diagnostics: [] } : false;
|
||||
}
|
||||
const scriptInfo = project.getScriptInfo(file)!;
|
||||
const { emitSkipped, diagnostics } = project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => this.host.writeFile(path, data, writeByteOrderMark));
|
||||
const h = this.fshost || this.host;
|
||||
const { emitSkipped, diagnostics } = project.emitFile(scriptInfo, (path, data, writeByteOrderMark) => h.writeFile(path, data, writeByteOrderMark));
|
||||
return args.richResponse ?
|
||||
{
|
||||
emitSkipped,
|
||||
@@ -2653,7 +2659,7 @@ namespace ts.server {
|
||||
}
|
||||
|
||||
getCanonicalFileName(fileName: string) {
|
||||
const name = this.host.useCaseSensitiveFileNames ? fileName : toFileNameLowerCase(fileName);
|
||||
const name = (this.fshost || this.host).useCaseSensitiveFileNames ? fileName : toFileNameLowerCase(fileName);
|
||||
return normalizePath(name);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ ${file.fileContent}`;
|
||||
it("with updateFileSystem request", () => {
|
||||
// TODO: probably some other watcher tests, not sure what
|
||||
const host = TestFSWithWatch.createVirtualServerHost([], { executingFilePath: "/a/tsc.js" });
|
||||
const session = createSession(host);
|
||||
const session = createVirtualFilesystemSession(host, host);
|
||||
const created = [app, file1, file2, file3, config, lib];
|
||||
session.executeCommandSeq<protocol.UpdateFileSystemRequest>({
|
||||
command: protocol.CommandTypes.UpdateFileSystem,
|
||||
|
||||
@@ -366,6 +366,7 @@ namespace ts.projectSystem {
|
||||
|
||||
const sessionOptions: TestSessionOptions = {
|
||||
host,
|
||||
fshost: undefined,
|
||||
cancellationToken: server.nullCancellationToken,
|
||||
useSingleInferredProject: false,
|
||||
useInferredProjectPerProjectRoot: false,
|
||||
@@ -413,6 +414,31 @@ namespace ts.projectSystem {
|
||||
}
|
||||
}
|
||||
|
||||
export function createVirtualFilesystemSession(host: server.ServerHost, fshost: TestFSWithWatch.VirtualServerHost | undefined, opts: Partial<TestSessionOptions> = {}) {
|
||||
if (opts.typingsInstaller === undefined) {
|
||||
opts.typingsInstaller = new TestTypingsInstaller("/a/data/", /*throttleLimit*/ 5, host);
|
||||
}
|
||||
|
||||
if (opts.eventHandler !== undefined) {
|
||||
opts.canUseEvents = true;
|
||||
}
|
||||
|
||||
const sessionOptions: TestSessionOptions = {
|
||||
host,
|
||||
fshost,
|
||||
cancellationToken: server.nullCancellationToken,
|
||||
useSingleInferredProject: false,
|
||||
useInferredProjectPerProjectRoot: false,
|
||||
typingsInstaller: undefined!, // TODO: GH#18217
|
||||
byteLength: Utils.byteLength,
|
||||
hrtime: process.hrtime,
|
||||
logger: opts.logger || createHasErrorMessageLogger(),
|
||||
canUseEvents: false
|
||||
};
|
||||
|
||||
return new TestSession({ ...sessionOptions, ...opts });
|
||||
}
|
||||
|
||||
export interface TestProjectServiceOptions extends server.ProjectServiceOptions {
|
||||
logger: Logger;
|
||||
}
|
||||
@@ -422,6 +448,7 @@ namespace ts.projectSystem {
|
||||
typingsInstaller: server.ITypingsInstaller, opts: Partial<TestProjectServiceOptions> = {}) {
|
||||
super({
|
||||
host,
|
||||
fshost: undefined, // TODO: should provide this for fshost tests
|
||||
logger,
|
||||
session: undefined,
|
||||
cancellationToken,
|
||||
|
||||
@@ -42,6 +42,7 @@ namespace ts.server {
|
||||
function createSession(): TestSession {
|
||||
const opts: SessionOptions = {
|
||||
host: mockHost,
|
||||
fshost: undefined,
|
||||
cancellationToken: nullCancellationToken,
|
||||
useSingleInferredProject: false,
|
||||
useInferredProjectPerProjectRoot: false,
|
||||
@@ -465,6 +466,7 @@ namespace ts.server {
|
||||
constructor() {
|
||||
super({
|
||||
host: mockHost,
|
||||
fshost: undefined,
|
||||
cancellationToken: nullCancellationToken,
|
||||
useSingleInferredProject: false,
|
||||
useInferredProjectPerProjectRoot: false,
|
||||
@@ -512,6 +514,7 @@ namespace ts.server {
|
||||
constructor() {
|
||||
super({
|
||||
host: mockHost,
|
||||
fshost: undefined,
|
||||
cancellationToken: nullCancellationToken,
|
||||
useSingleInferredProject: false,
|
||||
useInferredProjectPerProjectRoot: false,
|
||||
@@ -580,6 +583,7 @@ namespace ts.server {
|
||||
constructor(private client: InProcClient) {
|
||||
super({
|
||||
host: mockHost,
|
||||
fshost: undefined,
|
||||
cancellationToken: nullCancellationToken,
|
||||
useSingleInferredProject: false,
|
||||
useInferredProjectPerProjectRoot: false,
|
||||
|
||||
@@ -383,7 +383,7 @@ namespace ts.server {
|
||||
return eventPort !== undefined && !isNaN(eventPort) ? eventPort : undefined;
|
||||
}
|
||||
|
||||
function startNodeSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) {
|
||||
function startNodeSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken, fs: ServerHost) {
|
||||
const childProcess: {
|
||||
fork(modulePath: string, args: string[], options?: { execArgv: string[], env?: MapLike<string> }): NodeChildProcess;
|
||||
} = require("child_process");
|
||||
@@ -680,7 +680,7 @@ namespace ts.server {
|
||||
this.event(body, eventName);
|
||||
};
|
||||
|
||||
const host = sys as ServerHost;
|
||||
const host = fs // sys as ServerHost;
|
||||
|
||||
const typingsInstaller = disableAutomaticTypingAcquisition
|
||||
? undefined
|
||||
@@ -788,7 +788,7 @@ namespace ts.server {
|
||||
|
||||
const eventPort: number | undefined = parseEventPort(findArgument("--eventPort"));
|
||||
const typingSafeListLocation = findArgument(Arguments.TypingSafeListLocation)!; // TODO: GH#18217
|
||||
const typesMapLocation = findArgument(Arguments.TypesMapLocation) || combinePaths(getDirectoryPath(sys.getExecutingFilePath()), "typesMap.json");
|
||||
const typesMapLocation = findArgument(Arguments.TypesMapLocation) || combinePaths(getDirectoryPath(fs.getExecutingFilePath()), "typesMap.json");
|
||||
const npmLocation = findArgument(Arguments.NpmLocation);
|
||||
const validateDefaultNpmLocation = hasArgument(Arguments.ValidateDefaultNpmLocation);
|
||||
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
|
||||
|
||||
+8
-15
@@ -28,9 +28,9 @@ namespace ts.server {
|
||||
cancellationToken: ServerCancellationToken;
|
||||
serverMode: LanguageServiceMode | undefined;
|
||||
unknownServerMode?: string;
|
||||
startSession: (option: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) => void;
|
||||
startSession: (option: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken, fs: ServerHost) => void;
|
||||
}
|
||||
function start({ args, logger, cancellationToken, serverMode, unknownServerMode, startSession: startServer }: StartInput, platform: string) {
|
||||
function start({ args, logger, cancellationToken, serverMode, unknownServerMode, startSession: startServer }: StartInput, platform: string, fs: ServerHost) {
|
||||
const syntaxOnly = hasArgument("--syntaxOnly");
|
||||
|
||||
logger.info(`Starting TS Server`);
|
||||
@@ -70,31 +70,24 @@ namespace ts.server {
|
||||
serverMode
|
||||
},
|
||||
logger,
|
||||
cancellationToken
|
||||
cancellationToken,
|
||||
fs
|
||||
);
|
||||
}
|
||||
|
||||
setStackTraceLimit();
|
||||
// TODO: Not sure this is right
|
||||
const fs = findArgument("vfs") ? TestFSWithWatch.createVirtualServerHost([]) : ts.sys as ServerHost
|
||||
// Cannot check process var directory in webworker so has to be typeof check here
|
||||
if (typeof process !== "undefined") {
|
||||
start(initializeNodeSystem(), require("os").platform());
|
||||
}
|
||||
// TODO: Not sure this is right
|
||||
else if (findArgument("vfs")) {
|
||||
// Get args from first message
|
||||
const listener = (e: any) => {
|
||||
removeEventListener("message", listener);
|
||||
const args = e.data;
|
||||
start(initializeVirtualFileSystem(args), "vfs");
|
||||
};
|
||||
addEventListener("message", listener);
|
||||
start(initializeNodeSystem(), require("os").platform(), fs);
|
||||
}
|
||||
else {
|
||||
// Get args from first message
|
||||
const listener = (e: any) => {
|
||||
removeEventListener("message", listener);
|
||||
const args = e.data;
|
||||
start(initializeWebSystem(args), "web");
|
||||
start(initializeWebSystem(args), "web", fs);
|
||||
};
|
||||
addEventListener("message", listener);
|
||||
}
|
||||
|
||||
@@ -46,18 +46,6 @@ namespace ts.server {
|
||||
};
|
||||
}
|
||||
|
||||
export function initializeVirtualFileSystem(args: string[]): StartInput {
|
||||
createVirtualFileSystem();
|
||||
return {
|
||||
args,
|
||||
logger: createLogger(),
|
||||
cancellationToken: nullCancellationToken,
|
||||
// Only semantic mode right now
|
||||
serverMode: LanguageServiceMode.Semantic,
|
||||
startSession: startVirtualFileSystemSession
|
||||
};
|
||||
}
|
||||
|
||||
function createLogger() {
|
||||
const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity"));
|
||||
return cmdLineVerbosity !== undefined ? new MainProcessLogger(cmdLineVerbosity, { writeMessage }) : nullLogger;
|
||||
@@ -93,19 +81,6 @@ namespace ts.server {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createVirtualFileSystem() {
|
||||
Debug.assert(sys === undefined);
|
||||
// TODO: I don't think I need the XMLHttpRequest code since vfs will get its files from messages sent by the owner
|
||||
// ...but this means I may not need the webSession-copied code in startVirtualFileSystemSession
|
||||
const vfshost = TestFSWithWatch.createVirtualServerHost([]);
|
||||
setSys(vfshost);
|
||||
const localeStr = findArgument("--locale");
|
||||
if (localeStr) {
|
||||
validateLocaleAndSetLanguage(localeStr, sys);
|
||||
}
|
||||
}
|
||||
|
||||
function hrtime(previous?: [number, number]) {
|
||||
const now = self.performance.now() * 1e-3;
|
||||
let seconds = Math.floor(now);
|
||||
@@ -121,9 +96,12 @@ namespace ts.server {
|
||||
return [seconds, nanoseconds];
|
||||
}
|
||||
|
||||
function startWebSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) {
|
||||
// TODO: Maybe should leave as ServerHost cast below and declare fs: System
|
||||
// TODO: host is probably still a better name
|
||||
function startWebSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken, fs: ServerHost) {
|
||||
class WorkerSession extends server.WorkerSession {
|
||||
constructor() {
|
||||
// TODO: Not really sure this is the way to do it
|
||||
super(sys as ServerHost, { writeMessage }, options, logger, cancellationToken, hrtime);
|
||||
}
|
||||
|
||||
@@ -145,28 +123,4 @@ namespace ts.server {
|
||||
// Start listening
|
||||
session.listen();
|
||||
}
|
||||
|
||||
function startVirtualFileSystemSession(options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken) {
|
||||
class WorkerSession extends server.WorkerSession {
|
||||
constructor() {
|
||||
super(sys as ServerHost, { writeMessage }, options, logger, cancellationToken, hrtime);
|
||||
}
|
||||
|
||||
exit() {
|
||||
this.logger.info("Exiting...");
|
||||
this.projectService.closeLog();
|
||||
close();
|
||||
}
|
||||
|
||||
listen() {
|
||||
addEventListener("message", (message: any) => {
|
||||
this.onMessage(message.data);
|
||||
});
|
||||
}
|
||||
}
|
||||
const session = new WorkerSession();
|
||||
|
||||
// Start listening
|
||||
session.listen();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ namespace ts.server.typingsInstaller {
|
||||
private delayedInitializationError: InitializationFailedResponse | undefined;
|
||||
|
||||
constructor(globalTypingsCacheLocation: string, typingSafeListLocation: string, typesMapLocation: string, npmLocation: string | undefined, validateDefaultNpmLocation: boolean, throttleLimit: number, log: Log) {
|
||||
// TODO: This just uses sys, I'm not sure how to circumvent that
|
||||
super(
|
||||
sys,
|
||||
globalTypingsCacheLocation,
|
||||
|
||||
@@ -181,6 +181,7 @@ namespace ts.server {
|
||||
constructor(host: ServerHost, private webHost: HostWithWriteMessage, options: StartSessionOptions, logger: Logger, cancellationToken: ServerCancellationToken, hrtime: SessionOptions["hrtime"]) {
|
||||
super({
|
||||
host,
|
||||
fshost: undefined, // TODO: Provide this? Check Sheetal's comments.
|
||||
cancellationToken,
|
||||
...options,
|
||||
typingsInstaller: nullTypingsInstaller,
|
||||
|
||||
Reference in New Issue
Block a user