From 96a867a52ef7ad9f5483a4475a903d93cf55ac33 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 27 Jun 2016 14:25:43 -0700 Subject: [PATCH] add support for single inferred project --- src/harness/harnessLanguageService.ts | 1 + src/server/editorServices.ts | 86 +++++++---- src/server/project.ts | 4 + src/server/protocol.d.ts | 5 + src/server/server.ts | 7 +- src/server/session.ts | 14 +- .../cases/unittests/cachingInServerLSHost.ts | 4 +- tests/cases/unittests/session.ts | 6 +- .../cases/unittests/tsserverProjectSystem.ts | 145 +++++++++++++----- 9 files changed, 183 insertions(+), 89 deletions(-) diff --git a/src/harness/harnessLanguageService.ts b/src/harness/harnessLanguageService.ts index 7a7d0f6dcd8..553d59428ab 100644 --- a/src/harness/harnessLanguageService.ts +++ b/src/harness/harnessLanguageService.ts @@ -681,6 +681,7 @@ namespace Harness.LanguageService { const serverHost = new SessionServerHost(clientHost); const server = new ts.server.Session(serverHost, { isCancellationRequested: () => false }, + /*useOneInferredProject*/ false, Buffer ? Buffer.byteLength : (string: string, encoding?: string) => string.length, process.hrtime, serverHost); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index f5a7aba2917..857d2c95dc5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -137,26 +137,25 @@ namespace ts.server { private readonly directoryWatchers: DirectoryWatchers; - private hostConfiguration: HostConfiguration; + private readonly hostConfiguration: HostConfiguration; private timerForDetectingProjectFileListChanges: Map = {}; constructor(public readonly host: ServerHost, public readonly logger: Logger, public readonly cancellationToken: HostCancellationToken, + private readonly useOneInferredProject: boolean, private readonly eventHandler?: ProjectServiceEventHandler) { this.directoryWatchers = new DirectoryWatchers(this); // ts.disableIncrementalParsing = true; - this.setDefaultHostConfiguration(); - this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory()); - } - private setDefaultHostConfiguration() { this.hostConfiguration = { formatCodeOptions: getDefaultFormatCodeSettings(this.host), hostInfo: "Unknown host" }; + + this.documentRegistry = createDocumentRegistry(host.useCaseSensitiveFileNames, host.getCurrentDirectory()); } stopWatchingDirectory(directory: string) { @@ -378,31 +377,36 @@ namespace ts.server { } if (info.containingProjects.length === 0) { // create new inferred project p with the newly opened file as root - const inferredProject = this.createAndAddInferredProject(info); - const openFileRoots: ScriptInfo[] = []; - // for each inferred project root r - for (const rootFile of this.openFileRoots) { - // if r referenced by the new project - if (inferredProject.containsScriptInfo(rootFile)) { - // remove inferred project that was initially created for rootFile - const defaultProject = rootFile.getDefaultProject(); - if (defaultProject === inferredProject) { - continue; - } - Debug.assert(defaultProject.projectKind === ProjectKind.Inferred); + // or add root to existing inferred project if 'useOneInferredProject' is true + const inferredProject = this.addFileToInferredProject(info); + if (!this.useOneInferredProject) { - this.removeProject(defaultProject); - // put r in referenced open file list - this.openFilesReferenced.push(rootFile); - // set default project of r to the new project - rootFile.attachToProject(inferredProject); - } - else { - // otherwise, keep r as root of inferred project - openFileRoots.push(rootFile); + // if useOneInferredProject is not set then try to fixup ownership of open files + const openFileRoots: ScriptInfo[] = []; + // for each inferred project root r + for (const rootFile of this.openFileRoots) { + // if r referenced by the new project + if (inferredProject.containsScriptInfo(rootFile)) { + // remove inferred project that was initially created for rootFile + const defaultProject = rootFile.getDefaultProject(); + if (defaultProject === inferredProject) { + continue; + } + Debug.assert(defaultProject.projectKind === ProjectKind.Inferred); + + this.removeProject(defaultProject); + // put r in referenced open file list + this.openFilesReferenced.push(rootFile); + // set default project of r to the new project + rootFile.attachToProject(inferredProject); + } + else { + // otherwise, keep r as root of inferred project + openFileRoots.push(rootFile); + } } + this.openFileRoots = openFileRoots; } - this.openFileRoots = openFileRoots; } this.openFileRoots.push(info); @@ -736,6 +740,8 @@ namespace ts.server { // delete inferred project let toRemove: Project[]; + + // TODO: unify logic for (const p of info.containingProjects) { if (p.projectKind === ProjectKind.Inferred && p.isRoot(info)) { (toRemove || (toRemove = [])).push(p); @@ -743,7 +749,10 @@ namespace ts.server { } if (toRemove) { for (const p of toRemove) { - this.removeProject(p); + p.removeFile(info); + if (!p.hasRoots()) { + this.removeProject(p); + } } } } @@ -792,8 +801,12 @@ namespace ts.server { } } - createAndAddInferredProject(root: ScriptInfo) { - const project = new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true); + addFileToInferredProject(root: ScriptInfo) { + const useExistingProject = this.useOneInferredProject && this.inferredProjects.length; + const project = useExistingProject + ? this.inferredProjects[0] + : new InferredProject(this, this.documentRegistry, /*languageServiceEnabled*/ true); + project.addRoot(root); this.directoryWatchers.startWatchingContainingDirectoriesForFile( @@ -802,7 +815,10 @@ namespace ts.server { fileName => this.onConfigFileAddedForInferredProject(fileName)); project.updateGraph(); - this.inferredProjects.push(project); + + if (!useExistingProject) { + this.inferredProjects.push(project); + } return project; } @@ -966,7 +982,10 @@ namespace ts.server { if (inConfiguredProject || inExternalProject) { const inferredProjects = rootFile.containingProjects.filter(p => p.projectKind === ProjectKind.Inferred); for (const p of inferredProjects) { - this.removeProject(p); + p.removeFile(rootFile, /*detachFromProject*/ true); + if (!p.hasRoots()) { + this.removeProject(p); + } } if (inConfiguredProject) { this.openFileRootsConfigured.push(rootFile); @@ -1033,6 +1052,9 @@ namespace ts.server { for (const f of unattachedOpenFiles) { this.addOpenFile(f); } + for (const p of this.inferredProjects) { + p.updateGraph(); + } this.printProjects(); } diff --git a/src/server/project.ts b/src/server/project.ts index 2e735b01237..00eeda9d8f0 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -108,6 +108,10 @@ namespace ts.server { return this.compilerOptions; } + hasRoots() { + return this.rootFiles.length > 0; + } + getRootFiles() { return this.rootFiles.map(info => info.fileName); } diff --git a/src/server/protocol.d.ts b/src/server/protocol.d.ts index 54ce4acea41..e89b737b8c1 100644 --- a/src/server/protocol.d.ts +++ b/src/server/protocol.d.ts @@ -606,6 +606,11 @@ declare namespace ts.server.protocol { * The format options to use during formatting and other code editing features. */ formatOptions?: FormatOptions; + + /** + * If set to true - then all loose files will land into one inferred project + */ + useOneInferredProject?: boolean; } /** diff --git a/src/server/server.ts b/src/server/server.ts index 98762982d3f..79b40836f54 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -91,8 +91,8 @@ namespace ts.server { } class IOSession extends Session { - constructor(host: ServerHost, cancellationToken: HostCancellationToken, logger: ts.server.Logger) { - super(host, cancellationToken, Buffer.byteLength, process.hrtime, logger); + constructor(host: ServerHost, cancellationToken: HostCancellationToken, useOneInferredProject: boolean, logger: ts.server.Logger) { + super(host, cancellationToken, useOneInferredProject, Buffer.byteLength, process.hrtime, logger); } exit() { @@ -304,7 +304,8 @@ namespace ts.server { }; }; - const ioSession = new IOSession(sys, cancellationToken, logger); + const useOneInferredProject = sys.args.some(arg => arg === "--useOneInferredProject"); + const ioSession = new IOSession(sys, cancellationToken, useOneInferredProject, logger); process.on("uncaughtException", function(err: Error) { ioSession.logError(err, "unknown"); }); diff --git a/src/server/session.ts b/src/server/session.ts index d300cd13990..dfe532805cd 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -178,12 +178,13 @@ namespace ts.server { constructor( private host: ServerHost, - private cancellationToken: HostCancellationToken, + cancellationToken: HostCancellationToken, + useOneInferredProject: boolean, private byteLength: (buf: string, encoding?: string) => number, private hrtime: (start?: number[]) => number[], private logger: Logger) { this.projectService = - new ProjectService(host, logger, cancellationToken, (eventName, project, fileName) => { + new ProjectService(host, logger, cancellationToken, useOneInferredProject, (eventName, project, fileName) => { this.handleEvent(eventName, project, fileName); }); } @@ -1380,10 +1381,10 @@ namespace ts.server { this.cleanup(); return this.requiredResponse(true); }, - [CommandNames.SemanticDiagnosticsSync]: (request: protocol.FileRequest) => { + [CommandNames.SemanticDiagnosticsSync]: (request: protocol.SemanticDiagnosticsSyncRequest) => { return this.requiredResponse(this.getSemanticDiagnosticsSync(request.arguments)); }, - [CommandNames.SyntacticDiagnosticsSync]: (request: protocol.FileRequest) => { + [CommandNames.SyntacticDiagnosticsSync]: (request: protocol.SyntacticDiagnosticsSyncRequest) => { return this.requiredResponse(this.getSyntacticDiagnosticsSync(request.arguments)); }, [CommandNames.Geterr]: (request: protocol.Request) => { @@ -1398,9 +1399,8 @@ namespace ts.server { this.change(request.arguments); return this.notRequired(); }, - [CommandNames.Configure]: (request: protocol.Request) => { - const configureArgs = request.arguments; - this.projectService.setHostConfiguration(configureArgs); + [CommandNames.Configure]: (request: protocol.ConfigureRequest) => { + this.projectService.setHostConfiguration(request.arguments); this.output(undefined, CommandNames.Configure, request.seq); return this.notRequired(); }, diff --git a/tests/cases/unittests/cachingInServerLSHost.ts b/tests/cases/unittests/cachingInServerLSHost.ts index 2402102496d..0a96afa14f4 100644 --- a/tests/cases/unittests/cachingInServerLSHost.ts +++ b/tests/cases/unittests/cachingInServerLSHost.ts @@ -79,9 +79,9 @@ namespace ts { msg: (s: string, type?: string) => { } }; - const projectService = new server.ProjectService(serverHost, logger, { isCancellationRequested: () => false }); + const projectService = new server.ProjectService(serverHost, logger, { isCancellationRequested: () => false }, /*useOneInferredProject*/ false); const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */true, /*containingProject*/ undefined); - const project = projectService.createAndAddInferredProject(rootScriptInfo); + const project = projectService.addFileToInferredProject(rootScriptInfo); project.setCompilerOptions({ module: ts.ModuleKind.AMD } ); return { project, diff --git a/tests/cases/unittests/session.ts b/tests/cases/unittests/session.ts index a687d8cac93..c8a952da02e 100644 --- a/tests/cases/unittests/session.ts +++ b/tests/cases/unittests/session.ts @@ -40,7 +40,7 @@ namespace ts.server { let lastSent: protocol.Message; beforeEach(() => { - session = new Session(mockHost, nullCancellationToken, Utils.byteLength, process.hrtime, mockLogger); + session = new Session(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, Utils.byteLength, process.hrtime, mockLogger); session.send = (msg: protocol.Message) => { lastSent = msg; }; @@ -265,7 +265,7 @@ namespace ts.server { lastSent: protocol.Message; customHandler = "testhandler"; constructor() { - super(mockHost, nullCancellationToken, Utils.byteLength, process.hrtime, mockLogger); + super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, Utils.byteLength, process.hrtime, mockLogger); this.addProtocolHandler(this.customHandler, () => { return { response: undefined, responseRequired: true }; }); @@ -323,7 +323,7 @@ namespace ts.server { class InProcSession extends Session { private queue: protocol.Request[] = []; constructor(private client: InProcClient) { - super(mockHost, nullCancellationToken, Utils.byteLength, process.hrtime, mockLogger); + super(mockHost, nullCancellationToken, /*useOneInferredProject*/ false, Utils.byteLength, process.hrtime, mockLogger); this.addProtocolHandler("echo", (req: protocol.Request) => ({ response: req.arguments, responseRequired: true diff --git a/tests/cases/unittests/tsserverProjectSystem.ts b/tests/cases/unittests/tsserverProjectSystem.ts index 78cb8a23716..9e003bc8049 100644 --- a/tests/cases/unittests/tsserverProjectSystem.ts +++ b/tests/cases/unittests/tsserverProjectSystem.ts @@ -26,6 +26,22 @@ namespace ts { return combinePaths(getDirectoryPath(libFile.path), "tsc.js"); } + interface TestServerHostCreationParameters { + fileOrFolderList: FileOrFolder[]; + useCaseSensitiveFileNames?: boolean; + executingFilePath?: string; + libFile?: FileOrFolder; + currentDirectory?: string; + } + + function createServerHost(params: TestServerHostCreationParameters): TestServerHost { + return new TestServerHost( + params.useCaseSensitiveFileNames !== undefined ? params.useCaseSensitiveFileNames : false, + params.executingFilePath || getExecutingFilePathFromLibFile(params.libFile), + params.currentDirectory || "/", + params.fileOrFolderList); + } + interface FileOrFolder { path: string; content?: string; @@ -112,12 +128,12 @@ namespace ts { checkMapKeys("watchedDirectories", host.watchedDirectories, expectedDirectories); } - function checkConfiguredProjectActualFiles(project: server.Project, expectedFiles: string[]) { - checkFileNames("configuredProjects project, actualFileNames", project.getFileNames(), expectedFiles); + function checkProjectActualFiles(project: server.Project, expectedFiles: string[]) { + checkFileNames(`${server.ProjectKind[project.projectKind]} project, actual files`, project.getFileNames(), expectedFiles); } - function checkConfiguredProjectRootFiles(project: server.Project, expectedFiles: string[]) { - checkFileNames("configuredProjects project, rootFileNames", project.getRootFiles(), expectedFiles); + function checkProjectRootFiles(project: server.Project, expectedFiles: string[]) { + checkFileNames(`${server.ProjectKind[project.projectKind]} project, rootFileNames`, project.getRootFiles(), expectedFiles); } type TimeOutCallback = () => any; @@ -193,7 +209,7 @@ namespace ts { return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), (dir) => { const result: FileSystemEntries = { directories: [], - files : [] + files: [] }; const dirEntry = that.fs.get(that.toPath(dir)); if (isFolder(dirEntry)) { @@ -325,8 +341,8 @@ namespace ts { path: "/a/b/c/module.d.ts", content: `export let x: number` }; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [appFile, moduleFile, libFile]); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: [appFile, moduleFile, libFile], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); const { configFileName } = projectService.openClientFile(appFile.path); assert(!configFileName, `should not find config, got: '${configFileName}`); @@ -363,8 +379,9 @@ namespace ts { content: "let z = 1" }; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [ configFile, libFile, file1, file2, file3 ]); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + + const host = createServerHost({ fileOrFolderList: [configFile, libFile, file1, file2, file3], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); assert(configFileName, "should find config file"); @@ -373,8 +390,8 @@ namespace ts { checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; - checkConfiguredProjectActualFiles(project, [file1.path, libFile.path, file2.path]); - checkConfiguredProjectRootFiles(project, [file1.path, file2.path]); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path]); + checkProjectRootFiles(project, [file1.path, file2.path]); // watching all files except one that was open checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); checkWatchedDirectories(host, [getDirectoryPath(configFile.path)]); @@ -387,10 +404,11 @@ namespace ts { "files": ["commonFile1.ts"] }` }; - const filesWithoutConfig = [ libFile, commonFile1, commonFile2 ]; - const filesWithConfig = [ libFile, commonFile1, commonFile2, configFile ]; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", filesWithoutConfig); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const filesWithoutConfig = [libFile, commonFile1, commonFile2]; + const host = createServerHost({ fileOrFolderList: filesWithoutConfig, libFile }); + + const filesWithConfig = [libFile, commonFile1, commonFile2, configFile]; + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(commonFile1.path); projectService.openClientFile(commonFile2.path); @@ -420,21 +438,21 @@ namespace ts { path: "/a/b/tsconfig.json", content: `{}` }; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [commonFile1, libFile, configFile]); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: [commonFile1, libFile, configFile], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(commonFile1.path); checkWatchedDirectories(host, ["/a/b"]); checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; - checkConfiguredProjectRootFiles(project, [commonFile1.path]); + checkProjectRootFiles(project, [commonFile1.path]); // add a new ts file host.reloadFS([commonFile1, commonFile2, libFile, configFile]); host.triggerDirectoryWatcherCallback("/a/b", commonFile2.path); host.runQueuedTimeoutCallbacks(); // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. - checkConfiguredProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); it("should ignore non-existing files specified in the config file", () => { @@ -448,14 +466,14 @@ namespace ts { ] }` }; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [commonFile1, commonFile2, configFile]); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: [commonFile1, commonFile2, configFile], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(commonFile1.path); projectService.openClientFile(commonFile2.path); checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; - checkConfiguredProjectRootFiles(project, [commonFile1.path]); + checkProjectRootFiles(project, [commonFile1.path]); checkNumberOfInferredProjects(projectService, 1); }); @@ -464,25 +482,25 @@ namespace ts { path: "/a/b/tsconfig.json", content: `{}` }; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [commonFile1, commonFile2, configFile]); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: [commonFile1, commonFile2, configFile], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(commonFile1.path); checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; - checkConfiguredProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); // delete commonFile2 host.reloadFS([commonFile1, configFile]); host.triggerDirectoryWatcherCallback("/a/b", commonFile2.path); host.runQueuedTimeoutCallbacks(); - checkConfiguredProjectRootFiles(project, [commonFile1.path]); + checkProjectRootFiles(project, [commonFile1.path]); // re-add commonFile2 host.reloadFS([commonFile1, commonFile2, configFile]); host.triggerDirectoryWatcherCallback("/a/b", commonFile2.path); host.runQueuedTimeoutCallbacks(); - checkConfiguredProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); it("should create new inferred projects for files excluded from a configured project", () => { @@ -494,12 +512,12 @@ namespace ts { }` }; const files = [commonFile1, commonFile2, configFile]; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", files); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: files, libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(commonFile1.path); const project = projectService.configuredProjects[0]; - checkConfiguredProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); configFile.content = `{ "compilerOptions": {}, "files": ["${commonFile1.path}"] @@ -508,7 +526,7 @@ namespace ts { host.triggerFileWatcherCallback(configFile.path); checkNumberOfConfiguredProjects(projectService, 1); - checkConfiguredProjectRootFiles(project, [commonFile1.path]); + checkProjectRootFiles(project, [commonFile1.path]); projectService.openClientFile(commonFile2.path); checkNumberOfInferredProjects(projectService, 1); @@ -527,13 +545,13 @@ namespace ts { content: `let t = 1;` }; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [commonFile1, commonFile2, excludedFile1, configFile]); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: [commonFile1, commonFile2, excludedFile1, configFile], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(commonFile1.path); checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; - checkConfiguredProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); projectService.openClientFile(excludedFile1.path); checkNumberOfInferredProjects(projectService, 1); }); @@ -561,15 +579,15 @@ namespace ts { }` }; const files = [file1, nodeModuleFile, classicModuleFile, configFile]; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", files); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: files, libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(file1.path); projectService.openClientFile(nodeModuleFile.path); projectService.openClientFile(classicModuleFile.path); checkNumberOfConfiguredProjects(projectService, 1); const project = projectService.configuredProjects[0]; - checkConfiguredProjectActualFiles(project, [file1.path, nodeModuleFile.path]); + checkProjectActualFiles(project, [file1.path, nodeModuleFile.path]); checkNumberOfInferredProjects(projectService, 1); configFile.content = `{ @@ -580,7 +598,7 @@ namespace ts { }`; host.reloadFS(files); host.triggerFileWatcherCallback(configFile.path); - checkConfiguredProjectActualFiles(project, [file1.path, classicModuleFile.path]); + checkProjectActualFiles(project, [file1.path, classicModuleFile.path]); checkNumberOfInferredProjects(projectService, 1); }); @@ -602,8 +620,8 @@ namespace ts { "files": [ "main.ts" ] }` }; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [file1, file2, configFile]); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: [file1, file2, configFile], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(file1.path); projectService.closeClientFile(file1.path); projectService.openClientFile(file2.path); @@ -629,13 +647,56 @@ namespace ts { "files": [ "main.ts" ] }` }; - const host = new TestServerHost(/*useCaseSensitiveFileNames*/ false, getExecutingFilePathFromLibFile(libFile), "/", [file1, file2, configFile]); - const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken); + const host = createServerHost({ fileOrFolderList: [file1, file2, configFile], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ false); projectService.openClientFile(file1.path); projectService.closeClientFile(file1.path); projectService.openClientFile(file2.path); checkNumberOfConfiguredProjects(projectService, 1); checkNumberOfInferredProjects(projectService, 0); }); + + it("should use only one inferred project if 'useOneInferredProject' is set", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ + "compilerOptions": { + "target": "es6" + }, + "files": [ "main.ts" ] + }` + }; + const file2 = { + path: "/a/c/main.ts", + content: "let x =1;" + }; + + const file3 = { + path: "/a/d/main.ts", + content: "let x =1;" + }; + + const host = createServerHost({ fileOrFolderList: [file1, file2, file3, libFile], libFile }); + const projectService = new server.ProjectService(host, nullLogger, nullCancellationToken, /*useOneInferredProject*/ true); + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); + projectService.openClientFile(file3.path); + + checkNumberOfConfiguredProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path, libFile.path]); + + + host.reloadFS([file1, configFile, file2, file3, libFile]); + host.triggerDirectoryWatcherCallback(getDirectoryPath(configFile.path), configFile.path); + + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); + }); }); } \ No newline at end of file