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:
Nathan Shively-Sanders
2022-04-08 15:57:32 -07:00
parent bacc170adf
commit b178a3c7b2
14 changed files with 121 additions and 122 deletions
+1
View File
@@ -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;
+1
View File
@@ -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,
+42 -34
View File
@@ -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;
}
+1 -1
View File
@@ -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
View File
@@ -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);
}
+8 -2
View File
@@ -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,
+3 -3
View File
@@ -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
View File
@@ -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);
}
+4 -50
View File
@@ -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,
+1
View File
@@ -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,