From 0e4b8196f37c3231b4e30ddd28f05285b405f910 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 27 Nov 2017 15:14:40 -0800 Subject: [PATCH 01/21] enable running all tsserverProjectSystem tests --- src/harness/unittests/tsserverProjectSystem.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index ff2db0f9ee..3496d527d0 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -474,6 +474,8 @@ namespace ts.projectSystem { } describe("tsserverProjectSystem", () => { + + describe("general functionality", () => { const commonFile1: FileOrFolder = { path: "/a/b/commonFile1.ts", content: "let x = 1" @@ -6470,4 +6472,5 @@ namespace ts.projectSystem { verifyWatchedDirectories(/*useProjectAtRoot*/ false); }); }); + }); } From 0a6a568d013941dd416af2b35f9c5ea4304c4a39 Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Mon, 27 Nov 2017 15:14:55 -0800 Subject: [PATCH 02/21] reindent --- .../unittests/tsserverProjectSystem.ts | 10784 ++++++++-------- 1 file changed, 5392 insertions(+), 5392 deletions(-) diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 3496d527d0..deb7ca64cb 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -474,5219 +474,5219 @@ namespace ts.projectSystem { } describe("tsserverProjectSystem", () => { - - describe("general functionality", () => { - const commonFile1: FileOrFolder = { - path: "/a/b/commonFile1.ts", - content: "let x = 1" - }; - const commonFile2: FileOrFolder = { - path: "/a/b/commonFile2.ts", - content: "let y = 1" - }; - it("create inferred project", () => { - const appFile: FileOrFolder = { - path: "/a/b/c/app.ts", - content: ` + describe("general functionality", () => { + const commonFile1: FileOrFolder = { + path: "/a/b/commonFile1.ts", + content: "let x = 1" + }; + const commonFile2: FileOrFolder = { + path: "/a/b/commonFile2.ts", + content: "let y = 1" + }; + + it("create inferred project", () => { + const appFile: FileOrFolder = { + path: "/a/b/c/app.ts", + content: ` import {f} from "./module" console.log(f) ` - }; + }; - const moduleFile: FileOrFolder = { - path: "/a/b/c/module.d.ts", - content: `export let x: number` - }; - const host = createServerHost([appFile, moduleFile, libFile]); - const projectService = createProjectService(host); - const { configFileName } = projectService.openClientFile(appFile.path); + const moduleFile: FileOrFolder = { + path: "/a/b/c/module.d.ts", + content: `export let x: number` + }; + const host = createServerHost([appFile, moduleFile, libFile]); + const projectService = createProjectService(host); + const { configFileName } = projectService.openClientFile(appFile.path); - assert(!configFileName, `should not find config, got: '${configFileName}`); - checkNumberOfConfiguredProjects(projectService, 0); - checkNumberOfInferredProjects(projectService, 1); + assert(!configFileName, `should not find config, got: '${configFileName}`); + checkNumberOfConfiguredProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 1); - const project = projectService.inferredProjects[0]; + const project = projectService.inferredProjects[0]; - checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); - const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; - const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); - checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); - checkWatchedDirectories(host, [], /*recursive*/ false); - checkWatchedDirectories(host, ["/a/b/c", combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true); - }); - - it("can handle tsconfig file name with difference casing", () => { - const f1 = { - path: "/a/b/app.ts", - content: "let x = 1" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ - include: [] - }) - }; - - const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); - const service = createProjectService(host); - const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); - service.openExternalProject({ - projectFileName: "/a/b/project.csproj", - rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), - options: {} + checkFileNames("inferred project", project.getFileNames(), [appFile.path, libFile.path, moduleFile.path]); + const configFileLocations = ["/a/b/c/", "/a/b/", "/a/", "/"]; + const configFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]); + checkWatchedFiles(host, configFiles.concat(libFile.path, moduleFile.path)); + checkWatchedDirectories(host, [], /*recursive*/ false); + checkWatchedDirectories(host, ["/a/b/c", combinePaths(getDirectoryPath(appFile.path), nodeModulesAtTypes)], /*recursive*/ true); }); - service.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]); - service.openClientFile(f1.path); - service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + it("can handle tsconfig file name with difference casing", () => { + const f1 = { + path: "/a/b/app.ts", + content: "let x = 1" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ + include: [] + }) + }; - checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]); - checkProjectActualFiles(service.inferredProjects[0], [f1.path]); - }); + const host = createServerHost([f1, config], { useCaseSensitiveFileNames: false }); + const service = createProjectService(host); + const upperCaseConfigFilePath = combinePaths(getDirectoryPath(config.path).toUpperCase(), getBaseFileName(config.path)); + service.openExternalProject({ + projectFileName: "/a/b/project.csproj", + rootFiles: toExternalFiles([f1.path, upperCaseConfigFilePath]), + options: {} + }); + service.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]); - it("create configured project without file list", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: ` + service.openClientFile(f1.path); + service.checkNumberOfProjects({ configuredProjects: 1, inferredProjects: 1 }); + + checkProjectActualFiles(configuredProjectAt(service, 0), [upperCaseConfigFilePath]); + checkProjectActualFiles(service.inferredProjects[0], [f1.path]); + }); + + it("create configured project without file list", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: ` { "compilerOptions": {}, "exclude": [ "e" ] }` - }; - const file1: FileOrFolder = { - path: "/a/b/c/f1.ts", - content: "let x = 1" - }; - const file2: FileOrFolder = { - path: "/a/b/d/f2.ts", - content: "let y = 1" - }; - const file3: FileOrFolder = { - path: "/a/b/e/f3.ts", - content: "let z = 1" - }; + }; + const file1: FileOrFolder = { + path: "/a/b/c/f1.ts", + content: "let x = 1" + }; + const file2: FileOrFolder = { + path: "/a/b/d/f2.ts", + content: "let y = 1" + }; + const file3: FileOrFolder = { + path: "/a/b/e/f3.ts", + content: "let z = 1" + }; - const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host); - const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + const host = createServerHost([configFile, libFile, file1, file2, file3]); + const projectService = createProjectService(host); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - assert(configFileName, "should find config file"); - assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); - checkNumberOfInferredProjects(projectService, 0); - checkNumberOfConfiguredProjects(projectService, 1); + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + checkNumberOfInferredProjects(projectService, 0); + checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); - checkProjectRootFiles(project, [file1.path, file2.path]); - // watching all files except one that was open - checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); - const configFileDirectory = getDirectoryPath(configFile.path); - checkWatchedDirectories(host, [configFileDirectory, combinePaths(configFileDirectory, nodeModulesAtTypes)], /*recursive*/ true); - }); + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.path]); + checkProjectRootFiles(project, [file1.path, file2.path]); + // watching all files except one that was open + checkWatchedFiles(host, [configFile.path, file2.path, libFile.path]); + const configFileDirectory = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, [configFileDirectory, combinePaths(configFileDirectory, nodeModulesAtTypes)], /*recursive*/ true); + }); - it("create configured project with the file list", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: ` + it("create configured project with the file list", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: ` { "compilerOptions": {}, "include": ["*.ts"] }` - }; - const file1: FileOrFolder = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2: FileOrFolder = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const file3: FileOrFolder = { - path: "/a/b/c/f3.ts", - content: "let z = 1" - }; + }; + const file1: FileOrFolder = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2: FileOrFolder = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const file3: FileOrFolder = { + path: "/a/b/c/f3.ts", + content: "let z = 1" + }; - const host = createServerHost([configFile, libFile, file1, file2, file3]); - const projectService = createProjectService(host); - const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); + const host = createServerHost([configFile, libFile, file1, file2, file3]); + const projectService = createProjectService(host); + const { configFileName, configFileErrors } = projectService.openClientFile(file1.path); - assert(configFileName, "should find config file"); - assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); - checkNumberOfInferredProjects(projectService, 0); - checkNumberOfConfiguredProjects(projectService, 1); + assert(configFileName, "should find config file"); + assert.isTrue(!configFileErrors || configFileErrors.length === 0, `expect no errors in config file, got ${JSON.stringify(configFileErrors)}`); + checkNumberOfInferredProjects(projectService, 0); + checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.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)], /*recursive*/ false); - }); + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [file1.path, libFile.path, file2.path, configFile.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)], /*recursive*/ false); + }); - it("add and then remove a config file in a folder with loose files", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("add and then remove a config file in a folder with loose files", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "files": ["commonFile1.ts"] }` - }; - const filesWithoutConfig = [libFile, commonFile1, commonFile2]; - const host = createServerHost(filesWithoutConfig); + }; + const filesWithoutConfig = [libFile, commonFile1, commonFile2]; + const host = createServerHost(filesWithoutConfig); - const filesWithConfig = [libFile, commonFile1, commonFile2, configFile]; - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(commonFile2.path); + const filesWithConfig = [libFile, commonFile1, commonFile2, configFile]; + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + projectService.openClientFile(commonFile2.path); - checkNumberOfInferredProjects(projectService, 2); - const configFileLocations = ["/", "/a/", "/a/b/"]; - const watchedFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]).concat(libFile.path); - checkWatchedFiles(host, watchedFiles); + checkNumberOfInferredProjects(projectService, 2); + const configFileLocations = ["/", "/a/", "/a/b/"]; + const watchedFiles = flatMap(configFileLocations, location => [location + "tsconfig.json", location + "jsconfig.json"]).concat(libFile.path); + checkWatchedFiles(host, watchedFiles); - // Add a tsconfig file - host.reloadFS(filesWithConfig); - host.checkTimeoutQueueLengthAndRun(1); - checkNumberOfInferredProjects(projectService, 1); - checkNumberOfConfiguredProjects(projectService, 1); - checkWatchedFiles(host, watchedFiles); + // Add a tsconfig file + host.reloadFS(filesWithConfig); + host.checkTimeoutQueueLengthAndRun(1); + checkNumberOfInferredProjects(projectService, 1); + checkNumberOfConfiguredProjects(projectService, 1); + checkWatchedFiles(host, watchedFiles); - // remove the tsconfig file - host.reloadFS(filesWithoutConfig); + // remove the tsconfig file + host.reloadFS(filesWithoutConfig); - checkNumberOfInferredProjects(projectService, 1); - host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects + checkNumberOfInferredProjects(projectService, 1); + host.checkTimeoutQueueLengthAndRun(1); // Refresh inferred projects - checkNumberOfInferredProjects(projectService, 2); - checkNumberOfConfiguredProjects(projectService, 0); - checkWatchedFiles(host, watchedFiles); - }); + checkNumberOfInferredProjects(projectService, 2); + checkNumberOfConfiguredProjects(projectService, 0); + checkWatchedFiles(host, watchedFiles); + }); - it("add new files to a configured project without file list", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const host = createServerHost([commonFile1, libFile, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - const configFileDir = getDirectoryPath(configFile.path); - checkWatchedDirectories(host, [configFileDir, combinePaths(configFileDir, nodeModulesAtTypes)], /*recursive*/ true); - checkNumberOfConfiguredProjects(projectService, 1); + it("add new files to a configured project without file list", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([commonFile1, libFile, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + const configFileDir = getDirectoryPath(configFile.path); + checkWatchedDirectories(host, [configFileDir, combinePaths(configFileDir, nodeModulesAtTypes)], /*recursive*/ true); + checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path]); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path]); - // add a new ts file - host.reloadFS([commonFile1, commonFile2, libFile, configFile]); - host.checkTimeoutQueueLengthAndRun(2); - // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - }); + // add a new ts file + host.reloadFS([commonFile1, commonFile2, libFile, configFile]); + host.checkTimeoutQueueLengthAndRun(2); + // project service waits for 250ms to update the project structure, therefore the assertion needs to wait longer. + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + }); - it("should ignore non-existing files specified in the config file", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("should ignore non-existing files specified in the config file", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "files": [ "commonFile1.ts", "commonFile3.ts" ] }` - }; - const host = createServerHost([commonFile1, commonFile2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - projectService.openClientFile(commonFile2.path); + }; + const host = createServerHost([commonFile1, commonFile2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + projectService.openClientFile(commonFile2.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path]); - checkNumberOfInferredProjects(projectService, 1); - }); - - it("remove not-listed external projects", () => { - const f1 = { - path: "/a/app.ts", - content: "let x = 1" - }; - const f2 = { - path: "/b/app.ts", - content: "let x = 1" - }; - const f3 = { - path: "/c/app.ts", - content: "let x = 1" - }; - const makeProject = (f: FileOrFolder) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} }); - const p1 = makeProject(f1); - const p2 = makeProject(f2); - const p3 = makeProject(f3); - - const host = createServerHost([f1, f2, f3]); - const session = createSession(host); - - session.executeCommand({ - seq: 1, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p1, p2] } + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path]); + checkNumberOfInferredProjects(projectService, 1); }); - const projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { externalProjects: 2 }); - assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); - assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName); + it("remove not-listed external projects", () => { + const f1 = { + path: "/a/app.ts", + content: "let x = 1" + }; + const f2 = { + path: "/b/app.ts", + content: "let x = 1" + }; + const f3 = { + path: "/c/app.ts", + content: "let x = 1" + }; + const makeProject = (f: FileOrFolder) => ({ projectFileName: f.path + ".csproj", rootFiles: [toExternalFile(f.path)], options: {} }); + const p1 = makeProject(f1); + const p2 = makeProject(f2); + const p3 = makeProject(f3); - session.executeCommand({ - seq: 2, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p1, p3] } + const host = createServerHost([f1, f2, f3]); + const session = createSession(host); + + session.executeCommand({ + seq: 1, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1, p2] } + }); + + const projectService = session.getProjectService(); + checkNumberOfProjects(projectService, { externalProjects: 2 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + assert.equal(projectService.externalProjects[1].getProjectName(), p2.projectFileName); + + session.executeCommand({ + seq: 2, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p1, p3] } + }); + checkNumberOfProjects(projectService, { externalProjects: 2 }); + assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); + assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName); + + session.executeCommand({ + seq: 3, + type: "request", + command: "openExternalProjects", + arguments: { projects: [] } + }); + checkNumberOfProjects(projectService, { externalProjects: 0 }); + + session.executeCommand({ + seq: 3, + type: "request", + command: "openExternalProjects", + arguments: { projects: [p2] } + }); + assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName); }); - checkNumberOfProjects(projectService, { externalProjects: 2 }); - assert.equal(projectService.externalProjects[0].getProjectName(), p1.projectFileName); - assert.equal(projectService.externalProjects[1].getProjectName(), p3.projectFileName); - session.executeCommand({ - seq: 3, - type: "request", - command: "openExternalProjects", - arguments: { projects: [] } + it("handle recreated files correctly", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{}` + }; + const host = createServerHost([commonFile1, commonFile2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + + // delete commonFile2 + host.reloadFS([commonFile1, configFile]); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(project, [commonFile1.path]); + + // re-add commonFile2 + host.reloadFS([commonFile1, commonFile2, configFile]); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); }); - checkNumberOfProjects(projectService, { externalProjects: 0 }); - session.executeCommand({ - seq: 3, - type: "request", - command: "openExternalProjects", - arguments: { projects: [p2] } - }); - assert.equal(projectService.externalProjects[0].getProjectName(), p2.projectFileName); - }); - - it("handle recreated files correctly", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{}` - }; - const host = createServerHost([commonFile1, commonFile2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - - // delete commonFile2 - host.reloadFS([commonFile1, configFile]); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(project, [commonFile1.path]); - - // re-add commonFile2 - host.reloadFS([commonFile1, commonFile2, configFile]); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - }); - - it("handles the missing files - that were added to program because they were added with /// { - const file1: FileOrFolder = { - path: "/a/b/commonFile1.ts", - content: `/// + it("handles the missing files - that were added to program because they were added with /// { + const file1: FileOrFolder = { + path: "/a/b/commonFile1.ts", + content: `/// let x = y` - }; - const host = createServerHost([file1, libFile]); - const session = createSession(host); - openFilesForSession([file1], session); - const projectService = session.getProjectService(); + }; + const host = createServerHost([file1, libFile]); + const session = createSession(host); + openFilesForSession([file1], session); + const projectService = session.getProjectService(); - checkNumberOfInferredProjects(projectService, 1); - const project = projectService.inferredProjects[0]; - checkProjectRootFiles(project, [file1.path]); - checkProjectActualFiles(project, [file1.path, libFile.path]); - const getErrRequest = makeSessionRequest( - server.CommandNames.SemanticDiagnosticsSync, - { file: file1.path } - ); + checkNumberOfInferredProjects(projectService, 1); + const project = projectService.inferredProjects[0]; + checkProjectRootFiles(project, [file1.path]); + checkProjectActualFiles(project, [file1.path, libFile.path]); + const getErrRequest = makeSessionRequest( + server.CommandNames.SemanticDiagnosticsSync, + { file: file1.path } + ); - // Two errors: CommonFile2 not found and cannot find name y - let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyDiagnostics(diags, [ - { diagnosticMessage: Diagnostics.Cannot_find_name_0, errorTextArguments: ["y"] }, - { diagnosticMessage: Diagnostics.File_0_not_found, errorTextArguments: [commonFile2.path] } - ]); + // Two errors: CommonFile2 not found and cannot find name y + let diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyDiagnostics(diags, [ + { diagnosticMessage: Diagnostics.Cannot_find_name_0, errorTextArguments: ["y"] }, + { diagnosticMessage: Diagnostics.File_0_not_found, errorTextArguments: [commonFile2.path] } + ]); - host.reloadFS([file1, commonFile2, libFile]); - host.runQueuedTimeoutCallbacks(); - checkNumberOfInferredProjects(projectService, 1); - assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same"); - checkProjectRootFiles(project, [file1.path]); - checkProjectActualFiles(project, [file1.path, libFile.path, commonFile2.path]); - diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; - verifyNoDiagnostics(diags); - }); + host.reloadFS([file1, commonFile2, libFile]); + host.runQueuedTimeoutCallbacks(); + checkNumberOfInferredProjects(projectService, 1); + assert.strictEqual(projectService.inferredProjects[0], project, "Inferred project should be same"); + checkProjectRootFiles(project, [file1.path]); + checkProjectActualFiles(project, [file1.path, libFile.path, commonFile2.path]); + diags = session.executeCommand(getErrRequest).response as server.protocol.Diagnostic[]; + verifyNoDiagnostics(diags); + }); - it("should create new inferred projects for files excluded from a configured project", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("should create new inferred projects for files excluded from a configured project", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "files": ["${commonFile1.path}", "${commonFile2.path}"] }` - }; - const files = [commonFile1, commonFile2, configFile]; - const host = createServerHost(files); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); + }; + const files = [commonFile1, commonFile2, configFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - configFile.content = `{ + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + configFile.content = `{ "compilerOptions": {}, "files": ["${commonFile1.path}"] }`; - host.reloadFS(files); + host.reloadFS(files); - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(project, [commonFile1.path]); + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + host.checkTimeoutQueueLengthAndRun(2); // Update the configured project + refresh inferred projects + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(project, [commonFile1.path]); - projectService.openClientFile(commonFile2.path); - checkNumberOfInferredProjects(projectService, 1); - }); + projectService.openClientFile(commonFile2.path); + checkNumberOfInferredProjects(projectService, 1); + }); - it("files explicitly excluded in config file", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("files explicitly excluded in config file", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": {}, "exclude": ["/a/c"] }` - }; - const excludedFile1: FileOrFolder = { - path: "/a/c/excluedFile1.ts", - content: `let t = 1;` - }; + }; + const excludedFile1: FileOrFolder = { + path: "/a/c/excluedFile1.ts", + content: `let t = 1;` + }; - const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]); - const projectService = createProjectService(host); + const host = createServerHost([commonFile1, commonFile2, excludedFile1, configFile]); + const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); - projectService.openClientFile(excludedFile1.path); - checkNumberOfInferredProjects(projectService, 1); - }); + projectService.openClientFile(commonFile1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectRootFiles(project, [commonFile1.path, commonFile2.path]); + projectService.openClientFile(excludedFile1.path); + checkNumberOfInferredProjects(projectService, 1); + }); - it("should properly handle module resolution changes in config file", () => { - const file1: FileOrFolder = { - path: "/a/b/file1.ts", - content: `import { T } from "module1";` - }; - const nodeModuleFile: FileOrFolder = { - path: "/a/b/node_modules/module1.ts", - content: `export interface T {}` - }; - const classicModuleFile: FileOrFolder = { - path: "/a/module1.ts", - content: `export interface T {}` - }; - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("should properly handle module resolution changes in config file", () => { + const file1: FileOrFolder = { + path: "/a/b/file1.ts", + content: `import { T } from "module1";` + }; + const nodeModuleFile: FileOrFolder = { + path: "/a/b/node_modules/module1.ts", + content: `export interface T {}` + }; + const classicModuleFile: FileOrFolder = { + path: "/a/module1.ts", + content: `export interface T {}` + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "moduleResolution": "node" }, "files": ["${file1.path}"] }` - }; - const files = [file1, nodeModuleFile, classicModuleFile, configFile]; - const host = createServerHost(files); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.openClientFile(nodeModuleFile.path); - projectService.openClientFile(classicModuleFile.path); + }; + const files = [file1, nodeModuleFile, classicModuleFile, configFile]; + const host = createServerHost(files); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.openClientFile(nodeModuleFile.path); + projectService.openClientFile(classicModuleFile.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = configuredProjectAt(projectService, 0); - checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); - checkNumberOfInferredProjects(projectService, 1); + checkNumberOfConfiguredProjects(projectService, 1); + const project = configuredProjectAt(projectService, 0); + checkProjectActualFiles(project, [file1.path, nodeModuleFile.path, configFile.path]); + checkNumberOfInferredProjects(projectService, 1); - configFile.content = `{ + configFile.content = `{ "compilerOptions": { "moduleResolution": "classic" }, "files": ["${file1.path}"] }`; - host.reloadFS(files); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); - checkNumberOfInferredProjects(projectService, 1); - }); + host.reloadFS(files); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectActualFiles(project, [file1.path, classicModuleFile.path, configFile.path]); + checkNumberOfInferredProjects(projectService, 1); + }); - it("should keep the configured project when the opened file is referenced by the project but not its root", () => { - const file1: FileOrFolder = { - path: "/a/b/main.ts", - content: "import { objA } from './obj-a';" - }; - const file2: FileOrFolder = { - path: "/a/b/obj-a.ts", - content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` - }; - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("should keep the configured project when the opened file is referenced by the project but not its root", () => { + const file1: FileOrFolder = { + path: "/a/b/main.ts", + content: "import { objA } from './obj-a';" + }; + const file2: FileOrFolder = { + path: "/a/b/obj-a.ts", + content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.closeClientFile(file1.path); - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); - }); + }; + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + }); - it("should keep the configured project when the opened file is referenced by the project but not its root", () => { - const file1: FileOrFolder = { - path: "/a/b/main.ts", - content: "import { objA } from './obj-a';" - }; - const file2: FileOrFolder = { - path: "/a/b/obj-a.ts", - content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` - }; - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("should keep the configured project when the opened file is referenced by the project but not its root", () => { + const file1: FileOrFolder = { + path: "/a/b/main.ts", + content: "import { objA } from './obj-a';" + }; + const file2: FileOrFolder = { + path: "/a/b/obj-a.ts", + content: `export const objA = Object.assign({foo: "bar"}, {bar: "baz"});` + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.closeClientFile(file1.path); - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); - }); - it("should tolerate config file errors and still try to build a project", () => { - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + }; + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); + }); + it("should tolerate config file errors and still try to build a project", () => { + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6", "allowAnything": true }, "someOtherProperty": {} }` - }; - const host = createServerHost([commonFile1, commonFile2, libFile, configFile]); - const projectService = createProjectService(host); - projectService.openClientFile(commonFile1.path); - checkNumberOfConfiguredProjects(projectService, 1); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]); - }); + }; + const host = createServerHost([commonFile1, commonFile2, libFile, configFile]); + const projectService = createProjectService(host); + projectService.openClientFile(commonFile1.path); + checkNumberOfConfiguredProjects(projectService, 1); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [commonFile1.path, commonFile2.path]); + }); - it("should disable features when the files are too large", () => { - const file1 = { - path: "/a/b/f1.js", - content: "let x =1;", - fileSize: 10 * 1024 * 1024 - }; - const file2 = { - path: "/a/b/f2.js", - content: "let y =1;", - fileSize: 6 * 1024 * 1024 - }; - const file3 = { - path: "/a/b/f3.js", - content: "let y =1;", - fileSize: 6 * 1024 * 1024 - }; + it("should disable features when the files are too large", () => { + const file1 = { + path: "/a/b/f1.js", + content: "let x =1;", + fileSize: 10 * 1024 * 1024 + }; + const file2 = { + path: "/a/b/f2.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; + const file3 = { + path: "/a/b/f3.js", + content: "let y =1;", + fileSize: 6 * 1024 * 1024 + }; - const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; + const proj1name = "proj1", proj2name = "proj2", proj3name = "proj3"; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); - projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); - const proj1 = projectService.findProject(proj1name); - assert.isTrue(proj1.languageServiceEnabled); + projectService.openExternalProject({ rootFiles: toExternalFiles([file1.path]), options: {}, projectFileName: proj1name }); + const proj1 = projectService.findProject(proj1name); + assert.isTrue(proj1.languageServiceEnabled); - projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); - const proj2 = projectService.findProject(proj2name); - assert.isTrue(proj2.languageServiceEnabled); + projectService.openExternalProject({ rootFiles: toExternalFiles([file2.path]), options: {}, projectFileName: proj2name }); + const proj2 = projectService.findProject(proj2name); + assert.isTrue(proj2.languageServiceEnabled); - projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); - const proj3 = projectService.findProject(proj3name); - assert.isFalse(proj3.languageServiceEnabled); - }); + projectService.openExternalProject({ rootFiles: toExternalFiles([file3.path]), options: {}, projectFileName: proj3name }); + const proj3 = projectService.findProject(proj3name); + assert.isFalse(proj3.languageServiceEnabled); + }); - 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: `{ + 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 file2 = { + path: "/a/c/main.ts", + content: "let x =1;" + }; - const file3 = { - path: "/a/d/main.ts", - content: "let x =1;" - }; + const file3 = { + path: "/a/d/main.ts", + content: "let x =1;" + }; - const host = createServerHost([file1, file2, file3, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); - projectService.openClientFile(file3.path); + const host = createServerHost([file1, file2, file3, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: 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]); + 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.checkTimeoutQueueLengthAndRun(1); - checkNumberOfConfiguredProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 1); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); - }); + host.reloadFS([file1, configFile, file2, file3, libFile]); + host.checkTimeoutQueueLengthAndRun(1); + checkNumberOfConfiguredProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path, file3.path, libFile.path]); + }); - it("should reuse same project if file is opened from the configured project that has no open files", () => { - const file1 = { - path: "/a/b/main.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/b/main2.ts", - content: "let y =1;" - }; - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("should reuse same project if file is opened from the configured project that has no open files", () => { + const file1 = { + path: "/a/b/main.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/main2.ts", + content: "let y =1;" + }; + const configFile: FileOrFolder = { + path: "/a/b/tsconfig.json", + content: `{ "compilerOptions": { "target": "es6" }, "files": [ "main.ts", "main2.ts" ] }` - }; - const host = createServerHost([file1, file2, configFile, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects.get(configFile.path); - assert.isTrue(project.hasOpenRef()); // file1 + }; + const host = createServerHost([file1, file2, configFile, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path); + assert.isTrue(project.hasOpenRef()); // file1 - projectService.closeClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isFalse(project.hasOpenRef()); // No open files - assert.isFalse(project.isClosed()); + projectService.closeClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isFalse(project.hasOpenRef()); // No open files + assert.isFalse(project.isClosed()); - projectService.openClientFile(file2.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isTrue(project.hasOpenRef()); // file2 - assert.isFalse(project.isClosed()); - }); + projectService.openClientFile(file2.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isTrue(project.hasOpenRef()); // file2 + assert.isFalse(project.isClosed()); + }); - it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { - const file1 = { - path: "/a/b/main.ts", - content: "let x =1;" - }; - const configFile: FileOrFolder = { - path: "/a/b/tsconfig.json", - content: `{ + it("should not close configured project after closing last open file, but should be closed on next file open if its not the file from same project", () => { + 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 host = createServerHost([file1, configFile, libFile]); - const projectService = createProjectService(host, { useSingleInferredProject: true }); - projectService.openClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - const project = projectService.configuredProjects.get(configFile.path); - assert.isTrue(project.hasOpenRef()); // file1 + }; + const host = createServerHost([file1, configFile, libFile]); + const projectService = createProjectService(host, { useSingleInferredProject: true }); + projectService.openClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + const project = projectService.configuredProjects.get(configFile.path); + assert.isTrue(project.hasOpenRef()); // file1 - projectService.closeClientFile(file1.path); - checkNumberOfConfiguredProjects(projectService, 1); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - assert.isFalse(project.hasOpenRef()); // No files - assert.isFalse(project.isClosed()); + projectService.closeClientFile(file1.path); + checkNumberOfConfiguredProjects(projectService, 1); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + assert.isFalse(project.hasOpenRef()); // No files + assert.isFalse(project.isClosed()); - projectService.openClientFile(libFile.path); - checkNumberOfConfiguredProjects(projectService, 0); - assert.isFalse(project.hasOpenRef()); // No files + project closed - assert.isTrue(project.isClosed()); - }); - - it("should not close external project with no open files", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y =1;" - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - rootFiles: toExternalFiles([file1.path, file2.path]), - options: {}, - projectFileName: externalProjectName + projectService.openClientFile(libFile.path); + checkNumberOfConfiguredProjects(projectService, 0); + assert.isFalse(project.hasOpenRef()); // No files + project closed + assert.isTrue(project.isClosed()); }); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + it("should not close external project with no open files", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y =1;" + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path, file2.path]), + options: {}, + projectFileName: externalProjectName + }); - // open client file - should not lead to creation of inferred project - projectService.openClientFile(file1.path, file1.content); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); - // close client file - external project should still exists - projectService.closeClientFile(file1.path); - checkNumberOfExternalProjects(projectService, 1); - checkNumberOfInferredProjects(projectService, 0); + // open client file - should not lead to creation of inferred project + projectService.openClientFile(file1.path, file1.content); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); - projectService.closeExternalProject(externalProjectName); - checkNumberOfExternalProjects(projectService, 0); - checkNumberOfInferredProjects(projectService, 0); - }); + // close client file - external project should still exists + projectService.closeClientFile(file1.path); + checkNumberOfExternalProjects(projectService, 1); + checkNumberOfInferredProjects(projectService, 0); - it("external project that included config files", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x =1;" - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f1.ts"] - } - ) - }; - const file2 = { - path: "/a/c/f2.ts", - content: "let y =1;" - }; - const config2 = { - path: "/a/c/tsconfig.json", - content: JSON.stringify( - { - compilerOptions: {}, - files: ["f2.ts"] - } - ) - }; - const file3 = { - path: "/a/d/f3.ts", - content: "let z =1;" - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2, file3, config1, config2]); - const projectService = createProjectService(host); - projectService.openExternalProject({ - rootFiles: toExternalFiles([config1.path, config2.path, file3.path]), - options: {}, - projectFileName: externalProjectName + projectService.closeExternalProject(externalProjectName); + checkNumberOfExternalProjects(projectService, 0); + checkNumberOfInferredProjects(projectService, 0); }); - checkNumberOfProjects(projectService, { configuredProjects: 2 }); - const proj1 = projectService.configuredProjects.get(config1.path); - const proj2 = projectService.configuredProjects.get(config2.path); - assert.isDefined(proj1); - assert.isDefined(proj2); + it("external project that included config files", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x =1;" + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f1.ts"] + } + ) + }; + const file2 = { + path: "/a/c/f2.ts", + content: "let y =1;" + }; + const config2 = { + path: "/a/c/tsconfig.json", + content: JSON.stringify( + { + compilerOptions: {}, + files: ["f2.ts"] + } + ) + }; + const file3 = { + path: "/a/d/f3.ts", + content: "let z =1;" + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2, file3, config1, config2]); + const projectService = createProjectService(host); + projectService.openExternalProject({ + rootFiles: toExternalFiles([config1.path, config2.path, file3.path]), + options: {}, + projectFileName: externalProjectName + }); - // open client file - should not lead to creation of inferred project - projectService.openClientFile(file1.path, file1.content); - checkNumberOfProjects(projectService, { configuredProjects: 2 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + const proj1 = projectService.configuredProjects.get(config1.path); + const proj2 = projectService.configuredProjects.get(config2.path); + assert.isDefined(proj1); + assert.isDefined(proj2); - projectService.openClientFile(file3.path, file3.content); - checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); + // open client file - should not lead to creation of inferred project + projectService.openClientFile(file1.path, file1.content); + checkNumberOfProjects(projectService, { configuredProjects: 2 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); - projectService.closeExternalProject(externalProjectName); - // open file 'file1' from configured project keeps project alive - checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); + projectService.openClientFile(file3.path, file3.content); + checkNumberOfProjects(projectService, { configuredProjects: 2, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.strictEqual(projectService.configuredProjects.get(config2.path), proj2); - projectService.closeClientFile(file3.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); + projectService.closeExternalProject(externalProjectName); + // open file 'file1' from configured project keeps project alive + checkNumberOfProjects(projectService, { configuredProjects: 1, inferredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); - assert.isUndefined(projectService.configuredProjects.get(config2.path)); + projectService.closeClientFile(file3.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); - projectService.openClientFile(file2.path, file2.content); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(config1.path)); - assert.isDefined(projectService.configuredProjects.get(config2.path)); + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(config1.path), proj1); + assert.isUndefined(projectService.configuredProjects.get(config2.path)); - }); + projectService.openClientFile(file2.path, file2.content); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config1.path)); + assert.isDefined(projectService.configuredProjects.get(config2.path)); - it("reload regular file after closing", () => { - const f1 = { - path: "/a/b/app.ts", - content: "x." - }; - const f2 = { - path: "/a/b/lib.ts", - content: "let x: number;" - }; - - const host = createServerHost([f1, f2, libFile]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); - - service.openClientFile(f1.path); - service.openClientFile(f2.path, "let x: string"); - - service.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); - - const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false }); - // should contain completions for string - assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); - assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'"); - - service.closeClientFile(f2.path); - const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false }); - // should contain completions for string - assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'"); - assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); - }); - - it("clear mixed content file after closing", () => { - const f1 = { - path: "/a/b/app.ts", - content: " " - }; - const f2 = { - path: "/a/b/lib.html", - content: "" - }; - - const host = createServerHost([f1, f2, libFile]); - const service = createProjectService(host); - service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }); - - service.openClientFile(f1.path); - service.openClientFile(f2.path, "let somelongname: string"); - - service.checkNumberOfProjects({ externalProjects: 1 }); - checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); - - const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false }); - assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); - - service.closeClientFile(f2.path); - const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false }); - assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'"); - const sf2 = service.externalProjects[0].getLanguageService().getProgram().getSourceFile(f2.path); - assert.equal(sf2.text, ""); - }); - - - it("external project with included config file opened after configured project", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - - projectService.openExternalProject({ - rootFiles: toExternalFiles([configFile.path]), - options: {}, - projectFileName: externalProjectName }); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + it("reload regular file after closing", () => { + const f1 = { + path: "/a/b/app.ts", + content: "x." + }; + const f2 = { + path: "/a/b/lib.ts", + content: "let x: number;" + }; - projectService.closeClientFile(file1.path); - // configured project is alive since it is opened as part of external project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const host = createServerHost([f1, f2, libFile]); + const service = createProjectService(host); + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: toExternalFiles([f1.path, f2.path]), options: {} }); - projectService.closeExternalProject(externalProjectName); - checkNumberOfProjects(projectService, { configuredProjects: 0 }); - }); + service.openClientFile(f1.path); + service.openClientFile(f2.path, "let x: string"); - it("external project with included config file opened after configured project and then closed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/f2.ts", - content: "let x = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const externalProjectName = "externalproject"; - const host = createServerHost([file1, file2, libFile, configFile]); - const projectService = createProjectService(host); + service.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const project = projectService.configuredProjects.get(configFile.path); + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false }); + // should contain completions for string + assert.isTrue(completions1.entries.some(e => e.name === "charAt"), "should contain 'charAt'"); + assert.isFalse(completions1.entries.some(e => e.name === "toExponential"), "should not contain 'toExponential'"); - projectService.openExternalProject({ - rootFiles: toExternalFiles([configFile.path]), - options: {}, - projectFileName: externalProjectName + service.closeClientFile(f2.path); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 2, { includeExternalModuleExports: false }); + // should contain completions for string + assert.isFalse(completions2.entries.some(e => e.name === "charAt"), "should not contain 'charAt'"); + assert.isTrue(completions2.entries.some(e => e.name === "toExponential"), "should contain 'toExponential'"); }); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + it("clear mixed content file after closing", () => { + const f1 = { + path: "/a/b/app.ts", + content: " " + }; + const f2 = { + path: "/a/b/lib.html", + content: "" + }; - projectService.closeExternalProject(externalProjectName); - // configured project is alive since file is still open - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + const host = createServerHost([f1, f2, libFile]); + const service = createProjectService(host); + service.openExternalProject({ projectFileName: "/a/b/project", rootFiles: [{ fileName: f1.path }, { fileName: f2.path, hasMixedContent: true }], options: {} }); - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); + service.openClientFile(f1.path); + service.openClientFile(f2.path, "let somelongname: string"); - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(configFile.path)); - }); + service.checkNumberOfProjects({ externalProjects: 1 }); + checkProjectActualFiles(service.externalProjects[0], [f1.path, f2.path, libFile.path]); - it("changes in closed files are reflected in project structure", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export let x = 1` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + const completions1 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false }); + assert.isTrue(completions1.entries.some(e => e.name === "somelongname"), "should contain 'somelongname'"); - projectService.openClientFile(file1.path); + service.closeClientFile(f2.path); + const completions2 = service.externalProjects[0].getLanguageService().getCompletionsAtPosition(f1.path, 0, { includeExternalModuleExports: false }); + assert.isFalse(completions2.entries.some(e => e.name === "somelongname"), "should not contain 'somelongname'"); + const sf2 = service.externalProjects[0].getLanguageService().getProgram().getSourceFile(f2.path); + assert.equal(sf2.text, ""); + }); - checkNumberOfInferredProjects(projectService, 1); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); - projectService.openClientFile(file3.path); - checkNumberOfInferredProjects(projectService, 2); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + it("external project with included config file opened after configured project", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, configFile]); + const projectService = createProjectService(host); - const modifiedFile2 = { - path: file2.path, - content: `export * from "../c/f3"` // now inferred project should inclule file3 - }; + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - host.reloadFS([file1, modifiedFile2, file3]); - host.checkTimeoutQueueLengthAndRun(2); - checkNumberOfInferredProjects(projectService, 1); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]); - }); + projectService.openExternalProject({ + rootFiles: toExternalFiles([configFile.path]), + options: {}, + projectFileName: externalProjectName + }); - it("deleted files affect project structure", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: `export * from "../c/f3"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: `export let y = 1;` - }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - projectService.openClientFile(file1.path); + projectService.closeClientFile(file1.path); + // configured project is alive since it is opened as part of external project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.closeExternalProject(externalProjectName); + checkNumberOfProjects(projectService, { configuredProjects: 0 }); + }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + it("external project with included config file opened after configured project and then closed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/f2.ts", + content: "let x = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const externalProjectName = "externalproject"; + const host = createServerHost([file1, file2, libFile, configFile]); + const projectService = createProjectService(host); - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const project = projectService.configuredProjects.get(configFile.path); - host.reloadFS([file1, file3]); - host.checkTimeoutQueueLengthAndRun(2); + projectService.openExternalProject({ + rootFiles: toExternalFiles([configFile.path]), + options: {}, + projectFileName: externalProjectName + }); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - }); + projectService.closeExternalProject(externalProjectName); + // configured project is alive since file is still open + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - it("ignores files excluded by a custom safe type list", () => { - const file1 = { - path: "/a/b/f1.js", - content: "export let x = 5" - }; - const office = { - path: "/lib/duckquack-3.min.js", - content: "whoa do @@ not parse me ok thanks!!!" - }; - const host = createServerHost([file1, office, customTypesMap]); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); - assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); - } finally { - projectService.resetSafeList(); - } - }); + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(projectService.configuredProjects.get(configFile.path), project); - it("ignores files excluded by the default type list", () => { - const file1 = { - path: "/a/b/f1.js", - content: "export let x = 5" - }; - const minFile = { - path: "/c/moment.min.js", - content: "unspecified" - }; - const kendoFile1 = { - path: "/q/lib/kendo/kendo.all.min.js", - content: "unspecified" - }; - const kendoFile2 = { - path: "/q/lib/kendo/kendo.ui.min.js", - content: "unspecified" - }; - const kendoFile3 = { - path: "/q/lib/kendo-ui/kendo.all.js", - content: "unspecified" - }; - const officeFile1 = { - path: "/scripts/Office/1/excel-15.debug.js", - content: "unspecified" - }; - const officeFile2 = { - path: "/scripts/Office/1/powerpoint.js", - content: "unspecified" - }; - const files = [file1, minFile, kendoFile1, kendoFile2, kendoFile3, officeFile1, officeFile2]; - const host = createServerHost(files); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); - assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); - } finally { - projectService.resetSafeList(); - } - }); + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(configFile.path)); + }); - it("removes version numbers correctly", () => { - const testData: [string, string][] = [ - ["jquery-max", "jquery-max"], - ["jquery.min", "jquery"], - ["jquery-min.4.2.3", "jquery"], - ["jquery.min.4.2.1", "jquery"], - ["minimum", "minimum"], - ["min", "min"], - ["min.3.2", "min"], - ["jquery", "jquery"] - ]; - for (const t of testData) { - assert.equal(removeMinAndVersionNumbers(t[0]), t[1], t[0]); - } - }); + it("changes in closed files are reflected in project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export let x = 1` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); - it("ignores files excluded by a legacy safe type list", () => { - const file1 = { - path: "/a/b/bliss.js", - content: "let x = 5" - }; - const file2 = { - path: "/a/b/foo.js", - content: "" - }; - const file3 = { - path: "/a/b/Bacon.js", - content: "let y = 5" - }; - const host = createServerHost([file1, file2, file3, customTypesMap]); - const projectService = createProjectService(host); - try { - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); - const proj = projectService.externalProjects[0]; - assert.deepEqual(proj.getFileNames(), [file2.path]); - } finally { - projectService.resetSafeList(); - } - }); + projectService.openClientFile(file1.path); - it("open file become a part of configured project if it is referenced from root file", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "export let x = 5" - }; - const file2 = { - path: "/a/c/f2.ts", - content: `import {x} from "../b/f1"` - }; - const file3 = { - path: "/a/c/f3.ts", - content: "export let y = 1" - }; - const configFile = { - path: "/a/c/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) - }; + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path]); - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + projectService.openClientFile(file3.path); + checkNumberOfInferredProjects(projectService, 2); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + const modifiedFile2 = { + path: file2.path, + content: `export * from "../c/f3"` // now inferred project should inclule file3 + }; - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + host.reloadFS([file1, modifiedFile2, file3]); + host.checkTimeoutQueueLengthAndRun(2); + checkNumberOfInferredProjects(projectService, 1); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, modifiedFile2.path, file3.path]); + }); - host.reloadFS([file1, file2, file3, configFile]); - host.checkTimeoutQueueLengthAndRun(1); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); - }); + it("deleted files affect project structure", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: `export * from "../c/f3"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: `export let y = 1;` + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); - it("correctly migrate files between projects", () => { - const file1 = { - path: "/a/b/f1.ts", - content: ` + projectService.openClientFile(file1.path); + + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + + host.reloadFS([file1, file3]); + host.checkTimeoutQueueLengthAndRun(2); + + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + }); + + it("ignores files excluded by a custom safe type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: "export let x = 5" + }; + const office = { + path: "/lib/duckquack-3.min.js", + content: "whoa do @@ not parse me ok thanks!!!" + }; + const host = createServerHost([file1, office, customTypesMap]); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, office.path]) }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["duck-types"]); + } finally { + projectService.resetSafeList(); + } + }); + + it("ignores files excluded by the default type list", () => { + const file1 = { + path: "/a/b/f1.js", + content: "export let x = 5" + }; + const minFile = { + path: "/c/moment.min.js", + content: "unspecified" + }; + const kendoFile1 = { + path: "/q/lib/kendo/kendo.all.min.js", + content: "unspecified" + }; + const kendoFile2 = { + path: "/q/lib/kendo/kendo.ui.min.js", + content: "unspecified" + }; + const kendoFile3 = { + path: "/q/lib/kendo-ui/kendo.all.js", + content: "unspecified" + }; + const officeFile1 = { + path: "/scripts/Office/1/excel-15.debug.js", + content: "unspecified" + }; + const officeFile2 = { + path: "/scripts/Office/1/powerpoint.js", + content: "unspecified" + }; + const files = [file1, minFile, kendoFile1, kendoFile2, kendoFile3, officeFile1, officeFile2]; + const host = createServerHost(files); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles(files.map(f => f.path)) }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(/*excludeFilesFromExternalLibraries*/ true), [file1.path]); + assert.deepEqual(proj.getTypeAcquisition().include, ["kendo-ui", "office"]); + } finally { + projectService.resetSafeList(); + } + }); + + it("removes version numbers correctly", () => { + const testData: [string, string][] = [ + ["jquery-max", "jquery-max"], + ["jquery.min", "jquery"], + ["jquery-min.4.2.3", "jquery"], + ["jquery.min.4.2.1", "jquery"], + ["minimum", "minimum"], + ["min", "min"], + ["min.3.2", "min"], + ["jquery", "jquery"] + ]; + for (const t of testData) { + assert.equal(removeMinAndVersionNumbers(t[0]), t[1], t[0]); + } + }); + + it("ignores files excluded by a legacy safe type list", () => { + const file1 = { + path: "/a/b/bliss.js", + content: "let x = 5" + }; + const file2 = { + path: "/a/b/foo.js", + content: "" + }; + const file3 = { + path: "/a/b/Bacon.js", + content: "let y = 5" + }; + const host = createServerHost([file1, file2, file3, customTypesMap]); + const projectService = createProjectService(host); + try { + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]), typeAcquisition: { enable: true } }); + const proj = projectService.externalProjects[0]; + assert.deepEqual(proj.getFileNames(), [file2.path]); + } finally { + projectService.resetSafeList(); + } + }); + + it("open file become a part of configured project if it is referenced from root file", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "export let x = 5" + }; + const file2 = { + path: "/a/c/f2.ts", + content: `import {x} from "../b/f1"` + }; + const file3 = { + path: "/a/c/f3.ts", + content: "export let y = 1" + }; + const configFile = { + path: "/a/c/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f2.ts", "f3.ts"] }) + }; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + + host.reloadFS([file1, file2, file3, configFile]); + host.checkTimeoutQueueLengthAndRun(1); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, file3.path, configFile.path]); + }); + + it("correctly migrate files between projects", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` export * from "../c/f2"; export * from "../d/f3";` - }; - const file2 = { - path: "/a/c/f2.ts", - content: "export let x = 1;" - }; - const file3 = { - path: "/a/d/f3.ts", - content: "export let y = 1;" - }; - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); + }; + const file2 = { + path: "/a/c/f2.ts", + content: "export let x = 1;" + }; + const file3 = { + path: "/a/d/f3.ts", + content: "export let y = 1;" + }; + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); - projectService.openClientFile(file3.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); + projectService.openClientFile(file3.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file2.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file3.path]); - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - checkProjectRootFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + checkProjectRootFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path, file2.path, file3.path]); - projectService.closeClientFile(file1.path); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - }); - - it("can correctly update configured project when set of root files has changed (new file on disk)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - - const host = createServerHost([file1, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); - - host.reloadFS([file1, file2, configFile]); - - host.checkTimeoutQueueLengthAndRun(2); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); - - it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) - }; - - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); - - const modifiedConfigFile = { - path: configFile.path, - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) - }; - - host.reloadFS([file1, file2, modifiedConfigFile]); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - host.checkTimeoutQueueLengthAndRun(2); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); - - it("can update configured project when set of root files was not changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const configFile = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) - }; - - const host = createServerHost([file1, file2, configFile]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); - - const modifiedConfigFile = { - path: configFile.path, - content: JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] }) - }; - - host.reloadFS([file1, file2, modifiedConfigFile]); - - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); - }); - - it("can correctly update external project when set of root files has changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 1" - }; - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host); - - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); - - projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - }); - - it("can update external project when set of root files was not changed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `export * from "m"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: "export let y = 1" - }; - const file3 = { - path: "/a/m.ts", - content: "export let y = 1" - }; - - const host = createServerHost([file1, file2, file3]); - const projectService = createProjectService(host); - - projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); - - projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); - checkNumberOfProjects(projectService, { externalProjects: 1 }); - checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); - checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); - }); - - it("regression test for crash in acquireOrUpdateDocument", () => { - const tsFile = { - fileName: "/a/b/file1.ts", - path: "/a/b/file1.ts", - content: "" - }; - const jsFile = { - path: "/a/b/file1.js", - content: "var x = 10;", - fileName: "/a/b/file1.js", - scriptKind: "JS" as "JS" - }; - - const host = createServerHost([]); - const projectService = createProjectService(host); - projectService.applyChangesInOpenFiles([tsFile], [], []); - const projs = projectService.synchronizeProjectList([]); - projectService.findProject(projs[0].info.projectName).getLanguageService().getNavigationBarItems(tsFile.fileName); - projectService.synchronizeProjectList([projs[0].info]); - projectService.applyChangesInOpenFiles([jsFile], [], []); - }); - - it("config file is deleted", () => { - const file1 = { - path: "/a/b/f1.ts", - content: "let x = 1;" - }; - const file2 = { - path: "/a/b/f2.ts", - content: "let y = 2;" - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: {} }) - }; - const host = createServerHost([file1, file2, config]); - const projectService = createProjectService(host); - - projectService.openClientFile(file1.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - - projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - - host.reloadFS([file1, file2]); - host.checkTimeoutQueueLengthAndRun(1); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); - checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); - }); - - it("loading files with correct priority", () => { - const f1 = { - path: "/a/main.ts", - content: "let x = 1" - }; - const f2 = { - path: "/a/main.js", - content: "var y = 1" - }; - const config = { - path: "/a/tsconfig.json", - content: JSON.stringify({ - compilerOptions: { allowJs: true } - }) - }; - const host = createServerHost([f1, f2, config]); - const projectService = createProjectService(host); - projectService.setHostConfiguration({ - extraFileExtensions: [ - { extension: ".js", isMixedContent: false }, - { extension: ".html", isMixedContent: true } - ] + projectService.closeClientFile(file1.path); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); }); - projectService.openClientFile(f1.path); - projectService.checkNumberOfProjects({ configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); - // Should close configured project with next file open - projectService.closeClientFile(f1.path); + it("can correctly update configured project when set of root files has changed (new file on disk)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; - projectService.openClientFile(f2.path); - projectService.checkNumberOfProjects({ inferredProjects: 1 }); - assert.isUndefined(projectService.configuredProjects.get(config.path)); - checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); - }); + const host = createServerHost([file1, configFile]); + const projectService = createProjectService(host); - it("tsconfig script block support", () => { - const file1 = { - path: "/a/b/f1.ts", - content: ` ` - }; - const file2 = { - path: "/a/b/f2.html", - content: `var hello = "hello";` - }; - const config = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true } }) - }; - const host = createServerHost([file1, file2, config]); - const session = createSession(host); - openFilesForSession([file1], session); - const projectService = session.getProjectService(); + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); - // HTML file will not be included in any projects yet - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - const configuredProj = configuredProjectAt(projectService, 0); - checkProjectActualFiles(configuredProj, [file1.path, config.path]); + host.reloadFS([file1, file2, configFile]); - // Specify .html extension as mixed content - const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; - const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); - session.executeCommand(configureHostRequest); + host.checkTimeoutQueueLengthAndRun(2); - // The configured project should now be updated to include html file - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); - // Open HTML file - projectService.applyChangesInOpenFiles( + it("can correctly update configured project when set of root files has changed (new file in list of files)", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts"] }) + }; + + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, configFile.path]); + + const modifiedConfigFile = { + path: configFile.path, + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + + host.reloadFS([file1, file2, modifiedConfigFile]); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + host.checkTimeoutQueueLengthAndRun(2); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); + + it("can update configured project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const configFile = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {}, files: ["f1.ts", "f2.ts"] }) + }; + + const host = createServerHost([file1, file2, configFile]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, configFile.path]); + + const modifiedConfigFile = { + path: configFile.path, + content: JSON.stringify({ compilerOptions: { outFile: "out.js" }, files: ["f1.ts", "f2.ts"] }) + }; + + host.reloadFS([file1, file2, modifiedConfigFile]); + + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectRootFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path]); + }); + + it("can correctly update external project when set of root files has changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 1" + }; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path]); + + projectService.openExternalProject({ projectFileName: "project", options: {}, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + }); + + it("can update external project when set of root files was not changed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `export * from "m"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: "export let y = 1" + }; + const file3 = { + path: "/a/m.ts", + content: "export let y = 1" + }; + + const host = createServerHost([file1, file2, file3]); + const projectService = createProjectService(host); + + projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.NodeJs }, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path]); + + projectService.openExternalProject({ projectFileName: "project", options: { moduleResolution: ModuleResolutionKind.Classic }, rootFiles: toExternalFiles([file1.path, file2.path]) }); + checkNumberOfProjects(projectService, { externalProjects: 1 }); + checkProjectRootFiles(projectService.externalProjects[0], [file1.path, file2.path]); + checkProjectActualFiles(projectService.externalProjects[0], [file1.path, file2.path, file3.path]); + }); + + it("regression test for crash in acquireOrUpdateDocument", () => { + const tsFile = { + fileName: "/a/b/file1.ts", + path: "/a/b/file1.ts", + content: "" + }; + const jsFile = { + path: "/a/b/file1.js", + content: "var x = 10;", + fileName: "/a/b/file1.js", + scriptKind: "JS" as "JS" + }; + + const host = createServerHost([]); + const projectService = createProjectService(host); + projectService.applyChangesInOpenFiles([tsFile], [], []); + const projs = projectService.synchronizeProjectList([]); + projectService.findProject(projs[0].info.projectName).getLanguageService().getNavigationBarItems(tsFile.fileName); + projectService.synchronizeProjectList([projs[0].info]); + projectService.applyChangesInOpenFiles([jsFile], [], []); + }); + + it("config file is deleted", () => { + const file1 = { + path: "/a/b/f1.ts", + content: "let x = 1;" + }; + const file2 = { + path: "/a/b/f2.ts", + content: "let y = 2;" + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: {} }) + }; + const host = createServerHost([file1, file2, config]); + const projectService = createProjectService(host); + + projectService.openClientFile(file1.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + projectService.openClientFile(file2.path); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + host.reloadFS([file1, file2]); + host.checkTimeoutQueueLengthAndRun(1); + checkNumberOfProjects(projectService, { inferredProjects: 2 }); + checkProjectActualFiles(projectService.inferredProjects[0], [file1.path]); + checkProjectActualFiles(projectService.inferredProjects[1], [file2.path]); + }); + + it("loading files with correct priority", () => { + const f1 = { + path: "/a/main.ts", + content: "let x = 1" + }; + const f2 = { + path: "/a/main.js", + content: "var y = 1" + }; + const config = { + path: "/a/tsconfig.json", + content: JSON.stringify({ + compilerOptions: { allowJs: true } + }) + }; + const host = createServerHost([f1, f2, config]); + const projectService = createProjectService(host); + projectService.setHostConfiguration({ + extraFileExtensions: [ + { extension: ".js", isMixedContent: false }, + { extension: ".html", isMixedContent: true } + ] + }); + projectService.openClientFile(f1.path); + projectService.checkNumberOfProjects({ configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [f1.path, config.path]); + + // Should close configured project with next file open + projectService.closeClientFile(f1.path); + + projectService.openClientFile(f2.path); + projectService.checkNumberOfProjects({ inferredProjects: 1 }); + assert.isUndefined(projectService.configuredProjects.get(config.path)); + checkProjectActualFiles(projectService.inferredProjects[0], [f2.path]); + }); + + it("tsconfig script block support", () => { + const file1 = { + path: "/a/b/f1.ts", + content: ` ` + }; + const file2 = { + path: "/a/b/f2.html", + content: `var hello = "hello";` + }; + const config = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; + const host = createServerHost([file1, file2, config]); + const session = createSession(host); + openFilesForSession([file1], session); + const projectService = session.getProjectService(); + + // HTML file will not be included in any projects yet + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + const configuredProj = configuredProjectAt(projectService, 0); + checkProjectActualFiles(configuredProj, [file1.path, config.path]); + + // Specify .html extension as mixed content + const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; + const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); + + // The configured project should now be updated to include html file + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + assert.strictEqual(configuredProjectAt(projectService, 0), configuredProj, "Same configured project should be updated"); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + + // Open HTML file + projectService.applyChangesInOpenFiles( /*openFiles*/[{ fileName: file2.path, hasMixedContent: true, scriptKind: ScriptKind.JS, content: `var hello = "hello";` }], /*changedFiles*/ undefined, /*closedFiles*/ undefined); - // Now HTML file is included in the project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + // Now HTML file is included in the project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - // Check identifiers defined in HTML content are available in .ts file - const project = configuredProjectAt(projectService, 0); - let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, { includeExternalModuleExports: false }); - assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`); + // Check identifiers defined in HTML content are available in .ts file + const project = configuredProjectAt(projectService, 0); + let completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 1, { includeExternalModuleExports: false }); + assert(completions && completions.entries[0].name === "hello", `expected entry hello to be in completion list`); - // Close HTML file - projectService.applyChangesInOpenFiles( + // Close HTML file + projectService.applyChangesInOpenFiles( /*openFiles*/ undefined, /*changedFiles*/ undefined, /*closedFiles*/[file2.path]); - // HTML file is still included in project - checkNumberOfProjects(projectService, { configuredProjects: 1 }); - checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); + // HTML file is still included in project + checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkProjectActualFiles(configuredProjectAt(projectService, 0), [file1.path, file2.path, config.path]); - // Check identifiers defined in HTML content are not available in .ts file - completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, { includeExternalModuleExports: false }); - assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`); - }); + // Check identifiers defined in HTML content are not available in .ts file + completions = project.getLanguageService().getCompletionsAtPosition(file1.path, 5, { includeExternalModuleExports: false }); + assert(completions && completions.entries[0].name !== "hello", `unexpected hello entry in completion list`); + }); - it("no tsconfig script block diagnostic errors", () => { + it("no tsconfig script block diagnostic errors", () => { - // #1. Ensure no diagnostic errors when allowJs is true - const file1 = { - path: "/a/b/f1.ts", - content: ` ` - }; - const file2 = { - path: "/a/b/f2.html", - content: `var hello = "hello";` - }; - const config1 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true } }) - }; + // #1. Ensure no diagnostic errors when allowJs is true + const file1 = { + path: "/a/b/f1.ts", + content: ` ` + }; + const file2 = { + path: "/a/b/f2.html", + content: `var hello = "hello";` + }; + const config1 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true } }) + }; - let host = createServerHost([file1, file2, config1, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - let session = createSession(host); + let host = createServerHost([file1, file2, config1, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + let session = createSession(host); - // Specify .html extension as mixed content in a configure host request - const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; - const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); - session.executeCommand(configureHostRequest); + // Specify .html extension as mixed content in a configure host request + const extraFileExtensions = [{ extension: ".html", scriptKind: ScriptKind.JS, isMixedContent: true }]; + const configureHostRequest = makeSessionRequest(CommandNames.Configure, { extraFileExtensions }); + session.executeCommand(configureHostRequest); - openFilesForSession([file1], session); - let projectService = session.getProjectService(); + openFilesForSession([file1], session); + let projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - let diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + let diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - // #2. Ensure no errors when allowJs is false - const config2 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: false } }) - }; + // #2. Ensure no errors when allowJs is false + const config2 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: false } }) + }; - host = createServerHost([file1, file2, config2, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + host = createServerHost([file1, file2, config2, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); - session.executeCommand(configureHostRequest); + session.executeCommand(configureHostRequest); - openFilesForSession([file1], session); - projectService = session.getProjectService(); + openFilesForSession([file1], session); + projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - // #3. Ensure no errors when compiler options aren't specified - const config3 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({}) - }; + // #3. Ensure no errors when compiler options aren't specified + const config3 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({}) + }; - host = createServerHost([file1, file2, config3, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + host = createServerHost([file1, file2, config3, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); - session.executeCommand(configureHostRequest); + session.executeCommand(configureHostRequest); - openFilesForSession([file1], session); - projectService = session.getProjectService(); + openFilesForSession([file1], session); + projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - // #4. Ensure no errors when files are explicitly specified in tsconfig - const config4 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true }, files: [file1.path, file2.path] }) - }; + // #4. Ensure no errors when files are explicitly specified in tsconfig + const config4 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, files: [file1.path, file2.path] }) + }; - host = createServerHost([file1, file2, config4, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + host = createServerHost([file1, file2, config4, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); - session.executeCommand(configureHostRequest); + session.executeCommand(configureHostRequest); - openFilesForSession([file1], session); - projectService = session.getProjectService(); + openFilesForSession([file1], session); + projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); - // #4. Ensure no errors when files are explicitly excluded in tsconfig - const config5 = { - path: "/a/b/tsconfig.json", - content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: [file2.path] }) - }; + // #4. Ensure no errors when files are explicitly excluded in tsconfig + const config5 = { + path: "/a/b/tsconfig.json", + content: JSON.stringify({ compilerOptions: { allowJs: true }, exclude: [file2.path] }) + }; - host = createServerHost([file1, file2, config5, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); - session = createSession(host); + host = createServerHost([file1, file2, config5, libFile], { executingFilePath: combinePaths(getDirectoryPath(libFile.path), "tsc.js") }); + session = createSession(host); - session.executeCommand(configureHostRequest); + session.executeCommand(configureHostRequest); - openFilesForSession([file1], session); - projectService = session.getProjectService(); + openFilesForSession([file1], session); + projectService = session.getProjectService(); - checkNumberOfProjects(projectService, { configuredProjects: 1 }); + checkNumberOfProjects(projectService, { configuredProjects: 1 }); - diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); - assert.deepEqual(diagnostics, []); - }); + diagnostics = configuredProjectAt(projectService, 0).getLanguageService().getCompilerOptionsDiagnostics(); + assert.deepEqual(diagnostics, []); + }); - it("project structure update is deferred if files are not added\removed", () => { - const file1 = { - path: "/a/b/f1.ts", - content: `import {x} from "./f2"` - }; - const file2 = { - path: "/a/b/f2.ts", - content: "export let x = 1" - }; - const host = createServerHost([file1, file2]); - const projectService = createProjectService(host); + it("project structure update is deferred if files are not added\removed", () => { + const file1 = { + path: "/a/b/f1.ts", + content: `import {x} from "./f2"` + }; + const file2 = { + path: "/a/b/f2.ts", + content: "export let x = 1" + }; + const host = createServerHost([file1, file2]); + const projectService = createProjectService(host); - projectService.openClientFile(file1.path); - projectService.openClientFile(file2.path); + projectService.openClientFile(file1.path); + projectService.openClientFile(file2.path); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - projectService.applyChangesInOpenFiles( + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + projectService.applyChangesInOpenFiles( /*openFiles*/ undefined, /*changedFiles*/[{ fileName: file1.path, changes: [{ span: createTextSpan(0, file1.path.length), newText: "let y = 1" }] }], /*closedFiles*/ undefined); - checkNumberOfProjects(projectService, { inferredProjects: 1 }); - const changedFiles = projectService.getChangedFiles_TestOnly(); - assert(changedFiles && changedFiles.length === 1, `expected 1 changed file, got ${JSON.stringify(changedFiles && changedFiles.length || 0)}`); + checkNumberOfProjects(projectService, { inferredProjects: 1 }); + const changedFiles = projectService.getChangedFiles_TestOnly(); + assert(changedFiles && changedFiles.length === 1, `expected 1 changed file, got ${JSON.stringify(changedFiles && changedFiles.length || 0)}`); - projectService.ensureInferredProjectsUpToDate_TestOnly(); - checkNumberOfProjects(projectService, { inferredProjects: 2 }); - }); - - it("files with mixed content are handled correctly", () => { - const file1 = { - path: "/a/b/f1.html", - content: `