From 4de96abd8fc5d678d6b494a953d3127dd2acf871 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 16 Jun 2017 14:37:39 -0700 Subject: [PATCH 1/4] Use the same logic of getting current directory as the one used when emitting files through project It means we would use currentDirectory as project Root or script info's directory as the current directory Fixes issue reported in https://developercommunity.visualstudio.com/content/problem/57099/typescript-generated-source-maps-have-invalid-path.html --- src/compiler/program.ts | 12 +++---- src/compiler/types.ts | 6 +++- src/harness/unittests/compileOnSave.ts | 45 +++++++++++++++++++++++++- src/server/builder.ts | 4 +-- src/server/project.ts | 11 ++++++- src/services/services.ts | 4 +-- src/services/transpile.ts | 2 +- src/services/types.ts | 2 +- 8 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index bbc0fa09780..30c61f50dff 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -874,12 +874,12 @@ namespace ts { return oldProgram.structureIsReused = StructureIsReused.Completely; } - function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { + function getEmitHost(writeFileCallback?: WriteFileCallback, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitHost { return { getCanonicalFileName, getCommonSourceDirectory: program.getCommonSourceDirectory, getCompilerOptions: program.getCompilerOptions, - getCurrentDirectory: () => currentDirectory, + getCurrentDirectory: getCurrentDirectoryCallback || (() => currentDirectory), getNewLine: () => host.getNewLine(), getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, @@ -907,15 +907,15 @@ namespace ts { return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); } - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers): EmitResult { - return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers)); + function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitResult { + return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers, getCurrentDirectoryCallback)); } function isEmitBlocked(emitFileName: string): boolean { return hasEmitBlockingDiagnostics.contains(toPath(emitFileName, currentDirectory, getCanonicalFileName)); } - function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { + function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitResult { let declarationDiagnostics: Diagnostic[] = []; if (options.noEmit) { @@ -960,7 +960,7 @@ namespace ts { const transformers = emitOnlyDtsFiles ? [] : getTransformers(options, customTransformers); const emitResult = emitFiles( emitResolver, - getEmitHost(writeFileCallback), + getEmitHost(writeFileCallback, getCurrentDirectoryCallback), sourceFile, emitOnlyDtsFiles, transformers); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ac64624e1a6..ac5de153226 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2405,6 +2405,10 @@ namespace ts { (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: SourceFile[]): void; } + export interface GetCurrentDirectoryCallback { + (): string; + } + export class OperationCanceledException { } export interface CancellationToken { @@ -2436,7 +2440,7 @@ namespace ts { * used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter * will be invoked when writing the JavaScript and declaration files. */ - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitResult; getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; diff --git a/src/harness/unittests/compileOnSave.ts b/src/harness/unittests/compileOnSave.ts index 7e262a1b257..3183ffd71b0 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -600,5 +600,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}"`); + } + }); }); -} \ No newline at end of file +} diff --git a/src/server/builder.ts b/src/server/builder.ts index 895732ebece..711045d0ae6 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -148,9 +148,9 @@ namespace ts.server { const { emitSkipped, outputFiles } = this.project.getFileEmitOutput(fileInfo.scriptInfo, /*emitOnlyDtsFiles*/ false); if (!emitSkipped) { - const projectRootPath = this.project.getProjectRootPath(); + const currentDirectoryForEmit = this.project.getCurrentDirectoryForScriptInfoEmit(scriptInfo); for (const outputFile of outputFiles) { - const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); + const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, currentDirectoryForEmit); writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); } } diff --git a/src/server/project.ts b/src/server/project.ts index ac040a77ace..a7d8605315b 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -367,7 +367,16 @@ namespace ts.server { if (!this.languageServiceEnabled) { return undefined; } - return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles); + + const getCurrentDirectoryCallback = memoize( + () => this.getCurrentDirectoryForScriptInfoEmit(info) + ); + return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles, getCurrentDirectoryCallback); + } + + getCurrentDirectoryForScriptInfoEmit(info: ScriptInfo) { + const projectRootPath = this.getProjectRootPath(); + return projectRootPath || getDirectoryPath(info.fileName); } getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { diff --git a/src/services/services.ts b/src/services/services.ts index b508285b182..11061181ee6 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1528,7 +1528,7 @@ namespace ts { return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); } - function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput { + function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitOutput { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); @@ -1543,7 +1543,7 @@ namespace ts { } const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); - const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); + const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, getCurrentDirectoryCallback); return { outputFiles, diff --git a/src/services/transpile.ts b/src/services/transpile.ts index 561c188c6cd..5ba393a90c9 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -104,7 +104,7 @@ namespace ts { addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics()); } // Emit - program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers); + program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers, /*getCurrentDirectoryCallback*/ undefined); Debug.assert(outputText !== undefined, "Output generation failed"); diff --git a/src/services/types.ts b/src/services/types.ts index 2d47da2fd1d..07aaaeeb4b4 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -269,7 +269,7 @@ namespace ts { getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; - getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, getCurrentDirectoryCallBack?: GetCurrentDirectoryCallback): EmitOutput; getProgram(): Program; From 6ba62d2d8dbfcfcbf542ab3213949ed98beaf459 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 26 Sep 2017 16:29:25 -0700 Subject: [PATCH 2/4] Revert all the changes except test case --- src/compiler/program.ts | 12 ++++++------ src/compiler/types.ts | 6 +----- src/server/builder.ts | 4 ++-- src/server/project.ts | 11 +---------- src/services/services.ts | 4 ++-- src/services/transpile.ts | 2 +- src/services/types.ts | 2 +- 7 files changed, 14 insertions(+), 27 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 30c61f50dff..bbc0fa09780 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -874,12 +874,12 @@ namespace ts { return oldProgram.structureIsReused = StructureIsReused.Completely; } - function getEmitHost(writeFileCallback?: WriteFileCallback, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitHost { + function getEmitHost(writeFileCallback?: WriteFileCallback): EmitHost { return { getCanonicalFileName, getCommonSourceDirectory: program.getCommonSourceDirectory, getCompilerOptions: program.getCompilerOptions, - getCurrentDirectory: getCurrentDirectoryCallback || (() => currentDirectory), + getCurrentDirectory: () => currentDirectory, getNewLine: () => host.getNewLine(), getSourceFile: program.getSourceFile, getSourceFileByPath: program.getSourceFileByPath, @@ -907,15 +907,15 @@ namespace ts { return noDiagnosticsTypeChecker || (noDiagnosticsTypeChecker = createTypeChecker(program, /*produceDiagnostics:*/ false)); } - function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitResult { - return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers, getCurrentDirectoryCallback)); + function emit(sourceFile?: SourceFile, writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, transformers?: CustomTransformers): EmitResult { + return runWithCancellationToken(() => emitWorker(program, sourceFile, writeFileCallback, cancellationToken, emitOnlyDtsFiles, transformers)); } function isEmitBlocked(emitFileName: string): boolean { return hasEmitBlockingDiagnostics.contains(toPath(emitFileName, currentDirectory, getCanonicalFileName)); } - function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitResult { + function emitWorker(program: Program, sourceFile: SourceFile, writeFileCallback: WriteFileCallback, cancellationToken: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult { let declarationDiagnostics: Diagnostic[] = []; if (options.noEmit) { @@ -960,7 +960,7 @@ namespace ts { const transformers = emitOnlyDtsFiles ? [] : getTransformers(options, customTransformers); const emitResult = emitFiles( emitResolver, - getEmitHost(writeFileCallback, getCurrentDirectoryCallback), + getEmitHost(writeFileCallback), sourceFile, emitOnlyDtsFiles, transformers); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ac5de153226..ac64624e1a6 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2405,10 +2405,6 @@ namespace ts { (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void, sourceFiles?: SourceFile[]): void; } - export interface GetCurrentDirectoryCallback { - (): string; - } - export class OperationCanceledException { } export interface CancellationToken { @@ -2440,7 +2436,7 @@ namespace ts { * used for writing the JavaScript and declaration files. Otherwise, the writeFile parameter * will be invoked when writing the JavaScript and declaration files. */ - emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitResult; + emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken, emitOnlyDtsFiles?: boolean, customTransformers?: CustomTransformers): EmitResult; getOptionsDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; getGlobalDiagnostics(cancellationToken?: CancellationToken): Diagnostic[]; diff --git a/src/server/builder.ts b/src/server/builder.ts index 711045d0ae6..895732ebece 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -148,9 +148,9 @@ namespace ts.server { const { emitSkipped, outputFiles } = this.project.getFileEmitOutput(fileInfo.scriptInfo, /*emitOnlyDtsFiles*/ false); if (!emitSkipped) { - const currentDirectoryForEmit = this.project.getCurrentDirectoryForScriptInfoEmit(scriptInfo); + const projectRootPath = this.project.getProjectRootPath(); for (const outputFile of outputFiles) { - const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, currentDirectoryForEmit); + const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); } } diff --git a/src/server/project.ts b/src/server/project.ts index a7d8605315b..ac040a77ace 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -367,16 +367,7 @@ namespace ts.server { if (!this.languageServiceEnabled) { return undefined; } - - const getCurrentDirectoryCallback = memoize( - () => this.getCurrentDirectoryForScriptInfoEmit(info) - ); - return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles, getCurrentDirectoryCallback); - } - - getCurrentDirectoryForScriptInfoEmit(info: ScriptInfo) { - const projectRootPath = this.getProjectRootPath(); - return projectRootPath || getDirectoryPath(info.fileName); + return this.getLanguageService().getEmitOutput(info.fileName, emitOnlyDtsFiles); } getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) { diff --git a/src/services/services.ts b/src/services/services.ts index 11061181ee6..b508285b182 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1528,7 +1528,7 @@ namespace ts { return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles); } - function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, getCurrentDirectoryCallback?: GetCurrentDirectoryCallback): EmitOutput { + function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput { synchronizeHostData(); const sourceFile = getValidSourceFile(fileName); @@ -1543,7 +1543,7 @@ namespace ts { } const customTransformers = host.getCustomTransformers && host.getCustomTransformers(); - const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers, getCurrentDirectoryCallback); + const emitOutput = program.emit(sourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); return { outputFiles, diff --git a/src/services/transpile.ts b/src/services/transpile.ts index 5ba393a90c9..561c188c6cd 100644 --- a/src/services/transpile.ts +++ b/src/services/transpile.ts @@ -104,7 +104,7 @@ namespace ts { addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics()); } // Emit - program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers, /*getCurrentDirectoryCallback*/ undefined); + program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transpileOptions.transformers); Debug.assert(outputText !== undefined, "Output generation failed"); diff --git a/src/services/types.ts b/src/services/types.ts index 07aaaeeb4b4..2d47da2fd1d 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -269,7 +269,7 @@ namespace ts { getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[]; getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined; - getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, getCurrentDirectoryCallBack?: GetCurrentDirectoryCallback): EmitOutput; + getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput; getProgram(): Program; From fad71d3dc69bc5381ba8bb17605a9660f5a05339 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Tue, 26 Sep 2017 17:29:53 -0700 Subject: [PATCH 3/4] Use project root as the current directory whenever possible to create the project --- src/compiler/program.ts | 13 +++- src/harness/unittests/compileOnSave.ts | 2 +- src/harness/unittests/session.ts | 4 +- src/server/builder.ts | 3 +- src/server/editorServices.ts | 33 ++++++++-- src/server/lsHost.ts | 4 +- src/server/project.ts | 66 +++++++++---------- tests/cases/fourslash/server/projectInfo01.ts | 8 +-- tests/cases/fourslash/server/projectInfo02.ts | 2 +- .../server/projectWithNonExistentFiles.ts | 2 +- 10 files changed, 78 insertions(+), 59 deletions(-) diff --git a/src/compiler/program.ts b/src/compiler/program.ts index c8ff2496725..1397d46af1c 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -984,11 +984,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 89b980f23f8..dddcd64cd39 100644 --- a/src/harness/unittests/compileOnSave.ts +++ b/src/harness/unittests/compileOnSave.ts @@ -567,7 +567,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(); diff --git a/src/harness/unittests/session.ts b/src/harness/unittests/session.ts index f37c9ea2392..417591cdb6f 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/server/builder.ts b/src/server/builder.ts index 5cf65611fb3..0279f08bfab 100644 --- a/src/server/builder.ts +++ b/src/server/builder.ts @@ -148,9 +148,8 @@ namespace ts.server { const { emitSkipped, outputFiles } = this.project.getFileEmitOutput(fileInfo.scriptInfo, /*emitOnlyDtsFiles*/ false); if (!emitSkipped) { - const projectRootPath = this.project.getProjectRootPath(); for (const outputFile of outputFiles) { - const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, projectRootPath ? projectRootPath : getDirectoryPath(scriptInfo.fileName)); + const outputFileAbsoluteFileName = getNormalizedAbsolutePath(outputFile.name, this.project.currentDirectory); writeFile(outputFileAbsoluteFileName, outputFile.text, outputFile.writeByteOrderMark); } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index d86f45a9ad9..9a12c4cab7c 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -417,7 +417,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"); @@ -442,6 +442,16 @@ namespace ts.server { this.documentRegistry = createDocumentRegistry(this.host.useCaseSensitiveFileNames, this.host.getCurrentDirectory()); } + /*@internal*/ + getExecutingFilePath() { + return this.getNormalizedAbsolutePath(this.host.getExecutingFilePath()); + } + + /*@internal*/ + getNormalizedAbsolutePath(fileName: string) { + return getNormalizedAbsolutePath(fileName, this.host.getCurrentDirectory()); + } + /* @internal */ getChangedFiles_TestOnly() { return this.changedFiles; @@ -924,6 +934,14 @@ namespace ts.server { }); } + /*@internal*/ getScriptInfoPaths() { + const result: Path[] = []; + this.filenameToScriptInfo.forEach(info => { + result.push(info.path); + }); + return result; + } + /** * This function tries to search for a tsconfig.json for the given file. If we found it, * we first detect if there is already a configured project created for it: if so, we re-read @@ -1365,7 +1383,7 @@ namespace ts.server { return project; } } - return this.createInferredProject(/*isSingleInferredProject*/ false, projectRootPath); + return this.createInferredProject(projectRootPath, /*isSingleInferredProject*/ false, projectRootPath); } // we don't have an explicit root path, so we should try to find an inferred project @@ -1402,12 +1420,13 @@ namespace ts.server { return this.inferredProjects[0]; } - return this.createInferredProject(/*isSingleInferredProject*/ true); + // Single inferred project does not have a project root. + return this.createInferredProject(/*currentDirectory*/ undefined, /*isSingleInferredProject*/ true); } - private createInferredProject(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); + const project = new InferredProject(this, this.documentRegistry, compilerOptions, currentDirectory, projectRootPath); if (isSingleInferredProject) { this.inferredProjects.unshift(project); } @@ -1419,8 +1438,8 @@ namespace ts.server { createInferredProjectWithRootFileIfNecessary(root: ScriptInfo, projectRootPath?: string) { const project = this.getOrCreateInferredProjectForProjectRootPathIfEnabled(root, projectRootPath) || - this.getOrCreateSingleInferredProjectIfEnabled() || - this.createInferredProject(); + this.getOrCreateSingleInferredProjectIfEnabled() || + this.createInferredProject(getDirectoryPath(root.path)); project.addRoot(root); diff --git a/src/server/lsHost.ts b/src/server/lsHost.ts index 13b9505a658..08dd5000cba 100644 --- a/src/server/lsHost.ts +++ b/src/server/lsHost.ts @@ -173,7 +173,7 @@ namespace ts.server { } getDefaultLibFileName() { - const nodeModuleBinDir = getDirectoryPath(normalizePath(this.host.getExecutingFilePath())); + const nodeModuleBinDir = getDirectoryPath(this.project.projectService.getExecutingFilePath()); return combinePaths(nodeModuleBinDir, getDefaultLibFileName(this.compilationSettings)); } @@ -203,7 +203,7 @@ namespace ts.server { } getCurrentDirectory(): string { - return this.host.getCurrentDirectory(); + return this.project.currentDirectory; } resolvePath(path: string): string { diff --git a/src/server/project.ts b/src/server/project.ts index 9ef79530e51..084193a97f5 100644 --- a/src/server/project.ts +++ b/src/server/project.ts @@ -177,6 +177,9 @@ namespace ts.server { return result.module; } + /*@internal*/ + readonly currentDirectory: string; + constructor( private readonly projectName: string, readonly projectKind: ProjectKind, @@ -185,8 +188,9 @@ namespace ts.server { hasExplicitListOfFiles: boolean, languageServiceEnabled: boolean, private compilerOptions: CompilerOptions, - public compileOnSaveEnabled: boolean) { - + public compileOnSaveEnabled: boolean, + currentDirectory: string | undefined) { + this.currentDirectory = this.projectService.getNormalizedAbsolutePath(currentDirectory || ""); if (!this.compilerOptions) { this.compilerOptions = getDefaultCompilerOptions(); this.compilerOptions.allowNonTsExtensions = true; @@ -268,7 +272,6 @@ namespace ts.server { getProjectName() { return this.projectName; } - abstract getProjectRootPath(): string | undefined; abstract getTypeAcquisition(): TypeAcquisition; getExternalFiles(): SortedReadonlyArray { @@ -363,7 +366,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.\nProgram currentDirectory: '${this.program.getCurrentDirectory()}'\nCurrentScriptInfos: ${this.projectService.getScriptInfoPaths()}\ncurrentDirectory: ${this.projectService.host.getCurrentDirectory()}`); } return scriptInfo; }); @@ -842,8 +845,6 @@ namespace ts.server { * the file and its imports/references are put into an InferredProject. */ export class InferredProject extends Project { - public readonly projectRootPath: string | undefined; - private static readonly newName = (() => { let nextId = 1; return () => { @@ -882,7 +883,7 @@ namespace ts.server { // Used to keep track of what directories are watched for this project directoriesWatchedForTsconfig: string[] = []; - constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, projectRootPath?: string) { + constructor(projectService: ProjectService, documentRegistry: DocumentRegistry, compilerOptions: CompilerOptions, currentDirectory: string | undefined, readonly projectRootPath: string | undefined) { super(InferredProject.newName(), ProjectKind.Inferred, projectService, @@ -890,7 +891,8 @@ namespace ts.server { /*files*/ undefined, /*languageServiceEnabled*/ true, compilerOptions, - /*compileOnSaveEnabled*/ false); + /*compileOnSaveEnabled*/ false, + currentDirectory); this.projectRootPath = projectRootPath; } @@ -910,15 +912,6 @@ namespace ts.server { super.removeRoot(info); } - getProjectRootPath() { - // Single inferred project does not have a project root. - if (this.projectService.useSingleInferredProject) { - return undefined; - } - const rootFiles = this.getRootFiles(); - return getDirectoryPath(rootFiles[0]); - } - close() { super.close(); @@ -962,7 +955,15 @@ namespace ts.server { private wildcardDirectories: Map, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean) { - super(configFileName, ProjectKind.Configured, projectService, documentRegistry, hasExplicitListOfFiles, languageServiceEnabled, compilerOptions, compileOnSaveEnabled); + super(configFileName, + ProjectKind.Configured, + projectService, + documentRegistry, + hasExplicitListOfFiles, + languageServiceEnabled, + compilerOptions, + compileOnSaveEnabled, + getDirectoryPath(configFileName)); this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName)); this.enablePlugins(); } @@ -982,7 +983,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); @@ -1062,10 +1063,6 @@ namespace ts.server { } } - getProjectRootPath() { - return getDirectoryPath(this.getConfigFilePath()); - } - setProjectErrors(projectErrors: ReadonlyArray) { this.projectErrors = projectErrors; } @@ -1196,25 +1193,22 @@ namespace ts.server { compilerOptions: CompilerOptions, languageServiceEnabled: boolean, public compileOnSaveEnabled: boolean, - private readonly projectFilePath?: string) { - super(externalProjectName, ProjectKind.External, projectService, documentRegistry, /*hasExplicitListOfFiles*/ true, languageServiceEnabled, compilerOptions, compileOnSaveEnabled); - + projectFilePath?: string) { + super(externalProjectName, + ProjectKind.External, + projectService, + documentRegistry, + /*hasExplicitListOfFiles*/ true, + languageServiceEnabled, + compilerOptions, + compileOnSaveEnabled, + getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName))); } getExcludedFiles() { 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/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"]) From 2facead886b2850df151dca9cf2852f0e68f800c Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Thu, 5 Oct 2017 09:54:21 -0700 Subject: [PATCH 4/4] Update tests after the merge from master --- src/harness/unittests/tsserverProjectSystem.ts | 11 +++++------ src/harness/virtualFileSystemWithWatch.ts | 12 ++++++++---- tests/baselines/reference/api/tsserverlibrary.d.ts | 7 +------ 3 files changed, 14 insertions(+), 16 deletions(-) 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/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 3bb2ed11674..713188e9da8 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -7131,7 +7131,6 @@ declare namespace ts.server { enableLanguageService(): void; disableLanguageService(): void; getProjectName(): string; - abstract getProjectRootPath(): string | undefined; abstract getTypeAcquisition(): TypeAcquisition; getExternalFiles(): SortedReadonlyArray; getSourceFile(path: Path): SourceFile; @@ -7184,7 +7183,6 @@ declare namespace ts.server { addRoot(info: ScriptInfo): void; removeRoot(info: ScriptInfo): void; isProjectWithSingleRoot(): boolean; - getProjectRootPath(): string; close(): void; getTypeAcquisition(): TypeAcquisition; } @@ -7211,7 +7209,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 */ @@ -7237,11 +7234,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; } @@ -7519,7 +7514,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);