diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 6fee35d2448..9a4e26a2903 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1411,6 +1411,13 @@ namespace ts { for (const newSourceFile of newSourceFiles) { const filePath = newSourceFile.path; addFileToFilesByName(newSourceFile, filePath, newSourceFile.resolvedPath); + if (useSourceOfReference) { + const redirectProject = getProjectReferenceRedirectProject(newSourceFile.fileName); + if (redirectProject && !(redirectProject.commandLine.options.outFile || redirectProject.commandLine.options.out)) { + const redirect = getProjectReferenceOutputName(redirectProject, newSourceFile.fileName); + addFileToFilesByName(newSourceFile, toPath(redirect), /*redirectedPath*/ undefined); + } + } // Set the file as found during node modules search if it was found that way in old progra, if (oldProgram.isSourceFileFromExternalLibrary(oldProgram.getSourceFileByPath(filePath)!)) { sourceFilesFoundSearchingNodeModules.set(filePath, true); @@ -2240,9 +2247,11 @@ namespace ts { if (useSourceOfReference) { const source = getSourceOfProjectReferenceRedirect(fileName); if (source) { - return isString(source) ? + const file = isString(source) ? findSourceFile(source, toPath(source), isDefaultLib, ignoreNoDefaultLib, refFile, refPos, refEnd, packageId) : undefined; + if (file) addFileToFilesByName(file, path, /*redirectedPath*/ undefined); + return file; } } const originalFileName = fileName; diff --git a/src/services/services.ts b/src/services/services.ts index a8b05402e76..aa6ea119225 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1148,10 +1148,11 @@ namespace ts { useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, getCurrentDirectory: () => currentDirectory, getProgram, - fileExists: host.fileExists && (f => host.fileExists!(f)), - readFile: host.readFile && ((f, encoding) => host.readFile!(f, encoding)), - getDocumentPositionMapper: host.getDocumentPositionMapper && ((generatedFileName, sourceFileName) => host.getDocumentPositionMapper!(generatedFileName, sourceFileName)), - getSourceFileLike: host.getSourceFileLike && (f => host.getSourceFileLike!(f)), + fileExists: maybeBind(host, host.fileExists), + readFile: maybeBind(host, host.readFile), + getDocumentPositionMapper: maybeBind(host, host.getDocumentPositionMapper), + useSourceInsteadOfReferenceRedirect: maybeBind(host, host.useSourceInsteadOfReferenceRedirect), + getSourceFileLike: maybeBind(host, host.getSourceFileLike), log }); diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index d07c21a9f43..de590d4e5b4 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -17,6 +17,7 @@ namespace ts { readFile?(path: string, encoding?: string): string | undefined; getSourceFileLike?(fileName: string): SourceFileLike | undefined; getDocumentPositionMapper?(generatedFileName: string, sourceFileName?: string): DocumentPositionMapper | undefined; + /* @internal */ useSourceInsteadOfReferenceRedirect?(): boolean; log(s: string): void; } @@ -70,6 +71,11 @@ namespace ts { if (!sourceFile) return undefined; const program = host.getProgram()!; + // If this is source file of project reference source (instead of redirect) there is no generated position + if (host.useSourceInsteadOfReferenceRedirect && + host.useSourceInsteadOfReferenceRedirect() && + program.getResolvedProjectReferenceToRedirect(sourceFile.fileName)) return undefined; + const options = program.getCompilerOptions(); const outPath = options.outFile || options.out; diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 9c02b670bb7..5320cfe13bc 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -373,7 +373,7 @@ fn5(); verifyInfosWithRandom(session, host, openInfos, closedInfos, otherWatchedFiles); } - function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: true) { + function verifyInfosWhenNoMapFile(session: TestSession, host: TestServerHost, dependencyTsOK?: boolean) { const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); verifyInfosWithRandom( session, @@ -384,46 +384,48 @@ fn5(); ); } - function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, watchDts: boolean, dependencyTsAndMapOk?: true) { + function verifyInfosWhenNoDtsFile(session: TestSession, host: TestServerHost, watchDts: boolean, dependencyTsOk?: boolean, depedencyMapOk?: boolean) { const dtsMapClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsMapPath ? f : undefined); const dtsClosedInfo = firstDefined(closedInfos, f => f.toLowerCase() === dtsPath ? f : undefined); verifyInfosWithRandom( session, host, openInfos, - closedInfos.filter(f => (dependencyTsAndMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsAndMapOk || f !== dependencyTs.path)), + closedInfos.filter(f => (depedencyMapOk || f !== dtsMapClosedInfo) && f !== dtsClosedInfo && (dependencyTsOk || f !== dependencyTs.path)), dtsClosedInfo && watchDts ? otherWatchedFiles.concat(dtsClosedInfo) : otherWatchedFiles ); } - function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { + function verifyDocumentPositionMapper(session: TestSession, dependencyMap: server.ScriptInfo | undefined, documentPositionMapper: server.ScriptInfo["documentPositionMapper"], notEqual?: true) { assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap); - if (notEqual) { - assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); - } - else { - assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + if (dependencyMap) { + if (notEqual) { + assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } + else { + assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper); + } } } - function action(verifier: DocumentPositionMapperVerifier, fn: number, session: TestSession) { - const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts } = verifier.actionGetter(fn); - const { response } = session.executeCommandSeq(request); - return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, verifier }; + function action(verifier: DocumentPositionMapperVerifier, fn: number, session: TestSession, useDependencyChange?: boolean) { + const { reqName, request, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, requestDependencyChange, expectedResponseDependencyChange } = verifier.actionGetter(fn); + const { response } = session.executeCommandSeq(useDependencyChange ? requestDependencyChange || request : request); + return { reqName, response, expectedResponse, expectedResponseNoMap, expectedResponseNoDts, expectedResponseDependencyChange, verifier }; } function firstAction(session: TestSession) { verifier.forEach(v => action(v, 1, session)); } - function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: true) { + function verifyAllFnActionWorker(session: TestSession, verifyAction: (result: ReturnType, dtsInfo: server.ScriptInfo | undefined, isFirst: boolean) => void, dtsAbsent?: boolean, useDependencyChange?: boolean) { // action let isFirst = true; for (const v of verifier) { for (let fn = 1; fn <= 5; fn++) { - const result = action(v, fn, session); + const result = action(v, fn, session, useDependencyChange); const dtsInfo = session.getProjectService().filenameToScriptInfo.get(dtsPath); if (dtsAbsent) { assert.isUndefined(dtsInfo); @@ -437,33 +439,38 @@ fn5(); } } + function dtsAbsent() { + return withRefs && !contains(closedInfos, dtsPath, (a, b) => a.toLowerCase() === b.toLowerCase()); + } + function verifyAllFnAction( session: TestSession, host: TestServerHost, firstDocumentPositionMapperNotEquals?: true, dependencyMap?: server.ScriptInfo, - documentPositionMapper?: server.ScriptInfo["documentPositionMapper"] + documentPositionMapper?: server.ScriptInfo["documentPositionMapper"], + useDependencyChange?: boolean ) { // action - verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedResponse, `Failed on ${reqName}`); + verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseDependencyChange }, dtsInfo, isFirst) => { + assert.deepEqual(response, useDependencyChange ? expectedResponseDependencyChange || expectedResponse : expectedResponse, `Failed on ${reqName}`); verifyInfos(session, host); - assert.equal(dtsInfo!.sourceMapFilePath, dtsMapPath); + if (dtsInfo) assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath); if (isFirst) { if (dependencyMap) { verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, firstDocumentPositionMapperNotEquals); documentPositionMapper = dependencyMap.documentPositionMapper; } else { - dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; - documentPositionMapper = dependencyMap.documentPositionMapper; + dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); + documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; } } else { - verifyDocumentPositionMapper(session, dependencyMap!, documentPositionMapper); + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper); } - }); - return { dependencyMap: dependencyMap!, documentPositionMapper }; + }, dtsAbsent(), useDependencyChange); + return { dependencyMap, documentPositionMapper }; } function verifyAllFnActionWithNoMap( @@ -474,19 +481,21 @@ fn5(); let sourceMapFilePath: server.ScriptInfo["sourceMapFilePath"]; // action verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoMap }, dtsInfo, isFirst) => { - assert.deepEqual(response, expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); + assert.deepEqual(response, withRefs ? expectedResponse : expectedResponseNoMap || expectedResponse, `Failed on ${reqName}`); verifyInfosWhenNoMapFile(session, host, dependencyTsOK); assert.isUndefined(session.getProjectService().filenameToScriptInfo.get(dtsMapPath)); - if (isFirst) { - assert.isNotString(dtsInfo!.sourceMapFilePath); - assert.isNotFalse(dtsInfo!.sourceMapFilePath); - assert.isDefined(dtsInfo!.sourceMapFilePath); - sourceMapFilePath = dtsInfo!.sourceMapFilePath; + if (!withRefs) { + if (isFirst) { + assert.isNotString(dtsInfo!.sourceMapFilePath); + assert.isNotFalse(dtsInfo!.sourceMapFilePath); + assert.isDefined(dtsInfo!.sourceMapFilePath); + sourceMapFilePath = dtsInfo!.sourceMapFilePath; + } + else { + assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); + } } - else { - assert.equal(dtsInfo!.sourceMapFilePath, sourceMapFilePath); - } - }); + }, dtsAbsent()); return sourceMapFilePath; } @@ -497,7 +506,7 @@ fn5(); ) { // action verifyAllFnActionWorker(session, ({ reqName, response, expectedResponse, expectedResponseNoDts, verifier }) => { - assert.deepEqual(response, expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); + assert.deepEqual(response, withRefs ? expectedResponse : expectedResponseNoDts || expectedResponse, `Failed on ${reqName}`); verifyInfosWhenNoDtsFile( session, host, @@ -505,7 +514,8 @@ fn5(); // instead of succeeding to source file and then mapping using project reference (When using usage location) // But watched if sourcemapper is in source project since we need to keep track of dts to update the source mapper for any potential usages verifier.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath), - dependencyTsAndMapOk, + /*dependencyTsOk*/ withRefs || dependencyTsAndMapOk, + /*dependencyMapOk*/ dependencyTsAndMapOk ); }, /*dtsAbsent*/ true); } @@ -513,14 +523,15 @@ fn5(); function verifyScenarioWithChangesWorker( change: (host: TestServerHost, session: TestSession) => void, afterActionDocumentPositionMapperNotEquals: true | undefined, - timeoutBeforeAction: boolean + timeoutBeforeAction: boolean, + useDependencyChange?: boolean ) { const { host, session } = openTsFile(); // Create DocumentPositionMapper firstAction(session); - const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath)!; - const documentPositionMapper = dependencyMap.documentPositionMapper; + const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); + const documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; // change change(host, session); @@ -531,21 +542,22 @@ fn5(); } // action - verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper); + verifyAllFnAction(session, host, afterActionDocumentPositionMapperNotEquals, dependencyMap, documentPositionMapper, useDependencyChange); } function verifyScenarioWithChanges( scenarioName: string, change: (host: TestServerHost, session: TestSession) => void, - afterActionDocumentPositionMapperNotEquals?: true + afterActionDocumentPositionMapperNotEquals?: true, + useDependencyChange?: boolean ) { describe(scenarioName, () => { it("when timeout occurs before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true); + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ true, useDependencyChange); }); it("when timeout does not occur before request", () => { - verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false); + verifyScenarioWithChangesWorker(change, afterActionDocumentPositionMapperNotEquals, /*timeoutBeforeAction*/ false, useDependencyChange); }); }); } @@ -570,12 +582,12 @@ fn5(); function verifyMainScenarioAndScriptInfoCollectionWithNoMap(session: TestSession, host: TestServerHost, dependencyTsOKInScenario?: true) { // Main scenario action - verifyAllFnActionWithNoMap(session, host, dependencyTsOKInScenario); + verifyAllFnActionWithNoMap(session, host, withRefs || dependencyTsOKInScenario); // Collecting at this point retains dependency.d.ts and map watcher closeFilesForSession([randomFile], session); openFilesForSession([randomFile], session); - verifyInfosWhenNoMapFile(session, host); + verifyInfosWhenNoMapFile(session, host, withRefs); // Closing open file, removes dependencies too closeFilesForSession([...openFiles, randomFile], session); @@ -593,7 +605,8 @@ fn5(); verifyInfosWhenNoDtsFile( session, host, - !!forEach(verifier, v => v.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath)) + !!forEach(verifier, v => v.expectedProjectActualFiles.every(f => f.toLowerCase() !== dtsPath)), + /*dependencyTsOk*/ withRefs ); // Closing open file, removes dependencies too @@ -694,35 +707,26 @@ fn5(); ); if (withRefs) { - it("when defining project source changes", () => { - const { host, session } = openTsFile(); - - // First action - firstAction(session); - - // Make change, without rebuild of solution - if (contains(openInfos, dependencyTs.path)) { - session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } + verifyScenarioWithChanges( + "when defining project source changes", + (host, session) => { + // Make change, without rebuild of solution + if (contains(openInfos, dependencyTs.path)) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } `} - }); - } - else { - host.writeFile(dependencyTs.path, `function fooBar() { } -${dependencyTs.content}`); - } - host.runQueuedTimeoutCallbacks(); - - for (const v of verifier) { - for (let fn = 1; fn <= 5; fn++) { - const { reqName, request, requestDependencyChange, expectedResponseDependencyChange } = v.actionGetter(fn); - const { response } = session.executeCommandSeq(requestDependencyChange || request); - assert.deepEqual(response, expectedResponseDependencyChange, `Failed on ${reqName}`); + }); } - } - }); + else { + host.writeFile(dependencyTs.path, `function fooBar() { } +${dependencyTs.content}`); + } + }, + /*afterActionDocumentPositionMapperNotEquals*/ undefined, + /*useDepedencyChange*/ true + ); } } @@ -730,13 +734,15 @@ ${dependencyTs.content}`); describe(withRefs ? "when main tsconfig has project reference" : "when main tsconfig doesnt have project reference", () => { const usageVerifier: DocumentPositionMapperVerifier = { openFile: mainTs, - expectedProjectActualFiles: [mainTs.path, libFile.path, mainConfig.path, dtsPath], + expectedProjectActualFiles: withRefs ? + [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] : + [mainTs.path, libFile.path, mainConfig.path, dtsPath], actionGetter: gotoDefintinionFromMainTs, openFileLastLine: 14 }; describe("from project that uses dependency", () => { const closedInfos = withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path, dtsPath, dtsMapLocation] : + [dependencyTs.path, dependencyConfig.path, libFile.path] : [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "can go to definition correctly", @@ -764,7 +770,7 @@ ${dependencyTs.content}`); describe("when opening depedency and usage project", () => { const closedInfos = withRefs ? - [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : + [libFile.path, dependencyConfig.path] : [libFile.path, dtsPath, dtsMapLocation]; verifyDocumentPositionMapperUpdates( "goto Definition in usage and rename locations from defining project",