diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 3050c9762313e..a823fe6d5864f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -1277,7 +1277,8 @@ namespace ts.server { private setConfigFileExistenceByNewConfiguredProject(project: ConfiguredProject) { const configFileExistenceInfo = this.getConfigFileExistenceInfo(project); if (configFileExistenceInfo) { - Debug.assert(configFileExistenceInfo.exists); + // The existance might not be set if the file watcher is not invoked by the time config project is created by external project + configFileExistenceInfo.exists = true; // close existing watcher if (configFileExistenceInfo.configFileWatcherForRootOfInferredProject) { const configFileName = project.getConfigFilePath(); diff --git a/src/testRunner/unittests/tsserver/externalProjects.ts b/src/testRunner/unittests/tsserver/externalProjects.ts index 12a97151f4e8b..2055141538acd 100644 --- a/src/testRunner/unittests/tsserver/externalProjects.ts +++ b/src/testRunner/unittests/tsserver/externalProjects.ts @@ -760,5 +760,63 @@ namespace ts.projectSystem { assert.equal(project2.pendingReload, ConfigFileProgramReloadLevel.None); // External project referenced configured project loaded checkProjectActualFiles(project2, [config.path, f1.path]); }); + + it("handles creation of external project with jsconfig before jsconfig creation watcher is invoked", () => { + const projectLocation = `/user/username/projects/WebApplication36/WebApplication36`; + const projectFileName = `${projectLocation}/WebApplication36.csproj`; + const tsconfig: File = { + path: `${projectLocation}/tsconfig.json`, + content: "{}" + }; + const files = [libFile, tsconfig]; + const host = createServerHost(files); + const service = createProjectService(host); + + // Create external project + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: tsconfig.path }], + options: { allowJs: false } + }]); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const configProject = service.configuredProjects.get(tsconfig.path.toLowerCase())!; + checkProjectActualFiles(configProject, [tsconfig.path]); + + // write js file, open external project and open it for edit + const jsFilePath = `${projectLocation}/javascript.js`; + host.writeFile(jsFilePath, ""); + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: tsconfig.path }, { fileName: jsFilePath }], + options: { allowJs: false } + }]); + service.applyChangesInOpenFiles([ + { fileName: jsFilePath, scriptKind: ScriptKind.JS, content: "" } + ], /*changedFiles*/ undefined, /*closedFiles*/ undefined); + checkNumberOfProjects(service, { configuredProjects: 1, inferredProjects: 1 }); + checkProjectActualFiles(configProject, [tsconfig.path]); + const inferredProject = service.inferredProjects[0]; + checkProjectActualFiles(inferredProject, [libFile.path, jsFilePath]); + + // write jsconfig file + const jsConfig: File = { + path: `${projectLocation}/jsconfig.json`, + content: "{}" + }; + // Dont invoke file creation watchers as the repro suggests + host.ensureFileOrFolder(jsConfig, /*ignoreWatchInvokedWithTriggerAsFileCreate*/ true); + + // Open external project + service.openExternalProjects([{ + projectFileName, + rootFiles: [{ fileName: jsConfig.path }, { fileName: tsconfig.path }, { fileName: jsFilePath }], + options: { allowJs: false } + }]); + checkNumberOfProjects(service, { configuredProjects: 2, inferredProjects: 1 }); + checkProjectActualFiles(configProject, [tsconfig.path]); + assert.isTrue(inferredProject.isOrphan()); + const jsConfigProject = service.configuredProjects.get(jsConfig.path.toLowerCase())!; + checkProjectActualFiles(jsConfigProject, [jsConfig.path, jsFilePath, libFile.path]); + }); }); } diff --git a/src/testRunner/unittests/tsserver/helpers.ts b/src/testRunner/unittests/tsserver/helpers.ts index 1db6a9b36c8bf..3e79451cd116a 100644 --- a/src/testRunner/unittests/tsserver/helpers.ts +++ b/src/testRunner/unittests/tsserver/helpers.ts @@ -57,8 +57,8 @@ namespace ts.projectSystem { export const nullLogger: server.Logger = { close: noop, - hasLevel: () => false, - loggingEnabled: () => false, + hasLevel: returnFalse, + loggingEnabled: returnFalse, perftrc: noop, info: noop, msg: noop, @@ -80,6 +80,21 @@ namespace ts.projectSystem { return { logger, hasErrorMsg: () => hasErrorMsg }; } + export function createLoggerWritingToConsole(): server.Logger { + const { close, startGroup, endGroup, getLogFileName } = nullLogger; + return { + close, + hasLevel: returnTrue, + loggingEnabled: returnTrue, + perftrc: s => console.log(s), + info: s => console.log(s), + msg: (s, type) => console.log(`${type}:: ${s}`), + startGroup, + endGroup, + getLogFileName + }; + } + export class TestTypingsInstaller extends TI.TypingsInstaller implements server.ITypingsInstaller { protected projectService!: server.ProjectService; constructor(