diff --git a/src/compiler/sourcemapDecoder.ts b/src/compiler/sourcemapDecoder.ts index dd0d65c809..17cd1375f7 100644 --- a/src/compiler/sourcemapDecoder.ts +++ b/src/compiler/sourcemapDecoder.ts @@ -83,7 +83,7 @@ namespace ts.sourcemaps { if (!maps[targetIndex] || comparePaths(loc.fileName, maps[targetIndex].sourcePath, sourceRoot, !host.useCaseSensitiveFileNames) !== 0) { return loc; } - return { fileName: toPath(map.file!, sourceRoot, host.getCanonicalFileName), position: maps[targetIndex].emittedPosition }; // Closest pos + return { fileName: getNormalizedAbsolutePath(map.file!, sourceRoot), position: maps[targetIndex].emittedPosition }; // Closest pos } function getOriginalPosition(loc: SourceMappableLocation): SourceMappableLocation { @@ -94,13 +94,13 @@ namespace ts.sourcemaps { // if no exact match, closest is 2's compliment of result targetIndex = ~targetIndex; } - return { fileName: toPath(maps[targetIndex].sourcePath, sourceRoot, host.getCanonicalFileName), position: maps[targetIndex].sourcePosition }; // Closest pos + return { fileName: getNormalizedAbsolutePath(maps[targetIndex].sourcePath, sourceRoot), position: maps[targetIndex].sourcePosition }; // Closest pos } function getSourceFileLike(fileName: string, location: string): SourceFileLike | undefined { // Lookup file in program, if provided const path = toPath(fileName, location, host.getCanonicalFileName); - const file = program && program.getSourceFile(path); + const file = program && program.getSourceFileByPath(path); // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file if (!file || file.resolvedPath !== path) { // Otherwise check the cache (which may hit disk) diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index 25e6fb9c12..cf903bb1f2 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -23,8 +23,12 @@ namespace ts { let sourcemappedFileCache: SourceFileLikeCache; return { tryGetOriginalLocation, tryGetGeneratedLocation, toLineColumnOffset, clearCache }; + function toPath(fileName: string) { + return ts.toPath(fileName, currentDirectory, getCanonicalFileName); + } + function scanForSourcemapURL(fileName: string) { - const mappedFile = sourcemappedFileCache.get(toPath(fileName, currentDirectory, getCanonicalFileName)); + const mappedFile = sourcemappedFileCache.get(toPath(fileName)); if (!mappedFile) { return; } @@ -88,7 +92,7 @@ namespace ts { } possibleMapLocations.push(fileName + ".map"); for (const location of possibleMapLocations) { - const mapPath = toPath(location, getDirectoryPath(fileName), getCanonicalFileName); + const mapPath = ts.toPath(location, getDirectoryPath(fileName), getCanonicalFileName); if (host.fileExists(mapPath)) { return convertDocumentToSourceMapper(file, host.readFile(mapPath)!, mapPath); // TODO: GH#18217 } @@ -120,12 +124,16 @@ namespace ts { } function getFile(fileName: string): SourceFileLike | undefined { - return getProgram().getSourceFile(fileName) || sourcemappedFileCache.get(toPath(fileName, currentDirectory, getCanonicalFileName)); + const path = toPath(fileName); + const file = getProgram().getSourceFileByPath(path); + if (file && file.resolvedPath === path) { + return file; + } + return sourcemappedFileCache.get(path); } function toLineColumnOffset(fileName: string, position: number): LineAndCharacter { - const path = toPath(fileName, currentDirectory, getCanonicalFileName); - const file = getProgram().getSourceFile(path) || sourcemappedFileCache.get(path)!; // TODO: GH#18217 + const file = getFile(fileName)!; // TODO: GH#18217 return file.getLineAndCharacterOfPosition(position); } diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index 5bc414903a..171070e2cb 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10562,90 +10562,149 @@ declare class TestLib { }); describe("tsserverProjectSystem with tsbuild projects", () => { - function getProjectFiles(project: string): [File, File] { - return [ - TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), - TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), - ]; - } - - const project = "container"; - const containerLib = getProjectFiles("container/lib"); - const containerExec = getProjectFiles("container/exec"); - const containerCompositeExec = getProjectFiles("container/compositeExec"); - const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); - const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; - - function createHost() { + function createHost(files: ReadonlyArray, rootNames: ReadonlyArray) { const host = createServerHost(files); // ts build should succeed - const solutionBuilder = tscWatch.createSolutionBuilder(host, [containerConfig.path], {}); + const solutionBuilder = tscWatch.createSolutionBuilder(host, rootNames, {}); solutionBuilder.buildAllProjects(); assert.equal(host.getOutput().length, 0); return host; } - it("does not error on container only project", () => { - const host = createHost(); + describe("with container project", () => { + function getProjectFiles(project: string): [File, File] { + return [ + TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"), + TestFSWithWatch.getTsBuildProjectFile(project, "index.ts"), + ]; + } - // Open external project for the folder - const session = createSession(host); - const service = session.getProjectService(); - service.openExternalProjects([{ - projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project), - rootFiles: files.map(f => ({ fileName: f.path })), - options: {} - }]); - checkNumberOfProjects(service, { configuredProjects: 4 }); - files.forEach(f => { - const args: protocol.FileRequestArgs = { - file: f.path, - projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined - }; - const syntaxDiagnostics = session.executeCommandSeq({ - command: protocol.CommandTypes.SyntacticDiagnosticsSync, - arguments: args + const project = "container"; + const containerLib = getProjectFiles("container/lib"); + const containerExec = getProjectFiles("container/exec"); + const containerCompositeExec = getProjectFiles("container/compositeExec"); + const containerConfig = TestFSWithWatch.getTsBuildProjectFile(project, "tsconfig.json"); + const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; + + it("does not error on container only project", () => { + const host = createHost(files, [containerConfig.path]); + + // Open external project for the folder + const session = createSession(host); + const service = session.getProjectService(); + service.openExternalProjects([{ + projectFileName: TestFSWithWatch.getTsBuildProjectFilePath(project, project), + rootFiles: files.map(f => ({ fileName: f.path })), + options: {} + }]); + checkNumberOfProjects(service, { configuredProjects: 4 }); + files.forEach(f => { + const args: protocol.FileRequestArgs = { + file: f.path, + projectFileName: endsWith(f.path, "tsconfig.json") ? f.path : undefined + }; + const syntaxDiagnostics = session.executeCommandSeq({ + command: protocol.CommandTypes.SyntacticDiagnosticsSync, + arguments: args + }).response; + assert.deepEqual(syntaxDiagnostics, []); + const semanticDiagnostics = session.executeCommandSeq({ + command: protocol.CommandTypes.SemanticDiagnosticsSync, + arguments: args + }).response; + assert.deepEqual(semanticDiagnostics, []); + }); + const containerProject = service.configuredProjects.get(containerConfig.path)!; + checkProjectActualFiles(containerProject, [containerConfig.path]); + const optionsDiagnostics = session.executeCommandSeq({ + command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, + arguments: { projectFileName: containerProject.projectName } }).response; - assert.deepEqual(syntaxDiagnostics, []); - const semanticDiagnostics = session.executeCommandSeq({ - command: protocol.CommandTypes.SemanticDiagnosticsSync, - arguments: args - }).response; - assert.deepEqual(semanticDiagnostics, []); + assert.deepEqual(optionsDiagnostics, []); + }); + + it("can successfully find references with --out options", () => { + const host = createHost(files, [containerConfig.path]); + const session = createSession(host); + openFilesForSession([containerCompositeExec[1]], session); + const service = session.getProjectService(); + checkNumberOfProjects(service, { configuredProjects: 1 }); + const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.Rename, + arguments: { + file: containerCompositeExec[1].path, + ...locationOfMyConst + } + }).response as protocol.RenameResponseBody; + + + const myConstLen = "myConst".length; + const locationOfMyConstInLib = protocolLocationFromSubstring(containerLib[1].content, "myConst"); + assert.deepEqual(response.locs, [ + { file: containerCompositeExec[1].path, locs: [{ start: locationOfMyConst, end: { line: locationOfMyConst.line, offset: locationOfMyConst.offset + myConstLen } }] }, + { file: containerLib[1].path, locs: [{ start: locationOfMyConstInLib, end: { line: locationOfMyConstInLib.line, offset: locationOfMyConstInLib.offset + myConstLen } }] } + ]); }); - const containerProject = service.configuredProjects.get(containerConfig.path)!; - checkProjectActualFiles(containerProject, [containerConfig.path]); - const optionsDiagnostics = session.executeCommandSeq({ - command: protocol.CommandTypes.CompilerOptionsDiagnosticsFull, - arguments: { projectFileName: containerProject.projectName } - }).response; - assert.deepEqual(optionsDiagnostics, []); }); - it("can successfully find references with --out options", () => { - const host = createHost(); + it("can go to definition correctly", () => { + const projectLocation = "/user/username/projects/myproject"; + const dependecyLocation = `${projectLocation}/dependency`; + const mainLocation = `${projectLocation}/main`; + const dependencyTs: File = { + path: `${dependecyLocation}/FnS.ts`, + content: `export function fn1() { } +export function fn2() { } +export function fn3() { } +export function fn4() { } +export function fn5() { }` + }; + const dependencyConfig: File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true } }) + }; + + const mainTs: File = { + path: `${mainLocation}/main.ts`, + content: `import { + fn1, fn2, fn3, fn4, fn5 +} from '../dependency/fns' + +fn1(); +fn2(); +fn3(); +fn4(); +fn5();` + }; + const mainConfig: File = { + path: `${mainLocation}/tsconfig.json`, + content: JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true }, + references: [{ path: "../dependency" }] + }) + }; + + const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile]; + const host = createHost(files, [mainConfig.path]); const session = createSession(host); - openFilesForSession([containerCompositeExec[1]], session); const service = session.getProjectService(); + openFilesForSession([mainTs], session); checkNumberOfProjects(service, { configuredProjects: 1 }); - const locationOfMyConst = protocolLocationFromSubstring(containerCompositeExec[1].content, "myConst"); - const response = session.executeCommandSeq({ - command: protocol.CommandTypes.Rename, - arguments: { - file: containerCompositeExec[1].path, - ...locationOfMyConst - } - }).response as protocol.RenameResponseBody; - - - const myConstLen = "myConst".length; - const locationOfMyConstInLib = protocolLocationFromSubstring(containerLib[1].content, "myConst"); - assert.deepEqual(response.locs, [ - { file: containerCompositeExec[1].path, locs: [{ start: locationOfMyConst, end: { line: locationOfMyConst.line, offset: locationOfMyConst.offset + myConstLen } }] }, - { file: containerLib[1].path, locs: [{ start: locationOfMyConstInLib, end: { line: locationOfMyConstInLib.line, offset: locationOfMyConstInLib.offset + myConstLen } }] } - ]); + checkProjectActualFiles(service.configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, `${dependecyLocation}/fns.d.ts`]); + for (let i = 0; i < 5; i++) { + const startSpan = { line: i + 5, offset: 1 }; + const response = session.executeCommandSeq({ + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...startSpan } + }).response as protocol.DefinitionInfoAndBoundSpan; + assert.deepEqual(response, { + definitions: [{ file: dependencyTs.path, start: { line: i + 1, offset: 17 }, end: { line: i + 1, offset: 20 } }], + textSpan: { start: startSpan, end: { line: startSpan.line, offset: startSpan.offset + 3 } } + }); + } }); });