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
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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);
}
}

View File

@ -1171,7 +1171,7 @@ namespace ts {
}
}
const invalidOperationsOnSyntaxOnly: readonly (keyof LanguageService)[] = [
const invalidOperationsOnApproximateSemanticOnly: readonly (keyof LanguageService)[] = [
"getSyntacticDiagnostics",
"getSemanticDiagnostics",
"getSuggestionDiagnostics",
@ -1191,10 +1191,42 @@ namespace ts {
"provideCallHierarchyOutgoingCalls",
];
const invalidOperationsOnSyntaxOnly: readonly (keyof LanguageService)[] = [
...invalidOperationsOnApproximateSemanticOnly,
"getCompletionsAtPosition",
"getCompletionEntryDetails",
"getCompletionEntrySymbol",
"getSignatureHelpItems",
"getQuickInfoAtPosition",
"getDefinitionAtPosition",
"getDefinitionAndBoundSpan",
"getImplementationAtPosition",
"getTypeDefinitionAtPosition",
"getReferencesAtPosition",
"findReferences",
"getOccurrencesAtPosition",
"getDocumentHighlights",
"getNavigateToItems",
"getRenameInfo",
"findRenameLocations",
"getApplicableRefactors",
];
export function createLanguageService(
host: LanguageServiceHost,
documentRegistry: DocumentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames && host.useCaseSensitiveFileNames(), host.getCurrentDirectory()),
syntaxOnly = false): LanguageService {
syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode,
): LanguageService {
let languageServiceMode: LanguageServiceMode;
if (syntaxOnlyOrLanguageServiceMode === undefined) {
languageServiceMode = LanguageServiceMode.Semantic;
}
else if (typeof syntaxOnlyOrLanguageServiceMode === "boolean") {
// languageServiceMode = SyntaxOnly
languageServiceMode = syntaxOnlyOrLanguageServiceMode ? LanguageServiceMode.SyntaxOnly : LanguageServiceMode.Semantic;
}
else {
languageServiceMode = syntaxOnlyOrLanguageServiceMode;
}
const syntaxTreeCache: SyntaxTreeCache = new SyntaxTreeCache(host);
let program: Program;
@ -1244,6 +1276,7 @@ namespace ts {
}
function synchronizeHostData(): void {
Debug.assert(languageServiceMode !== LanguageServiceMode.SyntaxOnly);
// perform fast check if host supports it
if (host.getProjectVersion) {
const hostProjectVersion = host.getProjectVersion();
@ -1429,6 +1462,11 @@ namespace ts {
// TODO: GH#18217 frequently asserted as defined
function getProgram(): Program | undefined {
if (languageServiceMode === LanguageServiceMode.SyntaxOnly) {
Debug.assert(program === undefined);
return undefined;
}
synchronizeHostData();
return program;
@ -2504,14 +2542,26 @@ namespace ts {
uncommentSelection,
};
if (syntaxOnly) {
invalidOperationsOnSyntaxOnly.forEach(key =>
ls[key] = () => {
throw new Error(`LanguageService Operation: ${key} not allowed on syntaxServer`);
}
);
switch (languageServiceMode) {
case LanguageServiceMode.Semantic:
break;
case LanguageServiceMode.ApproximateSemanticOnly:
invalidOperationsOnApproximateSemanticOnly.forEach(key =>
ls[key] = () => {
throw new Error(`LanguageService Operation: ${key} not allowed on approximate semantic only server`);
}
);
break;
case LanguageServiceMode.SyntaxOnly:
invalidOperationsOnSyntaxOnly.forEach(key =>
ls[key] = () => {
throw new Error(`LanguageService Operation: ${key} not allowed on syntax only server`);
}
);
break;
default:
Debug.assertNever(languageServiceMode);
}
return ls;
}

View File

@ -221,6 +221,12 @@ namespace ts {
durationMs: number;
}
export enum LanguageServiceMode {
Semantic,
ApproximateSemanticOnly,
SyntaxOnly,
}
//
// Public interface of the host of a language service instance.
//

View File

@ -145,6 +145,7 @@
"unittests/tscWatch/watchApi.ts",
"unittests/tscWatch/watchEnvironment.ts",
"unittests/tsserver/applyChangesToOpenFiles.ts",
"unittests/tsserver/approximateSemanticOnlyServer.ts",
"unittests/tsserver/autoImportProvider.ts",
"unittests/tsserver/cachingFileSystemInformation.ts",
"unittests/tsserver/cancellationToken.ts",

View File

@ -0,0 +1,217 @@
namespace ts.projectSystem {
describe("unittests:: tsserver:: Semantic operations on Approximate Semantic only server", () => {
function setup() {
const file1: File = {
path: `${tscWatch.projectRoot}/a.ts`,
content: `import { y, cc } from "./b";
import { something } from "something";
class c { prop = "hello"; foo() { return this.prop; } }`
};
const file2: File = {
path: `${tscWatch.projectRoot}/b.ts`,
content: `export { cc } from "./c";
import { something } from "something";
export const y = 10;`
};
const file3: File = {
path: `${tscWatch.projectRoot}/c.ts`,
content: `export const cc = 10;`
};
const something: File = {
path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`,
content: "export const something = 10;"
};
const configFile: File = {
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
const host = createServerHost([file1, file2, file3, something, libFile, configFile]);
const session = createSession(host, { serverMode: LanguageServiceMode.ApproximateSemanticOnly, useSingleInferredProject: true });
return { host, session, file1, file2, file3, something, configFile };
}
it("open files are added to inferred project even if config file is present and semantic operations succeed", () => {
const { host, session, file1, file2, file3, something } = setup();
const service = session.getProjectService();
openFilesForSession([file1], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const project = service.inferredProjects[0];
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); // Relative import from open file is resolves but not non relative
verifyCompletions();
verifyGoToDefToB();
openFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]);
verifyCompletions();
verifyGoToDefToB();
verifyGoToDefToC();
openFilesForSession([file3], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]);
openFilesForSession([something], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
// Close open files and verify resolutions
closeFilesForSession([file3], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
closeFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
function verifyCompletions() {
assert.isTrue(project.languageServiceEnabled);
checkWatchedFiles(host, emptyArray);
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
const response = session.executeCommandSeq<protocol.CompletionsRequest>({
command: protocol.CommandTypes.Completions,
arguments: protocolFileLocationFromSubstring(file1, "prop", { index: 1 })
}).response as protocol.CompletionEntry[];
assert.deepEqual(response, [
completionEntry("foo", ScriptElementKind.memberFunctionElement),
completionEntry("prop", ScriptElementKind.memberVariableElement),
]);
}
function completionEntry(name: string, kind: ScriptElementKind): protocol.CompletionEntry {
return {
name,
kind,
kindModifiers: "",
sortText: Completions.SortText.LocationPriority,
hasAction: undefined,
insertText: undefined,
isPackageJsonImport: undefined,
isRecommended: undefined,
replacementSpan: undefined,
source: undefined
};
}
function verifyGoToDefToB() {
const response = session.executeCommandSeq<protocol.DefinitionAndBoundSpanRequest>({
command: protocol.CommandTypes.DefinitionAndBoundSpan,
arguments: protocolFileLocationFromSubstring(file1, "y")
}).response as protocol.DefinitionInfoAndBoundSpan;
assert.deepEqual(response, {
definitions: [{
file: file2.path,
...protocolTextSpanWithContextFromSubstring({ fileText: file2.content, text: "y", contextText: "export const y = 10;" })
}],
textSpan: protocolTextSpanWithContextFromSubstring({ fileText: file1.content, text: "y" })
});
}
function verifyGoToDefToC() {
const response = session.executeCommandSeq<protocol.DefinitionAndBoundSpanRequest>({
command: protocol.CommandTypes.DefinitionAndBoundSpan,
arguments: protocolFileLocationFromSubstring(file1, "cc")
}).response as protocol.DefinitionInfoAndBoundSpan;
assert.deepEqual(response, {
definitions: [{
file: file3.path,
...protocolTextSpanWithContextFromSubstring({ fileText: file3.content, text: "cc", contextText: "export const cc = 10;" })
}],
textSpan: protocolTextSpanWithContextFromSubstring({ fileText: file1.content, text: "cc" })
});
}
});
it("throws on unsupported commands", () => {
const { session, file1 } = setup();
const service = session.getProjectService();
openFilesForSession([file1], session);
let hasException = false;
const request: protocol.SemanticDiagnosticsSyncRequest = {
type: "request",
seq: 1,
command: protocol.CommandTypes.SemanticDiagnosticsSync,
arguments: { file: file1.path }
};
try {
session.executeCommand(request);
}
catch (e) {
assert.equal(e.message, `Request: semanticDiagnosticsSync not allowed on approximate semantic only server`);
hasException = true;
}
assert.isTrue(hasException);
hasException = false;
const project = service.inferredProjects[0];
try {
project.getLanguageService().getSemanticDiagnostics(file1.path);
}
catch (e) {
assert.equal(e.message, `LanguageService Operation: getSemanticDiagnostics not allowed on approximate semantic only server`);
hasException = true;
}
assert.isTrue(hasException);
});
it("should not include auto type reference directives", () => {
const { host, session, file1, file2 } = setup();
const atTypes: File = {
path: `/node_modules/@types/somemodule/index.d.ts`,
content: "export const something = 10;"
};
host.ensureFileOrFolder(atTypes);
const service = session.getProjectService();
openFilesForSession([file1], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const project = service.inferredProjects[0];
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); // Should not contain atTypes
});
it("should not include referenced files from unopened files", () => {
const file1: File = {
path: `${tscWatch.projectRoot}/a.ts`,
content: `///<reference path="b.ts"/>
///<reference path="${tscWatch.projectRoot}/node_modules/something/index.d.ts"/>
function fooA() { }`
};
const file2: File = {
path: `${tscWatch.projectRoot}/b.ts`,
content: `///<reference path="./c.ts"/>
///<reference path="${tscWatch.projectRoot}/node_modules/something/index.d.ts"/>
function fooB() { }`
};
const file3: File = {
path: `${tscWatch.projectRoot}/c.ts`,
content: `function fooC() { }`
};
const something: File = {
path: `${tscWatch.projectRoot}/node_modules/something/index.d.ts`,
content: "function something() {}"
};
const configFile: File = {
path: `${tscWatch.projectRoot}/tsconfig.json`,
content: "{}"
};
const host = createServerHost([file1, file2, file3, something, libFile, configFile]);
const session = createSession(host, { serverMode: LanguageServiceMode.ApproximateSemanticOnly, useSingleInferredProject: true });
const service = session.getProjectService();
openFilesForSession([file1], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const project = service.inferredProjects[0];
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, something.path]); // Should not contains c
openFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
assert.isTrue(project.dirty);
project.updateGraph();
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
closeFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
assert.isFalse(project.dirty);
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
});
});
}

View File

@ -86,7 +86,7 @@ namespace ts.projectSystem {
const proj = projectService.inferredProjects[0];
assert.isDefined(proj);
assert.isTrue(proj.languageServiceEnabled);
assert.isFalse(proj.languageServiceEnabled);
});
it("project settings for inferred projects", () => {

View File

@ -30,95 +30,74 @@ import { something } from "something";
return { host, session, file1, file2, file3, something, configFile };
}
it("open files are added to inferred project even if config file is present and semantic operations succeed", () => {
function verifySessionException<T extends server.protocol.Request>(session: TestSession, request: Partial<T>) {
let hasException = false;
try {
session.executeCommandSeq(request);
}
catch (e) {
assert.equal(e.message, `Request: ${request.command} not allowed on syntax only server`);
hasException = true;
}
assert.isTrue(hasException);
}
it("open files are added to inferred project even if config file is present and semantic operations fail", () => {
const { host, session, file1, file2, file3, something } = setup();
const service = session.getProjectService();
openFilesForSession([file1], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const project = service.inferredProjects[0];
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); // Relative import from open file is resolves but not non relative
checkProjectActualFiles(project, emptyArray);
verifyCompletions();
verifyGoToDefToB();
openFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]);
checkProjectActualFiles(project, emptyArray);
verifyCompletions();
verifyGoToDefToB();
verifyGoToDefToC();
openFilesForSession([file3], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path]);
checkProjectActualFiles(project, emptyArray);
openFilesForSession([something], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
checkProjectActualFiles(project, emptyArray);
// Close open files and verify resolutions
closeFilesForSession([file3], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
checkProjectActualFiles(project, emptyArray);
closeFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
checkProjectActualFiles(project, emptyArray);
function verifyCompletions() {
assert.isTrue(project.languageServiceEnabled);
assert.isFalse(project.languageServiceEnabled);
checkWatchedFiles(host, emptyArray);
checkWatchedDirectories(host, emptyArray, /*recursive*/ true);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
const response = session.executeCommandSeq<protocol.CompletionsRequest>({
verifySessionException<protocol.CompletionsRequest>(session, {
command: protocol.CommandTypes.Completions,
arguments: protocolFileLocationFromSubstring(file1, "prop", { index: 1 })
}).response as protocol.CompletionEntry[];
assert.deepEqual(response, [
completionEntry("foo", ScriptElementKind.memberFunctionElement),
completionEntry("prop", ScriptElementKind.memberVariableElement),
]);
}
function completionEntry(name: string, kind: ScriptElementKind): protocol.CompletionEntry {
return {
name,
kind,
kindModifiers: "",
sortText: Completions.SortText.LocationPriority,
hasAction: undefined,
insertText: undefined,
isPackageJsonImport: undefined,
isRecommended: undefined,
replacementSpan: undefined,
source: undefined
};
});
}
function verifyGoToDefToB() {
const response = session.executeCommandSeq<protocol.DefinitionAndBoundSpanRequest>({
verifySessionException<protocol.DefinitionAndBoundSpanRequest>(session, {
command: protocol.CommandTypes.DefinitionAndBoundSpan,
arguments: protocolFileLocationFromSubstring(file1, "y")
}).response as protocol.DefinitionInfoAndBoundSpan;
assert.deepEqual(response, {
definitions: [{
file: file2.path,
...protocolTextSpanWithContextFromSubstring({ fileText: file2.content, text: "y", contextText: "export const y = 10;" })
}],
textSpan: protocolTextSpanWithContextFromSubstring({ fileText: file1.content, text: "y" })
});
}
function verifyGoToDefToC() {
const response = session.executeCommandSeq<protocol.DefinitionAndBoundSpanRequest>({
verifySessionException<protocol.DefinitionAndBoundSpanRequest>(session, {
command: protocol.CommandTypes.DefinitionAndBoundSpan,
arguments: protocolFileLocationFromSubstring(file1, "cc")
}).response as protocol.DefinitionInfoAndBoundSpan;
assert.deepEqual(response, {
definitions: [{
file: file3.path,
...protocolTextSpanWithContextFromSubstring({ fileText: file3.content, text: "cc", contextText: "export const cc = 10;" })
}],
textSpan: protocolTextSpanWithContextFromSubstring({ fileText: file1.content, text: "cc" })
});
}
});
@ -127,36 +106,27 @@ import { something } from "something";
const { session, file1 } = setup();
const service = session.getProjectService();
openFilesForSession([file1], session);
let hasException = false;
const request: protocol.SemanticDiagnosticsSyncRequest = {
verifySessionException<protocol.SemanticDiagnosticsSyncRequest>(session, {
type: "request",
seq: 1,
command: protocol.CommandTypes.SemanticDiagnosticsSync,
arguments: { file: file1.path }
};
try {
session.executeCommand(request);
}
catch (e) {
assert.equal(e.message, `Request: semanticDiagnosticsSync not allowed on syntaxServer`);
hasException = true;
}
assert.isTrue(hasException);
});
hasException = false;
let hasException = false;
const project = service.inferredProjects[0];
try {
project.getLanguageService().getSemanticDiagnostics(file1.path);
}
catch (e) {
assert.equal(e.message, `LanguageService Operation: getSemanticDiagnostics not allowed on syntaxServer`);
assert.equal(e.message, `LanguageService Operation: getSemanticDiagnostics not allowed on syntax only server`);
hasException = true;
}
assert.isTrue(hasException);
});
it("should not include auto type reference directives", () => {
const { host, session, file1, file2 } = setup();
const { host, session, file1 } = setup();
const atTypes: File = {
path: `/node_modules/@types/somemodule/index.d.ts`,
content: "export const something = 10;"
@ -166,7 +136,7 @@ import { something } from "something";
openFilesForSession([file1], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const project = service.inferredProjects[0];
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path]); // Should not contain atTypes
checkProjectActualFiles(project, emptyArray); // Should not contain atTypes
});
it("should not include referenced files from unopened files", () => {
@ -200,18 +170,18 @@ function fooB() { }`
openFilesForSession([file1], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
const project = service.inferredProjects[0];
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, something.path]); // Should not contains c
checkProjectActualFiles(project, emptyArray);
openFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
assert.isTrue(project.dirty);
assert.isFalse(project.dirty);
project.updateGraph();
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
checkProjectActualFiles(project, emptyArray);
closeFilesForSession([file2], session);
checkNumberOfProjects(service, { inferredProjects: 1 });
assert.isFalse(project.dirty);
checkProjectActualFiles(project, [libFile.path, file1.path, file2.path, file3.path, something.path]);
assert.isTrue(project.dirty);
checkProjectActualFiles(project, emptyArray);
});
});
}

View File

@ -516,6 +516,7 @@ namespace ts.server {
canUseEvents: true,
suppressDiagnosticEvents,
syntaxOnly,
serverMode,
noGetErrOnBackgroundUpdate,
globalPlugins,
pluginProbeLocations,
@ -948,6 +949,26 @@ namespace ts.server {
return arg.split(",").filter(name => name !== "");
}
let unknownServerMode: string | undefined;
function parseServerMode(): LanguageServiceMode | undefined {
const mode = findArgument("--serverMode");
if (mode === undefined) {
return undefined;
}
switch (mode.toLowerCase()) {
case "semantic":
return LanguageServiceMode.Semantic;
case "approximatesemanticonly":
return LanguageServiceMode.ApproximateSemanticOnly;
case "syntaxonly":
return LanguageServiceMode.SyntaxOnly;
default:
unknownServerMode = mode;
return undefined;
}
}
const globalPlugins = parseStringArray("--globalPlugins");
const pluginProbeLocations = parseStringArray("--pluginProbeLocations");
const allowLocalPluginLoads = hasArgument("--allowLocalPluginLoads");
@ -957,6 +978,7 @@ namespace ts.server {
const disableAutomaticTypingAcquisition = hasArgument("--disableAutomaticTypingAcquisition");
const suppressDiagnosticEvents = hasArgument("--suppressDiagnosticEvents");
const syntaxOnly = hasArgument("--syntaxOnly");
const serverMode = parseServerMode();
const telemetryEnabled = hasArgument(Arguments.EnableTelemetry);
const noGetErrOnBackgroundUpdate = hasArgument("--noGetErrOnBackgroundUpdate");
@ -964,6 +986,7 @@ namespace ts.server {
logger.info(`Version: ${version}`);
logger.info(`Arguments: ${process.argv.join(" ")}`);
logger.info(`Platform: ${os.platform()} NodeVersion: ${nodeVersion} CaseSensitive: ${sys.useCaseSensitiveFileNames}`);
logger.info(`ServerMode: ${serverMode} syntaxOnly: ${syntaxOnly} hasUnknownServerMode: ${unknownServerMode}`);
const ioSession = new IOSession();
process.on("uncaughtException", err => {

View File

@ -5314,6 +5314,11 @@ declare namespace ts {
kind: "UpdateGraph" | "CreatePackageJsonAutoImportProvider";
durationMs: number;
}
enum LanguageServiceMode {
Semantic = 0,
ApproximateSemanticOnly = 1,
SyntaxOnly = 2
}
interface LanguageServiceHost extends GetEffectiveTypeRootsHost {
getCompilationSettings(): CompilerOptions;
getNewLine?(): string;
@ -6325,7 +6330,7 @@ declare namespace ts {
function getSupportedCodeFixes(): string[];
function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode): LanguageService;
/**
* Get the path of the default library files (lib.d.ts) as distributed with the typescript
* node package.
@ -9483,7 +9488,9 @@ declare namespace ts.server {
pluginProbeLocations?: readonly string[];
allowLocalPluginLoads?: boolean;
typesMapLocation?: string;
/** @deprecated use serverMode instead */
syntaxOnly?: boolean;
serverMode?: LanguageServiceMode;
}
export class ProjectService {
private readonly scriptInfoInNodeModulesWatchers;
@ -9555,7 +9562,9 @@ declare namespace ts.server {
readonly allowLocalPluginLoads: boolean;
private currentPluginConfigOverrides;
readonly typesMapLocation: string | undefined;
readonly syntaxOnly?: boolean;
/** @deprecated use serverMode instead */
readonly syntaxOnly: boolean;
readonly serverMode: LanguageServiceMode;
/** Tracks projects that we have already sent telemetry for. */
private readonly seenProjects;
private performanceEventHandler?;
@ -9773,7 +9782,9 @@ declare 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;
globalPlugins?: readonly string[];

View File

@ -5314,6 +5314,11 @@ declare namespace ts {
kind: "UpdateGraph" | "CreatePackageJsonAutoImportProvider";
durationMs: number;
}
enum LanguageServiceMode {
Semantic = 0,
ApproximateSemanticOnly = 1,
SyntaxOnly = 2
}
interface LanguageServiceHost extends GetEffectiveTypeRootsHost {
getCompilationSettings(): CompilerOptions;
getNewLine?(): string;
@ -6325,7 +6330,7 @@ declare namespace ts {
function getSupportedCodeFixes(): string[];
function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean, scriptKind?: ScriptKind): SourceFile;
function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange | undefined, aggressiveChecks?: boolean): SourceFile;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnly?: boolean): LanguageService;
function createLanguageService(host: LanguageServiceHost, documentRegistry?: DocumentRegistry, syntaxOnlyOrLanguageServiceMode?: boolean | LanguageServiceMode): LanguageService;
/**
* Get the path of the default library files (lib.d.ts) as distributed with the typescript
* node package.