diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 24d7707f50d..82c4180c565 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1092,11 +1092,18 @@ namespace ts { return true; } - if (defaultLibraryPath && defaultLibraryPath.length !== 0) { - return containsPath(defaultLibraryPath, file.path, currentDirectory, /*ignoreCase*/ !host.useCaseSensitiveFileNames()); + if (!options.noLib) { + return false; } - return compareStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + // If '--lib' is not specified, include default library file according to '--target' + // otherwise, using options specified in '--lib' instead of '--target' default library file + if (!options.lib) { + return compareStrings(file.fileName, getDefaultLibraryFileName(), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo; + } + else { + return forEach(options.lib, libFileName => compareStrings(file.fileName, combinePaths(defaultLibraryPath, libFileName), /*ignoreCase*/ !host.useCaseSensitiveFileNames()) === Comparison.EqualTo); + } } function getDiagnosticsProducingTypeChecker() { diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 4ddda5fcbea..1e765054eee 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -563,7 +563,7 @@ namespace ts.projectSystem { path: "/a/b/file3.js", content: "console.log('file3');" }; - const externalProjectName = "externalproject"; + const externalProjectName = "/a/b/externalproject"; const host = createServerHost([file1, file2, file3, libFile]); const session = createSession(host); const projectService = session.getProjectService(); @@ -588,5 +588,48 @@ namespace ts.projectSystem { assert.isTrue(outFileContent.indexOf(file2.content) === -1); assert.isTrue(outFileContent.indexOf(file3.content) === -1); }); + + it("should use project root as current directory so that compile on save results in correct file mapping", () => { + const inputFileName = "Foo.ts"; + const file1 = { + path: `/root/TypeScriptProject3/TypeScriptProject3/${inputFileName}`, + content: "consonle.log('file1');" + }; + const externalProjectName = "/root/TypeScriptProject3/TypeScriptProject3/TypeScriptProject3.csproj"; + const host = createServerHost([file1, libFile]); + const session = createSession(host); + const projectService = session.getProjectService(); + + const outFileName = "bar.js"; + projectService.openExternalProject({ + rootFiles: toExternalFiles([file1.path]), + options: { + outFile: outFileName, + sourceMap: true, + compileOnSave: true + }, + projectFileName: externalProjectName + }); + + const emitRequest = makeSessionRequest(CommandNames.CompileOnSaveEmitFile, { file: file1.path }); + session.executeCommand(emitRequest); + + // Verify js file + const expectedOutFileName = "/root/TypeScriptProject3/TypeScriptProject3/" + outFileName; + assert.isTrue(host.fileExists(expectedOutFileName)); + const outFileContent = host.readFile(expectedOutFileName); + verifyContentHasString(outFileContent, file1.content); + verifyContentHasString(outFileContent, `//# sourceMappingURL=${outFileName}.map`); + + // Verify map file + const expectedMapFileName = expectedOutFileName + ".map"; + assert.isTrue(host.fileExists(expectedMapFileName)); + const mapFileContent = host.readFile(expectedMapFileName); + verifyContentHasString(mapFileContent, `"sources":["${inputFileName}"]`); + + function verifyContentHasString(content: string, string: string) { + assert.isTrue(content.indexOf(string) !== -1, `Expected "${content}" to have "${string}"`); + } + }); }); } diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index cfc1493ff71..3b5efc2d6de 100644 --- a/src/harness/unittests/session.ts +++ b/src/harness/unittests/session.ts @@ -16,8 +16,8 @@ namespace ts.server { directoryExists: () => false, getDirectories: () => [], createDirectory: noop, - getExecutingFilePath(): string { return void 0; }, - getCurrentDirectory(): string { return void 0; }, + getExecutingFilePath(): string { return ""; }, + getCurrentDirectory(): string { return ""; }, getEnvironmentVariable(): string { return ""; }, readDirectory() { return []; }, exit: noop, diff --git a/src/harness/unittests/tsserverProjectSystem.ts b/src/harness/unittests/tsserverProjectSystem.ts index 06e2beeaaa6..e594093605a 100644 --- a/src/harness/unittests/tsserverProjectSystem.ts +++ b/src/harness/unittests/tsserverProjectSystem.ts @@ -352,8 +352,6 @@ namespace ts.projectSystem { verifyDiagnostics(actual, []); } - const typeRootFromTsserverLocation = "/node_modules/@types"; - export function getTypeRootsFromLocation(currentDirectory: string) { currentDirectory = normalizePath(currentDirectory); const result: string[] = []; @@ -401,7 +399,7 @@ namespace ts.projectSystem { 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", typeRootFromTsserverLocation], /*recursive*/ true); + checkWatchedDirectories(host, ["/a/b/c", ...getTypeRootsFromLocation(getDirectoryPath(appFile.path))], /*recursive*/ true); }); it("can handle tsconfig file name with difference casing", () => { @@ -4331,7 +4329,7 @@ namespace ts.projectSystem { function verifyCalledOnEachEntry(callback: CalledMaps, expectedKeys: Map) { const calledMap = calledMaps[callback]; - assert.equal(calledMap.size, expectedKeys.size, `${callback}: incorrect size of map: Actual keys: ${arrayFrom(calledMap.keys())} Expected: ${arrayFrom(expectedKeys.keys())}`); + ts.TestFSWithWatch.verifyMapSize(callback, calledMap, arrayFrom(expectedKeys.keys())); expectedKeys.forEach((called, name) => { assert.isTrue(calledMap.has(name), `${callback} is expected to contain ${name}, actual keys: ${arrayFrom(calledMap.keys())}`); assert.equal(calledMap.get(name).length, called, `${callback} is expected to be called ${called} times with ${name}. Actual entry: ${calledMap.get(name)}`); @@ -4413,6 +4411,7 @@ namespace ts.projectSystem { } const f2Lookups = getLocationsForModuleLookup("f2"); callsTrackingHost.verifyCalledOnEachEntryNTimes(CalledMapsWithSingleArg.fileExists, f2Lookups, 1); + const typeRootLocations = getTypeRootsFromLocation(getDirectoryPath(root.path)); const f2DirLookups = getLocationsForDirectoryLookup(); callsTrackingHost.verifyCalledOnEachEntry(CalledMapsWithSingleArg.directoryExists, f2DirLookups); callsTrackingHost.verifyNoCall(CalledMapsWithSingleArg.getDirectories); @@ -4423,7 +4422,7 @@ namespace ts.projectSystem { verifyImportedDiagnostics(); const f1Lookups = f2Lookups.map(s => s.replace("f2", "f1")); f1Lookups.length = f1Lookups.indexOf(imported.path) + 1; - const f1DirLookups = ["/c/d", "/c", typeRootFromTsserverLocation]; + const f1DirLookups = ["/c/d", "/c", ...typeRootLocations]; vertifyF1Lookups(); // setting compiler options discards module resolution cache @@ -4475,7 +4474,7 @@ namespace ts.projectSystem { function getLocationsForDirectoryLookup() { const result = createMap(); // Type root - result.set(typeRootFromTsserverLocation, 1); + typeRootLocations.forEach(location => result.set(location, 1)); forEachAncestorDirectory(getDirectoryPath(root.path), ancestor => { // To resolve modules result.set(ancestor, 2); diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index c16f57235e4..ff782bbf7d2 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -95,7 +95,7 @@ namespace ts.TestFSWithWatch { } } - function getDiffInKeys(map: Map, expectedKeys: ReadonlyArray) { + function getDiffInKeys(map: Map, expectedKeys: ReadonlyArray) { if (map.size === expectedKeys.length) { return ""; } @@ -122,8 +122,12 @@ namespace ts.TestFSWithWatch { return `\n\nNotInActual: ${notInActual}\nDuplicates: ${duplicates}\nInActualButNotInExpected: ${inActualNotExpected}`; } - function checkMapKeys(caption: string, map: Map, expectedKeys: ReadonlyArray) { + export function verifyMapSize(caption: string, map: Map, expectedKeys: ReadonlyArray) { assert.equal(map.size, expectedKeys.length, `${caption}: incorrect size of map: Actual keys: ${arrayFrom(map.keys())} Expected: ${expectedKeys}${getDiffInKeys(map, expectedKeys)}`); + } + + function checkMapKeys(caption: string, map: Map, expectedKeys: ReadonlyArray) { + verifyMapSize(caption, map, expectedKeys); for (const name of expectedKeys) { assert.isTrue(map.has(name), `${caption} is expected to contain ${name}, actual keys: ${arrayFrom(map.keys())}`); } @@ -548,7 +552,7 @@ namespace ts.TestFSWithWatch { const folder = this.toFolder(directoryName); // base folder has to be present - const base = getDirectoryPath(folder.fullPath); + const base = getDirectoryPath(folder.path); const baseFolder = this.fs.get(base) as Folder; Debug.assert(isFolder(baseFolder)); @@ -560,7 +564,7 @@ namespace ts.TestFSWithWatch { const file = this.toFile({ path, content }); // base folder has to be present - const base = getDirectoryPath(file.fullPath); + const base = getDirectoryPath(file.path); const folder = this.fs.get(base) as Folder; Debug.assert(isFolder(folder)); diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 2916fb60c57..1369ecfbda9 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -403,7 +403,7 @@ namespace ts.server { this.globalPlugins = opts.globalPlugins || emptyArray; this.pluginProbeLocations = opts.pluginProbeLocations || emptyArray; this.allowLocalPluginLoads = !!opts.allowLocalPluginLoads; - this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.host.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation; + this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation; Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService"); @@ -452,6 +452,16 @@ namespace ts.server { return toPath(fileName, this.currentDirectory, this.toCanonicalFileName); } + /*@internal*/ + getExecutingFilePath() { + return this.getNormalizedAbsolutePath(this.host.getExecutingFilePath()); + } + + /*@internal*/ + getNormalizedAbsolutePath(fileName: string) { + return getNormalizedAbsolutePath(fileName, this.host.getCurrentDirectory()); + } + /* @internal */ getChangedFiles_TestOnly() { return this.changedFiles; @@ -1626,12 +1636,13 @@ namespace ts.server { return this.inferredProjects[0]; } - return this.createInferredProject(/*rootDirectoryForResolution*/ undefined, /*isSingleInferredProject*/ true); + // Single inferred project does not have a project root and hence no current directory + return this.createInferredProject(/*currentDirectory*/ undefined, /*isSingleInferredProject*/ true); } - private createInferredProject(rootDirectoryForResolution: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject { + private createInferredProject(currentDirectory: string | undefined, isSingleInferredProject?: boolean, projectRootPath?: string): InferredProject { const compilerOptions = projectRootPath && this.compilerOptionsForInferredProjectsPerProjectRoot.get(projectRootPath) || this.compilerOptionsForInferredProjects; - const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, rootDirectoryForResolution); + const project = new InferredProject(this, this.documentRegistry, compilerOptions, projectRootPath, currentDirectory); if (isSingleInferredProject) { this.inferredProjects.unshift(project); } diff --git a/src/server/project.ts b/src/server/project.ts index ac18738027b..d5ebec39f93 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -195,6 +195,9 @@ namespace ts.server { return result.module; } + /*@internal*/ + readonly currentDirectory: string; + /*@internal*/ constructor( /*@internal*/readonly projectName: string, @@ -206,7 +209,8 @@ namespace ts.server { private compilerOptions: CompilerOptions, public compileOnSaveEnabled: boolean, /*@internal*/public directoryStructureHost: DirectoryStructureHost, - rootDirectoryForResolution: string | undefined) { + currentDirectory: string | undefined) { + this.currentDirectory = this.projectService.getNormalizedAbsolutePath(currentDirectory || ""); this.cancellationToken = new ThrottledCancellationToken(this.projectService.cancellationToken, this.projectService.throttleWaitMilliseconds); if (!this.compilerOptions) { @@ -230,7 +234,8 @@ namespace ts.server { } this.languageService = createLanguageService(this, this.documentRegistry); - this.resolutionCache = createResolutionCache(this, rootDirectoryForResolution); + // Use the current directory as resolution root only if the project created using current directory string + this.resolutionCache = createResolutionCache(this, currentDirectory && this.currentDirectory); if (!languageServiceEnabled) { this.disableLanguageService(); } @@ -301,11 +306,11 @@ namespace ts.server { } getCurrentDirectory(): string { - return this.directoryStructureHost.getCurrentDirectory(); + return this.currentDirectory; } getDefaultLibFileName() { - const nodeModuleBinDir = getDirectoryPath(normalizePath(this.projectService.host.getExecutingFilePath())); + const nodeModuleBinDir = getDirectoryPath(normalizePath(this.projectService.getExecutingFilePath())); return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilerOptions)); } @@ -448,9 +453,8 @@ namespace ts.server { this.ensureBuilder(); const { emitSkipped, outputFiles } = this.builder.emitFile(this.program, scriptInfo.path); if (!emitSkipped) { - const projectRootPath = this.getProjectRootPath(); for (const outputFile of outputFiles) { - const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); + const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, this.currentDirectory); writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); } } @@ -479,7 +483,6 @@ namespace ts.server { getProjectName() { return this.projectName; } - abstract getProjectRootPath(): string | undefined; abstract getTypeAcquisition(): TypeAcquisition; getExternalFiles(): SortedReadonlyArray { @@ -561,7 +564,7 @@ namespace ts.server { return map(this.program.getSourceFiles(), sourceFile => { const scriptInfo = this.projectService.getScriptInfoForPath(sourceFile.path); if (!scriptInfo) { - Debug.fail(`scriptInfo for a file '${sourceFile.fileName}' is missing.`); + Debug.fail(`scriptInfo for a file '${sourceFile.fileName}' Path: '${sourceFile.path}' is missing.`); } return scriptInfo; }); @@ -1042,8 +1045,8 @@ namespace ts.server { projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, - public readonly projectRootPath: string | undefined, - rootDirectoryForResolution: string | undefined) { + readonly projectRootPath: string | undefined, + currentDirectory: string | undefined) { super(InferredProject.newName(), ProjectKind.Inferred, projectService, @@ -1053,7 +1056,7 @@ namespace ts.server { compilerOptions, /*compileOnSaveEnabled*/ false, projectService.host, - rootDirectoryForResolution); + currentDirectory); } addRoot(info: ScriptInfo) { @@ -1082,12 +1085,6 @@ namespace ts.server { this.getRootScriptInfos().length === 1; } - getProjectRootPath() { - return this.projectRootPath || - // Single inferred project does not have a project root. - !this.projectService.useSingleInferredProject && getDirectoryPath(this.getRootFiles()[0]); - } - close() { forEach(this.getRootScriptInfos(), info => this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info)); super.close(); @@ -1183,7 +1180,7 @@ namespace ts.server { // Search our peer node_modules, then any globally-specified probe paths // ../../.. to walk from X/node_modules/typescript/lib/tsserver.js to X/node_modules/ - const searchPaths = [combinePaths(host.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations]; + const searchPaths = [combinePaths(this.projectService.getExecutingFilePath(), "../../.."), ...this.projectService.pluginProbeLocations]; if (this.projectService.allowLocalPluginLoads) { const local = getDirectoryPath(this.canonicalConfigFilePath); @@ -1263,10 +1260,6 @@ namespace ts.server { } } - getProjectRootPath() { - return getDirectoryPath(this.getConfigFilePath()); - } - /** * Get the errors that dont have any file name associated */ @@ -1379,13 +1372,14 @@ namespace ts.server { compilerOptions: CompilerOptions, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, - private readonly projectFilePath?: string) { + projectFilePath?: string) { super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, - languageServiceEnabled, compilerOptions, + languageServiceEnabled, + compilerOptions, compileOnSaveEnabled, projectService.host, getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName))); @@ -1395,16 +1389,6 @@ namespace ts.server { return this.excludedFiles; } - getProjectRootPath() { - if (this.projectFilePath) { - return getDirectoryPath(this.projectFilePath); - } - // if the projectFilePath is not given, we make the assumption that the project name - // is the path of the project file. AS the project name is provided by VS, we need to - // normalize slashes before using it as a file name. - return getDirectoryPath(normalizeSlashes(this.getProjectName())); - } - getTypeAcquisition() { return this.typeAcquisition; } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 6da4262e0b7..e412027cc93 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7141,7 +7141,6 @@ declare namespace ts.server { enableLanguageService(): void; disableLanguageService(): void; getProjectName(): string; - abstract getProjectRootPath(): string | undefined; abstract getTypeAcquisition(): TypeAcquisition; getExternalFiles(): SortedReadonlyArray; getSourceFile(path: Path): SourceFile; @@ -7194,7 +7193,6 @@ declare namespace ts.server { addRoot(info: ScriptInfo): void; removeRoot(info: ScriptInfo): void; isProjectWithSingleRoot(): boolean; - getProjectRootPath(): string; close(): void; getTypeAcquisition(): TypeAcquisition; } @@ -7221,7 +7219,6 @@ declare namespace ts.server { enablePlugins(): void; private enablePlugin(pluginConfigEntry, searchPaths); private enableProxy(pluginModuleFactory, configEntry); - getProjectRootPath(): string; /** * Get the errors that dont have any file name associated */ @@ -7247,11 +7244,9 @@ declare namespace ts.server { class ExternalProject extends Project { externalProjectName: string; compileOnSaveEnabled: boolean; - private readonly projectFilePath; excludedFiles: ReadonlyArray; private typeAcquisition; getExcludedFiles(): ReadonlyArray; - getProjectRootPath(): string; getTypeAcquisition(): TypeAcquisition; setTypeAcquisition(newTypeAcquisition: TypeAcquisition): void; } @@ -7529,7 +7524,7 @@ declare namespace ts.server { private updateNonInferredProject(project, newUncheckedFiles, propertyReader, newOptions, newTypeAcquisition, compileOnSave); private getOrCreateInferredProjectForProjectRootPathIfEnabled(info, projectRootPath); private getOrCreateSingleInferredProjectIfEnabled(); - private createInferredProject(rootDirectoryForResolution, isSingleInferredProject?, projectRootPath?); + private createInferredProject(currentDirectory, isSingleInferredProject?, projectRootPath?); getScriptInfo(uncheckedFileName: string): ScriptInfo; private watchClosedScriptInfo(info); private stopWatchingScriptInfo(info); diff --git a/tests/cases/fourslash/server/projectInfo01.ts b/tests/cases/fourslash/server/projectInfo01.ts index 0d8707bf8a1..036aa5f0d4d 100644 --- a/tests/cases/fourslash/server/projectInfo01.ts +++ b/tests/cases/fourslash/server/projectInfo01.ts @@ -14,11 +14,11 @@ ////console.log("nothing"); goTo.file("a.ts") -verify.ProjectInfo(["lib.d.ts", "a.ts"]) +verify.ProjectInfo(["/lib.d.ts", "a.ts"]) goTo.file("b.ts") -verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts"]) +verify.ProjectInfo(["/lib.d.ts", "a.ts", "b.ts"]) goTo.file("c.ts") -verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts", "c.ts"]) +verify.ProjectInfo(["/lib.d.ts", "a.ts", "b.ts", "c.ts"]) goTo.file("d.ts") -verify.ProjectInfo(["lib.d.ts", "d.ts"]) +verify.ProjectInfo(["/lib.d.ts", "d.ts"]) diff --git a/tests/cases/fourslash/server/projectInfo02.ts b/tests/cases/fourslash/server/projectInfo02.ts index 3077deb453c..fb7c9cf8257 100644 --- a/tests/cases/fourslash/server/projectInfo02.ts +++ b/tests/cases/fourslash/server/projectInfo02.ts @@ -10,4 +10,4 @@ ////{ "files": ["a.ts", "b.ts"] } goTo.file("a.ts") -verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts", "tsconfig.json"]) +verify.ProjectInfo(["/lib.d.ts", "a.ts", "b.ts", "tsconfig.json"]) diff --git a/tests/cases/fourslash/server/projectWithNonExistentFiles.ts b/tests/cases/fourslash/server/projectWithNonExistentFiles.ts index 0e263d9aca6..a52c5f8918f 100644 --- a/tests/cases/fourslash/server/projectWithNonExistentFiles.ts +++ b/tests/cases/fourslash/server/projectWithNonExistentFiles.ts @@ -10,4 +10,4 @@ ////{ "files": ["a.ts", "c.ts", "b.ts"] } goTo.file("a.ts"); -verify.ProjectInfo(["lib.d.ts", "a.ts", "b.ts", "tsconfig.json"]) +verify.ProjectInfo(["/lib.d.ts", "a.ts", "b.ts", "tsconfig.json"])