Add separate flag serverMode for server mode (#39735)

* Add separate flag serverMode for server mode to allow back compatibility

* Addressed code review feedback.

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
This commit is contained in:
Sheetal Nandi
2020-08-02 18:18:26 -07:00
committed by GitHub
parent 86a87c493c
commit aee78acedb
12 changed files with 470 additions and 104 deletions

View File

@@ -396,7 +396,9 @@ namespace ts.server {
pluginProbeLocations?: readonly string[];
allowLocalPluginLoads?: boolean;
typesMapLocation?: string;
/** @deprecated use serverMode instead */
syntaxOnly?: boolean;
serverMode?: LanguageServiceMode;
}
interface OriginalFileInfo { fileName: NormalizedPath; path: Path; }
@@ -683,7 +685,9 @@ namespace ts.server {
public readonly typesMapLocation: string | undefined;
public readonly syntaxOnly?: boolean;
/** @deprecated use serverMode instead */
public readonly syntaxOnly: boolean;
public readonly serverMode: LanguageServiceMode;
/** Tracks projects that we have already sent telemetry for. */
private readonly seenProjects = new Map<string, true>();
@@ -713,7 +717,18 @@ namespace ts.server {
this.pluginProbeLocations = opts.pluginProbeLocations || emptyArray;
this.allowLocalPluginLoads = !!opts.allowLocalPluginLoads;
this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(getDirectoryPath(this.getExecutingFilePath()), "typesMap.json") : opts.typesMapLocation;
this.syntaxOnly = opts.syntaxOnly;
if (opts.serverMode !== undefined) {
this.serverMode = opts.serverMode;
this.syntaxOnly = this.serverMode === LanguageServiceMode.SyntaxOnly;
}
else if (opts.syntaxOnly) {
this.serverMode = LanguageServiceMode.SyntaxOnly;
this.syntaxOnly = true;
}
else {
this.serverMode = LanguageServiceMode.Semantic;
this.syntaxOnly = false;
}
Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService");
if (this.host.realpath) {
@@ -749,7 +764,7 @@ namespace ts.server {
this.logger.loggingEnabled() ? WatchLogLevel.TriggerOnly : WatchLogLevel.None;
const log: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => this.logger.info(s)) : noop;
this.packageJsonCache = createPackageJsonCache(this);
this.watchFactory = this.syntaxOnly ?
this.watchFactory = this.serverMode !== LanguageServiceMode.Semantic ?
{
watchFile: returnNoopFileWatcher,
watchFilePath: returnNoopFileWatcher,
@@ -1727,7 +1742,7 @@ namespace ts.server {
* the newly opened file.
*/
private forEachConfigFileLocation(info: OpenScriptInfoOrClosedOrConfigFileInfo, action: (configFileName: NormalizedPath, canonicalConfigFilePath: string) => boolean | void) {
if (this.syntaxOnly) {
if (this.serverMode !== LanguageServiceMode.Semantic) {
return undefined;
}
@@ -3014,7 +3029,7 @@ namespace ts.server {
let retainProjects: ConfiguredProject[] | ConfiguredProject | undefined;
let projectForConfigFileDiag: ConfiguredProject | undefined;
let defaultConfigProjectIsCreated = false;
if (this.syntaxOnly) {
if (this.serverMode === LanguageServiceMode.ApproximateSemanticOnly) {
// Invalidate resolutions in the file since this file is now open
info.containingProjects.forEach(project => {
if (project.resolutionCache.removeRelativeNoResolveResolutionsOfFile(info.path)) {
@@ -3022,7 +3037,7 @@ namespace ts.server {
}
});
}
else if (!project) { // Checking syntaxOnly is an optimization
else if (!project && this.serverMode === LanguageServiceMode.Semantic) { // Checking semantic mode is an optimization
configFileName = this.getConfigFileNameForFile(info);
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
@@ -3109,7 +3124,7 @@ namespace ts.server {
Debug.assert(this.openFiles.has(info.path));
this.assignOrphanScriptInfoToInferredProject(info, this.openFiles.get(info.path));
}
else if (this.syntaxOnly && info.cacheSourceFile?.sourceFile.referencedFiles.length) {
else if (this.serverMode === LanguageServiceMode.ApproximateSemanticOnly && info.cacheSourceFile?.sourceFile.referencedFiles.length) {
// This file was just opened and references in this file will previously not been resolved so schedule update
info.containingProjects.forEach(project => project.markAsDirty());
}
@@ -3325,7 +3340,7 @@ namespace ts.server {
}
private telemetryOnOpenFile(scriptInfo: ScriptInfo): void {
if (this.syntaxOnly || !this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
if (this.serverMode !== LanguageServiceMode.Semantic || !this.eventHandler || !scriptInfo.isJavaScript() || !addToSeen(this.allJsFilesForOpenFileTelemetry, scriptInfo.path)) {
return;
}
@@ -3637,7 +3652,7 @@ namespace ts.server {
for (const file of proj.rootFiles) {
const normalized = toNormalizedPath(file.fileName);
if (getBaseConfigFileName(normalized)) {
if (!this.syntaxOnly && this.host.fileExists(normalized)) {
if (this.serverMode === LanguageServiceMode.Semantic && this.host.fileExists(normalized)) {
(tsConfigFiles || (tsConfigFiles = [])).push(normalized);
}
}

View File

@@ -279,9 +279,21 @@ namespace ts.server {
this.compilerOptions.allowNonTsExtensions = true;
}
this.languageServiceEnabled = true;
if (projectService.syntaxOnly) {
this.compilerOptions.types = [];
switch (projectService.serverMode) {
case LanguageServiceMode.Semantic:
this.languageServiceEnabled = true;
break;
case LanguageServiceMode.ApproximateSemanticOnly:
this.languageServiceEnabled = true;
this.compilerOptions.types = [];
break;
case LanguageServiceMode.SyntaxOnly:
this.languageServiceEnabled = false;
this.compilerOptions.noResolve = true;
this.compilerOptions.types = [];
break;
default:
Debug.assertNever(projectService.serverMode);
}
this.setInternalCompilerOptionsForEmittingJsFiles();
@@ -298,10 +310,10 @@ namespace ts.server {
this.resolutionCache = createResolutionCache(
this,
currentDirectory && this.currentDirectory,
projectService.syntaxOnly ? ResolutionKind.RelativeReferencesInOpenFileOnly : ResolutionKind.All,
projectService.serverMode === LanguageServiceMode.Semantic ? ResolutionKind.All : ResolutionKind.RelativeReferencesInOpenFileOnly,
/*logChangesWhenResolvingModule*/ true
);
this.languageService = createLanguageService(this, this.documentRegistry, this.projectService.syntaxOnly);
this.languageService = createLanguageService(this, this.documentRegistry, this.projectService.serverMode);
if (lastFileExceededProgramSize) {
this.disableLanguageService(lastFileExceededProgramSize);
}
@@ -456,7 +468,16 @@ namespace ts.server {
/*@internal*/
includeTripleslashReferencesFrom(containingFile: string) {
return !this.projectService.syntaxOnly || this.fileIsOpen(this.toPath(containingFile));
switch (this.projectService.serverMode) {
case LanguageServiceMode.Semantic:
return true;
case LanguageServiceMode.ApproximateSemanticOnly:
return this.fileIsOpen(this.toPath(containingFile));
case LanguageServiceMode.SyntaxOnly:
return false;
default:
Debug.assertNever(this.projectService.serverMode);
}
}
directoryExists(path: string): boolean {
@@ -656,7 +677,7 @@ namespace ts.server {
}
enableLanguageService() {
if (this.languageServiceEnabled) {
if (this.languageServiceEnabled || this.projectService.serverMode === LanguageServiceMode.SyntaxOnly) {
return;
}
this.languageServiceEnabled = true;
@@ -668,6 +689,7 @@ namespace ts.server {
if (!this.languageServiceEnabled) {
return;
}
Debug.assert(this.projectService.serverMode !== LanguageServiceMode.SyntaxOnly);
this.languageService.cleanupSemanticCache();
this.languageServiceEnabled = false;
this.lastFileExceededProgramSize = lastFileExceededProgramSize;
@@ -997,7 +1019,7 @@ namespace ts.server {
// update builder only if language service is enabled
// otherwise tell it to drop its internal state
if (this.languageServiceEnabled && !this.projectService.syntaxOnly) {
if (this.languageServiceEnabled && this.projectService.serverMode === LanguageServiceMode.Semantic) {
// 1. no changes in structure, no changes in unresolved imports - do nothing
// 2. no changes in structure, unresolved imports were changed - collect unresolved imports for all files
// (can reuse cached imports for files that were not changed)
@@ -1128,7 +1150,7 @@ namespace ts.server {
}
// Watch the type locations that would be added to program as part of automatic type resolutions
if (this.languageServiceEnabled && !this.projectService.syntaxOnly) {
if (this.languageServiceEnabled && this.projectService.serverMode === LanguageServiceMode.Semantic) {
this.resolutionCache.updateTypeRootsWatch();
}
}

View File

@@ -585,7 +585,7 @@ namespace ts.server {
undefined;
}
const invalidSyntaxOnlyCommands: readonly CommandNames[] = [
const invalidApproximateSemanticOnlyCommands: readonly CommandNames[] = [
CommandNames.OpenExternalProject,
CommandNames.OpenExternalProjects,
CommandNames.CloseExternalProject,
@@ -621,6 +621,36 @@ namespace ts.server {
CommandNames.ProvideCallHierarchyOutgoingCalls,
];
const invalidSyntaxOnlyCommands: readonly CommandNames[] = [
...invalidApproximateSemanticOnlyCommands,
CommandNames.Definition,
CommandNames.DefinitionFull,
CommandNames.DefinitionAndBoundSpan,
CommandNames.DefinitionAndBoundSpanFull,
CommandNames.TypeDefinition,
CommandNames.Implementation,
CommandNames.ImplementationFull,
CommandNames.References,
CommandNames.ReferencesFull,
CommandNames.Rename,
CommandNames.RenameLocationsFull,
CommandNames.RenameInfoFull,
CommandNames.Quickinfo,
CommandNames.QuickinfoFull,
CommandNames.CompletionInfo,
CommandNames.Completions,
CommandNames.CompletionsFull,
CommandNames.CompletionDetails,
CommandNames.CompletionDetailsFull,
CommandNames.SignatureHelp,
CommandNames.SignatureHelpFull,
CommandNames.Navto,
CommandNames.NavtoFull,
CommandNames.Occurrences,
CommandNames.DocumentHighlights,
CommandNames.DocumentHighlightsFull,
];
export interface SessionOptions {
host: ServerHost;
cancellationToken: ServerCancellationToken;
@@ -637,7 +667,9 @@ namespace ts.server {
eventHandler?: ProjectServiceEventHandler;
/** Has no effect if eventHandler is also specified. */
suppressDiagnosticEvents?: boolean;
/** @deprecated use serverMode instead */
syntaxOnly?: boolean;
serverMode?: LanguageServiceMode;
throttleWaitMilliseconds?: number;
noGetErrOnBackgroundUpdate?: boolean;
@@ -709,18 +741,32 @@ namespace ts.server {
allowLocalPluginLoads: opts.allowLocalPluginLoads,
typesMapLocation: opts.typesMapLocation,
syntaxOnly: opts.syntaxOnly,
serverMode: opts.serverMode,
};
this.projectService = new ProjectService(settings);
this.projectService.setPerformanceEventHandler(this.performanceEventHandler.bind(this));
this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger);
// Make sure to setup handlers to throw error for not allowed commands on syntax server;
if (this.projectService.syntaxOnly) {
invalidSyntaxOnlyCommands.forEach(commandName =>
this.handlers.set(commandName, request => {
throw new Error(`Request: ${request.command} not allowed on syntaxServer`);
})
);
// Make sure to setup handlers to throw error for not allowed commands on syntax server
switch (this.projectService.serverMode) {
case LanguageServiceMode.Semantic:
break;
case LanguageServiceMode.ApproximateSemanticOnly:
invalidApproximateSemanticOnlyCommands.forEach(commandName =>
this.handlers.set(commandName, request => {
throw new Error(`Request: ${request.command} not allowed on approximate semantic only server`);
})
);
break;
case LanguageServiceMode.SyntaxOnly:
invalidSyntaxOnlyCommands.forEach(commandName =>
this.handlers.set(commandName, request => {
throw new Error(`Request: ${request.command} not allowed on syntax only server`);
})
);
break;
default:
Debug.assertNever(this.projectService.serverMode);
}
}