From e98f6b07abd63af79afda147d80072b999bdf0e9 Mon Sep 17 00:00:00 2001 From: Sheetal Nandi Date: Fri, 12 Feb 2021 14:32:23 -0800 Subject: [PATCH] Refactor project references and source map tsserver tests so they are inline for easy editing (#42782) --- src/harness/virtualFileSystemWithWatch.ts | 2 +- src/testRunner/tsconfig.json | 1 + .../unittests/tsserver/projectReferences.ts | 1123 +---- .../tsserver/projectReferencesSourcemap.ts | 4439 +++++++++++++++++ 4 files changed, 4453 insertions(+), 1112 deletions(-) create mode 100644 src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts diff --git a/src/harness/virtualFileSystemWithWatch.ts b/src/harness/virtualFileSystemWithWatch.ts index e17136d67e3..c3fc8440b58 100644 --- a/src/harness/virtualFileSystemWithWatch.ts +++ b/src/harness/virtualFileSystemWithWatch.ts @@ -195,7 +195,7 @@ interface Array { length: number; [n: number]: T; }` checkMap(caption, arrayToMap(actual, identity), expected, /*eachKeyCount*/ undefined); } - export function checkWatchedFiles(host: TestServerHost, expectedFiles: string[], additionalInfo?: string) { + export function checkWatchedFiles(host: TestServerHost, expectedFiles: readonly string[], additionalInfo?: string) { checkMap(`watchedFiles:: ${additionalInfo || ""}::`, host.watchedFiles, expectedFiles, /*eachKeyCount*/ undefined); } diff --git a/src/testRunner/tsconfig.json b/src/testRunner/tsconfig.json index cfd4dc6f9c4..a1985566b92 100644 --- a/src/testRunner/tsconfig.json +++ b/src/testRunner/tsconfig.json @@ -189,6 +189,7 @@ "unittests/tsserver/projectReferenceCompileOnSave.ts", "unittests/tsserver/projectReferenceErrors.ts", "unittests/tsserver/projectReferences.ts", + "unittests/tsserver/projectReferencesSourcemap.ts", "unittests/tsserver/projects.ts", "unittests/tsserver/refactors.ts", "unittests/tsserver/reload.ts", diff --git a/src/testRunner/unittests/tsserver/projectReferences.ts b/src/testRunner/unittests/tsserver/projectReferences.ts index 103143390fc..87cec3a96b7 100644 --- a/src/testRunner/unittests/tsserver/projectReferences.ts +++ b/src/testRunner/unittests/tsserver/projectReferences.ts @@ -1,12 +1,12 @@ namespace ts.projectSystem { - describe("unittests:: tsserver:: with project references and tsbuild", () => { - function createHost(files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) { - const host = createServerHost(files); - // ts build should succeed - tscWatch.ensureErrorFreeBuild(host, rootNames); - return host; - } + export function createHostWithSolutionBuild(files: readonly TestFSWithWatch.FileOrFolderOrSymLink[], rootNames: readonly string[]) { + const host = createServerHost(files); + // ts build should succeed + tscWatch.ensureErrorFreeBuild(host, rootNames); + return host; + } + describe("unittests:: tsserver:: with project references and tsbuild", () => { describe("with container project", () => { function getProjectFiles(project: string): [File, File] { return [ @@ -23,7 +23,7 @@ namespace ts.projectSystem { const files = [libFile, ...containerLib, ...containerExec, ...containerCompositeExec, containerConfig]; it("does not error on container only project", () => { - const host = createHost(files, [containerConfig.path]); + const host = createHostWithSolutionBuild(files, [containerConfig.path]); // Open external project for the folder const session = createSession(host); @@ -60,7 +60,7 @@ namespace ts.projectSystem { }); it("can successfully find references with --out options", () => { - const host = createHost(files, [containerConfig.path]); + const host = createHostWithSolutionBuild(files, [containerConfig.path]); const session = createSession(host); openFilesForSession([containerCompositeExec[1]], session); const service = session.getProjectService(); @@ -101,7 +101,7 @@ namespace ts.projectSystem { path: `/user/username/projects/temp/temp.ts`, content: "let x = 10" }; - const host = createHost(files.concat([tempFile]), [containerConfig.path]); + const host = createHostWithSolutionBuild(files.concat([tempFile]), [containerConfig.path]); const session = createSession(host); openFilesForSession([containerCompositeExec[1]], session); const service = session.getProjectService(); @@ -140,1105 +140,6 @@ namespace ts.projectSystem { }); }); - describe("with main and depedency project", () => { - const dependecyLocation = `${tscWatch.projectRoot}/dependency`; - const dependecyDeclsLocation = `${tscWatch.projectRoot}/decls`; - const mainLocation = `${tscWatch.projectRoot}/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 dependencyTsPath = dependencyTs.path.toLowerCase(); - const dependencyConfig: File = { - path: `${dependecyLocation}/tsconfig.json`, - content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) - }; - - const mainTs: File = { - path: `${mainLocation}/main.ts`, - content: `import { - fn1, - fn2, - fn3, - fn4, - fn5 -} from '../decls/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 randomFile: File = { - path: `${tscWatch.projectRoot}/random/random.ts`, - content: "let a = 10;" - }; - const randomConfig: File = { - path: `${tscWatch.projectRoot}/random/tsconfig.json`, - content: "{}" - }; - const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; - const dtsPath = dtsLocation.toLowerCase() as Path; - const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`; - const dtsMapPath = dtsMapLocation.toLowerCase() as Path; - - const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; - - function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], additionalInfo: string) { - checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo); - checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo); - } - - function verifyInfosWithRandom(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], reqName: string) { - verifyScriptInfos(session, host, openInfos.concat(randomFile.path), closedInfos, otherWatchedFiles.concat(randomConfig.path), reqName); - } - - function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { - verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random"); - } - - function declarationSpan(fn: number): protocol.TextSpanWithContext { - return { - start: { line: fn, offset: 17 }, - end: { line: fn, offset: 20 }, - contextStart: { line: fn, offset: 1 }, - contextEnd: { line: fn, offset: 26 } - }; - } - function importSpan(fn: number): protocol.TextSpanWithContext { - return { - start: { line: fn + 1, offset: 5 }, - end: { line: fn + 1, offset: 8 }, - contextStart: { line: 1, offset: 1 }, - contextEnd: { line: 7, offset: 22 } - }; - } - function usageSpan(fn: number): protocol.TextSpan { - return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; - } - - function goToDefFromMainTs(fn: number): Action { - const textSpan = usageSpan(fn); - const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To dependency - definitions: [definition], - textSpan - } - }; - } - - function goToDefFromMainTsWithNoMap(fn: number): Action { - const textSpan = usageSpan(fn); - const definition = declarationSpan(fn); - const declareSpaceLength = "declare ".length; - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To the dts - definitions: [{ - file: dtsPath, - start: { line: fn, offset: definition.start.offset + declareSpaceLength }, - end: { line: fn, offset: definition.end.offset + declareSpaceLength }, - contextStart: { line: fn, offset: 1 }, - contextEnd: { line: fn, offset: 37 } - }], - textSpan - } - }; - } - - function goToDefFromMainTsWithNoDts(fn: number): Action { - const textSpan = usageSpan(fn); - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // To import declaration - definitions: [{ file: mainTs.path, ...importSpan(fn) }], - textSpan - } - }; - } - - function goToDefFromMainTsWithDependencyChange(fn: number): Action { - const textSpan = usageSpan(fn); - return { - reqName: "goToDef", - request: { - command: protocol.CommandTypes.DefinitionAndBoundSpan, - arguments: { file: mainTs.path, ...textSpan.start } - }, - expectedResponse: { - // Definition on fn + 1 line - definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], - textSpan - } - }; - } - - function goToDefFromMainTsProjectInfoVerifier(withRefs: boolean): ProjectInfoVerifier { - return { - openFile: mainTs, - openFileLastLine: 14, - configFile: mainConfig, - expectedProjectActualFiles: withRefs ? - [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path] : - [mainTs.path, libFile.path, mainConfig.path, dtsPath] - }; - } - - function renameFromDependencyTs(fn: number): Action { - const defSpan = declarationSpan(fn); - const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; - return { - reqName: "rename", - request: { - command: protocol.CommandTypes.Rename, - arguments: { file: dependencyTs.path, ...triggerSpan.start } - }, - expectedResponse: { - info: { - canRename: true, - fileToRename: undefined, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - kind: ScriptElementKind.functionElement, - kindModifiers: "export", - triggerSpan - }, - locs: [ - { file: dependencyTs.path, locs: [defSpan] } - ] - } - }; - } - - function renameFromDependencyTsWithDependencyChange(fn: number): Action { - const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1); - - return { - ...rest, - expectedResponse: { - info: { - ...info as protocol.RenameInfoSuccess, - displayName: `fn${fn}`, - fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, - }, - locs - } - }; - } - - function renameFromDependencyTsProjectInfoVerifier(): ProjectInfoVerifier { - return { - openFile: dependencyTs, - openFileLastLine: 6, - configFile: dependencyConfig, - expectedProjectActualFiles: [dependencyTs.path, libFile.path, dependencyConfig.path] - }; - } - - function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action { - const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); - const { info, locs } = expectedResponse; - return { - reqName, - request, - expectedResponse: { - info, - locs: [ - locs[0], - { - file: mainTs.path, - locs: [ - importSpan(fn), - usageSpan(fn) - ] - } - ] - } - }; - } - - function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action { - const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn); - const { info, locs } = expectedResponse; - return { - reqName, - request, - expectedResponse: { - info, - locs: [ - locs[0], - { - file: mainTs.path, - locs: [ - importSpan(fn), - usageSpan(fn) - ] - } - ] - } - }; - } - - function removePath(array: readonly string[], ...delPaths: string[]) { - return array.filter(a => { - const aLower = a.toLowerCase(); - return delPaths.every(dPath => dPath !== aLower); - }); - } - - interface Action { - reqName: string; - request: Partial; - expectedResponse: Response; - } - interface ActionInfo { - action: (fn: number) => Action; - closedInfos: readonly string[]; - otherWatchedFiles: readonly string[]; - expectsDts: boolean; - expectsMap: boolean; - freshMapInfo?: boolean; - freshDocumentMapper?: boolean; - skipDtsMapCheck?: boolean; - } - type ActionKey = keyof ActionInfoVerifier; - type ActionInfoGetterFn = () => ActionInfo; - type ActionInfoSpreader = [ - ActionKey, // Key to get initial value and pass this value to spread function - (actionInfo: ActionInfo) => Partial> - ]; - type ActionInfoGetter = ActionInfoGetterFn | ActionKey | ActionInfoSpreader; - interface ProjectInfoVerifier { - openFile: File; - openFileLastLine: number; - configFile: File; - expectedProjectActualFiles: readonly string[]; - } - interface ActionInfoVerifier { - main: ActionInfoGetter; - change: ActionInfoGetter; - dtsChange: ActionInfoGetter; - mapChange: ActionInfoGetter; - noMap: ActionInfoGetter; - mapFileCreated: ActionInfoGetter; - mapFileDeleted: ActionInfoGetter; - noDts: ActionInfoGetter; - dtsFileCreated: ActionInfoGetter; - dtsFileDeleted: ActionInfoGetter; - dependencyChange: ActionInfoGetter; - noBuild: ActionInfoGetter; - } - interface DocumentPositionMapperVerifier extends ProjectInfoVerifier, ActionInfoVerifier { - } - - interface VerifierAndWithRefs { - withRefs: boolean; - disableSourceOfProjectReferenceRedirect?: true; - verifier: (withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) => readonly DocumentPositionMapperVerifier[]; - } - - function openFiles(verifiers: readonly DocumentPositionMapperVerifier[]) { - return verifiers.map(v => v.openFile); - } - interface OpenTsFile extends VerifierAndWithRefs { - onHostCreate?: (host: TestServerHost) => void; - } - function openTsFile({ withRefs, disableSourceOfProjectReferenceRedirect, verifier, onHostCreate }: OpenTsFile) { - const host = createHost(files, [mainConfig.path]); - if (!withRefs) { - // Erase project reference - host.writeFile(mainConfig.path, JSON.stringify({ - compilerOptions: { composite: true, declarationMap: true } - })); - } - else if (disableSourceOfProjectReferenceRedirect) { - // Erase project reference - host.writeFile(mainConfig.path, JSON.stringify({ - compilerOptions: { - composite: true, - declarationMap: true, - disableSourceOfProjectReferenceRedirect: !!disableSourceOfProjectReferenceRedirect - }, - references: [{ path: "../dependency" }] - })); - } - if (onHostCreate) { - onHostCreate(host); - } - const session = createSession(host); - const verifiers = verifier(withRefs && !disableSourceOfProjectReferenceRedirect, disableSourceOfProjectReferenceRedirect); - openFilesForSession([...openFiles(verifiers), randomFile], session); - return { host, session, verifiers }; - } - - function checkProject(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[], noDts?: true) { - const service = session.getProjectService(); - checkNumberOfProjects(service, { configuredProjects: 1 + verifiers.length }); - verifiers.forEach(({ configFile, expectedProjectActualFiles }) => { - checkProjectActualFiles( - service.configuredProjects.get(configFile.path.toLowerCase())!, - noDts ? - expectedProjectActualFiles.filter(f => f.toLowerCase() !== dtsPath) : - expectedProjectActualFiles - ); - }); - } - - function firstAction(session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) { - for (const { action } of getActionInfo(verifiers, "main")) { - const { request } = action(1); - session.executeCommandSeq(request); - } - } - - function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) { - const { response } = session.executeCommandSeq(request); - assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`); - } - - function verifyScriptInfoPresence(session: TestSession, path: string, expectedToBePresent: boolean, reqName: string) { - const info = session.getProjectService().filenameToScriptInfo.get(path); - if (expectedToBePresent) { - assert.isDefined(info, `${reqName}:: ${path} expected to be present`); - } - else { - assert.isUndefined(info, `${reqName}:: ${path} expected to be not present`); - } - return info; - } - - interface VerifyDocumentPositionMapper { - session: TestSession; - dependencyMap: server.ScriptInfo | undefined; - documentPositionMapper: server.ScriptInfo["documentPositionMapper"]; - equal: boolean; - debugInfo: string; - } - function verifyDocumentPositionMapper({ session, dependencyMap, documentPositionMapper, equal, debugInfo }: VerifyDocumentPositionMapper) { - assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, debugInfo); - if (dependencyMap) { - if (equal) { - assert.strictEqual(dependencyMap.documentPositionMapper, documentPositionMapper, debugInfo); - } - else { - assert.notStrictEqual(dependencyMap.documentPositionMapper, documentPositionMapper, debugInfo); - } - } - } - - function getActionInfoOfVerfier(verifier: DocumentPositionMapperVerifier, actionKey: ActionKey): ActionInfo { - const actionInfoGetter = verifier[actionKey]; - if (isString(actionInfoGetter)) { - return getActionInfoOfVerfier(verifier, actionInfoGetter); - } - - if (isArray(actionInfoGetter)) { - const initialValue = getActionInfoOfVerfier(verifier, actionInfoGetter[0]); - return { - ...initialValue, - ...actionInfoGetter[1](initialValue) - }; - } - - return actionInfoGetter(); - } - - function getActionInfo(verifiers: readonly DocumentPositionMapperVerifier[], actionKey: ActionKey): ActionInfo[] { - return verifiers.map(v => getActionInfoOfVerfier(v, actionKey)); - } - - interface VerifyAllFnAction { - session: TestSession; - host: TestServerHost; - verifiers: readonly DocumentPositionMapperVerifier[]; - actionKey: ActionKey; - sourceMapPath?: server.ScriptInfo["sourceMapFilePath"]; - dependencyMap?: server.ScriptInfo | undefined; - documentPositionMapper?: server.ScriptInfo["documentPositionMapper"]; - } - interface VerifyAllFnActionResult { - actionInfos: readonly ActionInfo[]; - actionKey: ActionKey; - dependencyMap: server.ScriptInfo | undefined; - documentPositionMapper: server.ScriptInfo["documentPositionMapper"] | undefined; - } - function verifyAllFnAction({ - session, - host, - verifiers, - actionKey, - dependencyMap, - documentPositionMapper, - }: VerifyAllFnAction): VerifyAllFnActionResult { - const actionInfos = getActionInfo(verifiers, actionKey); - let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; - // action - let first = true; - for (const { - action, - closedInfos, - otherWatchedFiles, - expectsDts, - expectsMap, - freshMapInfo, - freshDocumentMapper, - skipDtsMapCheck - } of actionInfos) { - for (let fn = 1; fn <= 5; fn++) { - const fnAction = action(fn); - verifyAction(session, fnAction); - const debugInfo = `${actionKey}:: ${fnAction.reqName}:: ${fn}`; - const dtsInfo = verifyScriptInfoPresence(session, dtsPath, expectsDts, debugInfo); - const dtsMapInfo = verifyScriptInfoPresence(session, dtsMapPath, expectsMap, debugInfo); - verifyInfosWithRandom( - session, - host, - openFiles(verifiers).map(f => f.path), - closedInfos, - otherWatchedFiles, - debugInfo - ); - - if (dtsInfo) { - if (first || (fn === 1 && freshMapInfo)) { - if (!skipDtsMapCheck) { - if (dtsMapInfo) { - assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, debugInfo); - } - else { - assert.isNotString(dtsInfo.sourceMapFilePath, debugInfo); - assert.isNotFalse(dtsInfo.sourceMapFilePath, debugInfo); - assert.isDefined(dtsInfo.sourceMapFilePath, debugInfo); - } - } - } - else { - assert.equal(dtsInfo.sourceMapFilePath, sourceMapPath, debugInfo); - } - } - - if (!first && (fn !== 1 || !freshMapInfo)) { - verifyDocumentPositionMapper({ - session, - dependencyMap, - documentPositionMapper, - equal: fn !== 1 || !freshDocumentMapper, - debugInfo - }); - } - sourceMapPath = dtsInfo && dtsInfo.sourceMapFilePath; - dependencyMap = dtsMapInfo; - documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; - first = false; - } - } - - return { actionInfos, actionKey, dependencyMap, documentPositionMapper }; - } - - function verifyScriptInfoCollection( - session: TestSession, - host: TestServerHost, - verifiers: readonly DocumentPositionMapperVerifier[], - { dependencyMap, documentPositionMapper, actionInfos, actionKey }: VerifyAllFnActionResult - ) { - // Collecting at this point retains dependency.d.ts and map - closeFilesForSession([randomFile], session); - openFilesForSession([randomFile], session); - - const { closedInfos, otherWatchedFiles } = last(actionInfos); - const debugInfo = `${actionKey} Collection`; - verifyInfosWithRandom( - session, - host, - openFiles(verifiers).map(f => f.path), - closedInfos, - otherWatchedFiles, - debugInfo - ); - verifyDocumentPositionMapper({ - session, - dependencyMap, - documentPositionMapper, - equal: true, - debugInfo - }); - - // Closing open file, removes dependencies too - closeFilesForSession([...openFiles(verifiers), randomFile], session); - openFilesForSession([randomFile], session); - verifyOnlyRandomInfos(session, host); - } - - function verifyScenarioAndScriptInfoCollection( - session: TestSession, - host: TestServerHost, - verifiers: readonly DocumentPositionMapperVerifier[], - actionKey: ActionKey, - noDts?: true - ) { - // Main scenario action - const result = verifyAllFnAction({ session, host, verifiers, actionKey }); - checkProject(session, verifiers, noDts); - verifyScriptInfoCollection(session, host, verifiers, result); - } - - function verifyScenarioWithChangesWorker( - { - scenarioName, - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - change, - afterChangeActionKey - }: VerifyScenarioWithChanges, - timeoutBeforeAction: boolean, - ) { - it(scenarioName, () => { - const { host, session, verifiers } = openTsFile({ verifier, withRefs, disableSourceOfProjectReferenceRedirect }); - - // Create DocumentPositionMapper - firstAction(session, verifiers); - const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); - const documentPositionMapper = dependencyMap && dependencyMap.documentPositionMapper; - - // change - change(host, session, verifiers); - if (timeoutBeforeAction) { - host.runQueuedTimeoutCallbacks(); - checkProject(session, verifiers); - verifyDocumentPositionMapper({ - session, - dependencyMap, - documentPositionMapper, - equal: true, - debugInfo: "After change timeout" - }); - } - - // action - verifyAllFnAction({ - session, - host, - verifiers, - actionKey: afterChangeActionKey, - dependencyMap, - documentPositionMapper - }); - }); - } - - interface VerifyScenarioWithChanges extends VerifierAndWithRefs { - scenarioName: string; - change: (host: TestServerHost, session: TestSession, verifiers: readonly DocumentPositionMapperVerifier[]) => void; - afterChangeActionKey: ActionKey; - } - function verifyScenarioWithChanges(verify: VerifyScenarioWithChanges) { - describe("when timeout occurs before request", () => { - verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ true); - }); - - describe("when timeout does not occur before request", () => { - verifyScenarioWithChangesWorker(verify, /*timeoutBeforeAction*/ false); - }); - } - - interface VerifyScenarioWhenFileNotPresent extends VerifierAndWithRefs { - scenarioName: string; - fileLocation: string; - fileNotPresentKey: ActionKey; - fileCreatedKey: ActionKey; - fileDeletedKey: ActionKey; - noDts?: true; - } - function verifyScenarioWhenFileNotPresent({ - scenarioName, - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - fileLocation, - fileNotPresentKey, - fileCreatedKey, - fileDeletedKey, - noDts - }: VerifyScenarioWhenFileNotPresent) { - describe(scenarioName, () => { - it("when file is not present", () => { - const { host, session, verifiers } = openTsFile({ - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - onHostCreate: host => host.deleteFile(fileLocation) - }); - checkProject(session, verifiers, noDts); - - verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileNotPresentKey, noDts); - }); - - it("when file is created after actions on projects", () => { - let fileContents: string | undefined; - const { host, session, verifiers } = openTsFile({ - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - onHostCreate: host => { - fileContents = host.readFile(fileLocation); - host.deleteFile(fileLocation); - } - }); - firstAction(session, verifiers); - - host.writeFile(fileLocation, fileContents!); - verifyScenarioAndScriptInfoCollection(session, host, verifiers, fileCreatedKey); - }); - - it("when file is deleted after actions on the projects", () => { - const { host, session, verifiers } = openTsFile({ verifier, disableSourceOfProjectReferenceRedirect, withRefs }); - firstAction(session, verifiers); - - // The dependency file is deleted when orphan files are collected - host.deleteFile(fileLocation); - // Verify with deleted action key - verifyAllFnAction({ session, host, verifiers, actionKey: fileDeletedKey }); - checkProject(session, verifiers, noDts); - - // Script info collection should behave as fileNotPresentKey - verifyScriptInfoCollection( - session, - host, - verifiers, - { - actionInfos: getActionInfo(verifiers, fileNotPresentKey), - actionKey: fileNotPresentKey, - dependencyMap: undefined, - documentPositionMapper: undefined - } - ); - }); - }); - } - - function verifyScenarioWorker({ mainScenario, verifier }: VerifyScenario, withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) { - it(mainScenario, () => { - const { host, session, verifiers } = openTsFile({ withRefs, disableSourceOfProjectReferenceRedirect, verifier }); - checkProject(session, verifiers); - verifyScenarioAndScriptInfoCollection(session, host, verifiers, "main"); - }); - - // Edit - verifyScenarioWithChanges({ - scenarioName: "when usage file changes, document position mapper doesnt change", - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - change: (_host, session, verifiers) => verifiers.forEach( - verifier => session.executeCommandSeq({ - command: protocol.CommandTypes.Change, - arguments: { - file: verifier.openFile.path, - line: verifier.openFileLastLine, - offset: 1, - endLine: verifier.openFileLastLine, - endOffset: 1, - insertString: "const x = 10;" - } - }) - ), - afterChangeActionKey: "change" - }); - - // Edit dts to add new fn - verifyScenarioWithChanges({ - scenarioName: "when dependency .d.ts changes, document position mapper doesnt change", - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - change: host => host.writeFile( - dtsLocation, - host.readFile(dtsLocation)!.replace( - "//# sourceMappingURL=FnS.d.ts.map", - `export declare function fn6(): void; -//# sourceMappingURL=FnS.d.ts.map` - ) - ), - afterChangeActionKey: "dtsChange" - }); - - // Edit map file to represent added new line - verifyScenarioWithChanges({ - scenarioName: "when dependency file's map changes", - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - change: host => host.writeFile( - dtsMapLocation, - `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` - ), - afterChangeActionKey: "mapChange" - }); - - verifyScenarioWhenFileNotPresent({ - scenarioName: "with depedency files map file", - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - fileLocation: dtsMapLocation, - fileNotPresentKey: "noMap", - fileCreatedKey: "mapFileCreated", - fileDeletedKey: "mapFileDeleted" - }); - - verifyScenarioWhenFileNotPresent({ - scenarioName: "with depedency .d.ts file", - verifier, - withRefs, - disableSourceOfProjectReferenceRedirect, - fileLocation: dtsLocation, - fileNotPresentKey: "noDts", - fileCreatedKey: "dtsFileCreated", - fileDeletedKey: "dtsFileDeleted", - noDts: true - }); - - if (withRefs && !disableSourceOfProjectReferenceRedirect) { - verifyScenarioWithChanges({ - scenarioName: "when defining project source changes", - verifier, - withRefs, - change: (host, session, verifiers) => { - // Make change, without rebuild of solution - if (contains(openFiles(verifiers), dependencyTs)) { - 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}`); - } - }, - afterChangeActionKey: "dependencyChange" - }); - - it("when projects are not built", () => { - const host = createServerHost(files); - const session = createSession(host); - const verifiers = verifier(withRefs); - openFilesForSession([...openFiles(verifiers), randomFile], session); - verifyScenarioAndScriptInfoCollection(session, host, verifiers, "noBuild"); - }); - } - } - - interface VerifyScenario { - mainScenario: string; - verifier: (withRefs: boolean, disableSourceOfProjectReferenceRedirect?: true) => readonly DocumentPositionMapperVerifier[]; - } - function verifyScenario(scenario: VerifyScenario) { - describe(scenario.mainScenario, () => { - describe("when main tsconfig doesnt have project reference", () => { - verifyScenarioWorker(scenario, /*withRefs*/ false); - }); - describe("when main tsconfig has project reference", () => { - verifyScenarioWorker(scenario, /*withRefs*/ true); - }); - describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { - verifyScenarioWorker(scenario, /*withRefs*/ true, /*disableSourceOfProjectReferenceRedirect*/ true); - }); - }); - } - - describe("from project that uses dependency", () => { - verifyScenario({ - mainScenario: "can go to definition correctly", - verifier: (withRefs, disableSourceOfProjectReferenceRedirect) => [ - { - ...goToDefFromMainTsProjectInfoVerifier(withRefs), - main: () => ({ - action: goToDefFromMainTs, - closedInfos: withRefs ? - [dependencyTs.path, dependencyConfig.path, libFile.path] : - disableSourceOfProjectReferenceRedirect ? - [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : - [dependencyTs.path, libFile.path, dtsPath, dtsMapLocation], - otherWatchedFiles: [mainConfig.path], - expectsDts: !withRefs, // Dts script info present only if no project reference - expectsMap: !withRefs // Map script info present only if no project reference - }), - change: "main", - dtsChange: "main", - mapChange: ["main", () => ({ - freshDocumentMapper: true - })], - noMap: withRefs ? - "main" : - ["main", main => ({ - action: goToDefFromMainTsWithNoMap, - // Because map is deleted, dts and dependency are released - closedInfos: removePath(main.closedInfos, dtsMapPath, dependencyTsPath), - // Watches deleted file - otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), - expectsMap: false - })], - mapFileCreated: "main", - mapFileDeleted: withRefs ? - "main" : - ["noMap", noMap => ({ - // The script info for depedency is collected only after file open - closedInfos: noMap.closedInfos.concat(dependencyTs.path) - })], - noDts: withRefs ? - "main" : - ["main", main => ({ - action: goToDefFromMainTsWithNoDts, - // No dts, no map, no dependency - closedInfos: removePath(main.closedInfos, dtsPath, dtsMapPath, dependencyTsPath), - expectsDts: false, - expectsMap: false - })], - dtsFileCreated: "main", - dtsFileDeleted: withRefs ? - "main" : - ["noDts", noDts => ({ - // The script info for map is collected only after file open - closedInfos: noDts.closedInfos.concat(dependencyTs.path, dtsMapLocation), - expectsMap: true - })], - dependencyChange: ["main", () => ({ - action: goToDefFromMainTsWithDependencyChange, - })], - noBuild: "noDts" - } - ] - }); - }); - - describe("from defining project", () => { - verifyScenario({ - mainScenario: "rename locations from dependency", - verifier: () => [ - { - ...renameFromDependencyTsProjectInfoVerifier(), - main: () => ({ - action: renameFromDependencyTs, - closedInfos: [libFile.path, dtsLocation, dtsMapLocation], - otherWatchedFiles: [dependencyConfig.path], - expectsDts: true, - expectsMap: true - }), - change: "main", - dtsChange: "main", - mapChange: ["main", () => ({ - freshDocumentMapper: true - })], - noMap: ["main", main => ({ - // No map - closedInfos: removePath(main.closedInfos, dtsMapPath), - // watch map - otherWatchedFiles: [...main.otherWatchedFiles, dtsMapLocation], - expectsMap: false - })], - mapFileCreated: "main", - mapFileDeleted: "noMap", - noDts: ["main", main => ({ - // no dts or map since dts itself doesnt exist - closedInfos: removePath(main.closedInfos, dtsMapPath, dtsPath), - // watch deleted file - otherWatchedFiles: [...main.otherWatchedFiles, dtsLocation], - expectsDts: false, - expectsMap: false - })], - dtsFileCreated: "main", - dtsFileDeleted: ["noDts", noDts => ({ - // Map is collected after file open - closedInfos: noDts.closedInfos.concat(dtsMapLocation), - expectsMap: true - })], - dependencyChange: ["main", () => ({ - action: renameFromDependencyTsWithDependencyChange - })], - noBuild: "noDts" - } - ] - }); - }); - - describe("when opening depedency and usage project", () => { - verifyScenario({ - mainScenario: "goto Definition in usage and rename locations from defining project", - verifier: (withRefs, disableSourceOfProjectReferenceRedirect) => [ - { - ...goToDefFromMainTsProjectInfoVerifier(withRefs), - main: () => ({ - action: goToDefFromMainTs, - // DependencyTs is open, so omit it from closed infos - closedInfos: withRefs ? - [dependencyConfig.path, libFile.path] : - disableSourceOfProjectReferenceRedirect ? - [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : - [libFile.path, dtsPath, dtsMapLocation], - otherWatchedFiles: withRefs || disableSourceOfProjectReferenceRedirect ? - [mainConfig.path] : // dependencyConfig is in closed info - [mainConfig.path, dependencyConfig.path], - expectsDts: !withRefs, // Dts script info present only if no project reference - expectsMap: !withRefs // Map script info present only if no project reference - }), - change: withRefs ? - ["main", main => ({ - // Because before this rename is done the closed info remains same as rename's main operation - closedInfos: main.closedInfos.concat(dtsLocation, dtsMapLocation), - expectsDts: true, - expectsMap: true - })] : - "main", - dtsChange: "change", - mapChange: "change", - noMap: withRefs ? - "main" : - ["main", main => ({ - action: goToDefFromMainTsWithNoMap, - closedInfos: removePath(main.closedInfos, dtsMapPath), - otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), - expectsMap: false - })], - mapFileCreated: withRefs ? - ["main", main => ({ - // Because before this rename is done the closed info remains same as rename's main - closedInfos: main.closedInfos.concat(dtsLocation), - expectsDts: true, - // This operation doesnt need map so the map info path in dts is not refreshed - skipDtsMapCheck: withRefs - })] : - "main", - mapFileDeleted: withRefs ? - ["noMap", noMap => ({ - // Because before this rename is done the closed info remains same as rename's noMap operation - closedInfos: noMap.closedInfos.concat(dtsLocation), - expectsDts: true, - // This operation doesnt need map so the map info path in dts is not refreshed - skipDtsMapCheck: true - })] : - "noMap", - noDts: withRefs ? - "main" : - ["main", main => ({ - action: goToDefFromMainTsWithNoDts, - closedInfos: removePath(main.closedInfos, dtsMapPath, dtsPath), - expectsDts: false, - expectsMap: false - })], - dtsFileCreated: withRefs ? - ["main", main => ({ - // Since the project for dependency is not updated, the watcher from rename for dts still there - otherWatchedFiles: main.otherWatchedFiles.concat(dtsLocation) - })] : - "main", - dtsFileDeleted: ["noDts", noDts => ({ - // Map collection after file open - closedInfos: noDts.closedInfos.concat(dtsMapLocation), - expectsMap: true - })], - dependencyChange: ["change", () => ({ - action: goToDefFromMainTsWithDependencyChange, - })], - noBuild: "noDts" - }, - { - ...renameFromDependencyTsProjectInfoVerifier(), - main: () => ({ - action: renameFromDependencyTsWithBothProjectsOpen, - // DependencyTs is open, so omit it from closed infos - closedInfos: withRefs ? - [dependencyConfig.path, libFile.path, dtsLocation, dtsMapLocation] : - disableSourceOfProjectReferenceRedirect ? - [libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path] : - [libFile.path, dtsPath, dtsMapLocation], - otherWatchedFiles: withRefs || disableSourceOfProjectReferenceRedirect ? - [mainConfig.path] : // dependencyConfig is in closed info - [mainConfig.path, dependencyConfig.path], - expectsDts: true, - expectsMap: true, - freshMapInfo: withRefs - }), - change: ["main", () => ({ - freshMapInfo: false - })], - dtsChange: "change", - mapChange: ["main", () => ({ - freshMapInfo: false, - freshDocumentMapper: withRefs - })], - noMap: ["main", main => ({ - action: withRefs ? - renameFromDependencyTsWithBothProjectsOpen : - renameFromDependencyTs, - closedInfos: removePath(main.closedInfos, dtsMapPath), - otherWatchedFiles: main.otherWatchedFiles.concat(dtsMapLocation), - expectsMap: false, - freshDocumentMapper: withRefs - })], - mapFileCreated: "main", - mapFileDeleted: "noMap", - noDts: ["change", change => ({ - action: withRefs ? - renameFromDependencyTsWithBothProjectsOpen : - renameFromDependencyTs, - closedInfos: removePath(change.closedInfos, dtsPath, dtsMapPath), - otherWatchedFiles: change.otherWatchedFiles.concat(dtsLocation), - expectsDts: false, - expectsMap: false - })], - dtsFileCreated: "main", - dtsFileDeleted: ["noDts", noDts => ({ - // Map collection after file open - closedInfos: noDts.closedInfos.concat(dtsMapLocation), - expectsMap: true - })], - dependencyChange: ["change", () => ({ - action: renameFromDependencyTsWithBothProjectsOpenWithDependencyChange - })], - noBuild: "noDts" - } - ] - }); - }); - }); - describe("when root file is file from referenced project", () => { function verify(disableSourceOfProjectReferenceRedirect: boolean) { const projectLocation = `/user/username/projects/project`; @@ -1296,7 +197,7 @@ function foo() { } ` }; - const host = createHost( + const host = createHostWithSolutionBuild( [commonConfig, keyboardTs, keyboardTestTs, srcConfig, terminalTs, libFile], [srcConfig.path] ); @@ -1470,7 +371,7 @@ function foo() { const bConfig = config("B", extraOptions); const files = [libFile, bPackageJson, aConfig, bConfig, aTest, bFoo, bBar, bSymlink]; const host = alreadyBuilt ? - createHost(files, [aConfig.path]) : + createHostWithSolutionBuild(files, [aConfig.path]) : createServerHost(files); // Create symlink in node module diff --git a/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts new file mode 100644 index 00000000000..5c4c6cd2c4a --- /dev/null +++ b/src/testRunner/unittests/tsserver/projectReferencesSourcemap.ts @@ -0,0 +1,4439 @@ +namespace ts.projectSystem { + describe("unittests:: tsserver:: with project references and tsbuild source map", () => { + const dependecyLocation = `${tscWatch.projectRoot}/dependency`; + const dependecyDeclsLocation = `${tscWatch.projectRoot}/decls`; + const mainLocation = `${tscWatch.projectRoot}/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 dependencyTsPath = dependencyTs.path.toLowerCase(); + const dependencyConfig: File = { + path: `${dependecyLocation}/tsconfig.json`, + content: JSON.stringify({ compilerOptions: { composite: true, declarationMap: true, declarationDir: "../decls" } }) + }; + + const mainTs: File = { + path: `${mainLocation}/main.ts`, + content: `import { + fn1, + fn2, + fn3, + fn4, + fn5 +} from '../decls/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 randomFile: File = { + path: `${tscWatch.projectRoot}/random/random.ts`, + content: "let a = 10;" + }; + const randomConfig: File = { + path: `${tscWatch.projectRoot}/random/tsconfig.json`, + content: "{}" + }; + const dtsLocation = `${dependecyDeclsLocation}/FnS.d.ts`; + const dtsPath = dtsLocation.toLowerCase() as Path; + const dtsMapLocation = `${dependecyDeclsLocation}/FnS.d.ts.map`; + const dtsMapPath = dtsMapLocation.toLowerCase() as Path; + + const files = [dependencyTs, dependencyConfig, mainTs, mainConfig, libFile, randomFile, randomConfig]; + + function changeDtsFile(host: TestServerHost) { + host.writeFile( + dtsLocation, + host.readFile(dtsLocation)!.replace( + "//# sourceMappingURL=FnS.d.ts.map", + `export declare function fn6(): void; +//# sourceMappingURL=FnS.d.ts.map` + ) + ); + } + + function changeDtsMapFile(host: TestServerHost) { + host.writeFile( + dtsMapLocation, + `{"version":3,"file":"FnS.d.ts","sourceRoot":"","sources":["../dependency/FnS.ts"],"names":[],"mappings":"AAAA,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,wBAAgB,GAAG,SAAM;AACzB,eAAO,MAAM,CAAC,KAAK,CAAC"}` + ); + } + + function verifyScriptInfos(session: TestSession, host: TestServerHost, openInfos: readonly string[], closedInfos: readonly string[], otherWatchedFiles: readonly string[], additionalInfo?: string) { + checkScriptInfos(session.getProjectService(), openInfos.concat(closedInfos), additionalInfo); + checkWatchedFiles(host, closedInfos.concat(otherWatchedFiles).map(f => f.toLowerCase()), additionalInfo); + } + + function verifyOnlyRandomInfos(session: TestSession, host: TestServerHost) { + verifyScriptInfos(session, host, [randomFile.path], [libFile.path], [randomConfig.path], "Random"); + } + + function declarationSpan(fn: number): protocol.TextSpanWithContext { + return { + start: { line: fn, offset: 17 }, + end: { line: fn, offset: 20 }, + contextStart: { line: fn, offset: 1 }, + contextEnd: { line: fn, offset: 26 } + }; + } + function importSpan(fn: number): protocol.TextSpanWithContext { + return { + start: { line: fn + 1, offset: 5 }, + end: { line: fn + 1, offset: 8 }, + contextStart: { line: 1, offset: 1 }, + contextEnd: { line: 7, offset: 22 } + }; + } + function usageSpan(fn: number): protocol.TextSpan { + return { start: { line: fn + 8, offset: 1 }, end: { line: fn + 8, offset: 4 } }; + } + + function goToDefFromMainTs(fn: number): Action { + const textSpan = usageSpan(fn); + const definition: protocol.FileSpan = { file: dependencyTs.path, ...declarationSpan(fn) }; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To dependency + definitions: [definition], + textSpan + } + }; + } + + function goToDefFromMainTsWithNoMap(fn: number): Action { + const textSpan = usageSpan(fn); + const definition = declarationSpan(fn); + const declareSpaceLength = "declare ".length; + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To the dts + definitions: [{ + file: dtsPath, + start: { line: fn, offset: definition.start.offset + declareSpaceLength }, + end: { line: fn, offset: definition.end.offset + declareSpaceLength }, + contextStart: { line: fn, offset: 1 }, + contextEnd: { line: fn, offset: 37 } + }], + textSpan + } + }; + } + + function goToDefFromMainTsWithNoDts(fn: number): Action { + const textSpan = usageSpan(fn); + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // To import declaration + definitions: [{ file: mainTs.path, ...importSpan(fn) }], + textSpan + } + }; + } + + function goToDefFromMainTsWithDependencyChange(fn: number): Action { + const textSpan = usageSpan(fn); + return { + reqName: "goToDef", + request: { + command: protocol.CommandTypes.DefinitionAndBoundSpan, + arguments: { file: mainTs.path, ...textSpan.start } + }, + expectedResponse: { + // Definition on fn + 1 line + definitions: [{ file: dependencyTs.path, ...declarationSpan(fn + 1) }], + textSpan + } + }; + } + + function renameFromDependencyTs(fn: number): Action { + const defSpan = declarationSpan(fn); + const { contextStart: _, contextEnd: _1, ...triggerSpan } = defSpan; + return { + reqName: "rename", + request: { + command: protocol.CommandTypes.Rename, + arguments: { file: dependencyTs.path, ...triggerSpan.start } + }, + expectedResponse: { + info: { + canRename: true, + fileToRename: undefined, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, + kind: ScriptElementKind.functionElement, + kindModifiers: "export", + triggerSpan + }, + locs: [ + { file: dependencyTs.path, locs: [defSpan] } + ] + } + }; + } + + function renameFromDependencyTsWithDependencyChange(fn: number): Action { + const { expectedResponse: { info, locs }, ...rest } = renameFromDependencyTs(fn + 1); + + return { + ...rest, + expectedResponse: { + info: { + ...info as protocol.RenameInfoSuccess, + displayName: `fn${fn}`, + fullDisplayName: `"${dependecyLocation}/FnS".fn${fn}`, + }, + locs + } + }; + } + + function renameFromDependencyTsWithBothProjectsOpen(fn: number): Action { + const { reqName, request, expectedResponse } = renameFromDependencyTs(fn); + const { info, locs } = expectedResponse; + return { + reqName, + request, + expectedResponse: { + info, + locs: [ + locs[0], + { + file: mainTs.path, + locs: [ + importSpan(fn), + usageSpan(fn) + ] + } + ] + } + }; + } + + function renameFromDependencyTsWithBothProjectsOpenWithDependencyChange(fn: number): Action { + const { reqName, request, expectedResponse, } = renameFromDependencyTsWithDependencyChange(fn); + const { info, locs } = expectedResponse; + return { + reqName, + request, + expectedResponse: { + info, + locs: [ + locs[0], + { + file: mainTs.path, + locs: [ + importSpan(fn), + usageSpan(fn) + ] + } + ] + } + }; + } + + function removePath(array: readonly string[], ...delPaths: string[]) { + return array.filter(a => { + const aLower = a.toLowerCase(); + return delPaths.every(dPath => dPath !== aLower); + }); + } + + interface Action { + reqName: string; + request: Partial; + expectedResponse: Response; + } + + function verifyAction(session: TestSession, { reqName, request, expectedResponse }: Action) { + const { response } = session.executeCommandSeq(request); + assert.deepEqual(response, expectedResponse, `Failed Request: ${reqName}`); + } + + function verifyDocumentPositionMapper( + session: TestSession, + dependencyMap: server.ScriptInfo | undefined, + documentPositionMapper: server.ScriptInfo["documentPositionMapper"], + equal: boolean, + debugInfo?: string, + ) { + assert.strictEqual(session.getProjectService().filenameToScriptInfo.get(dtsMapPath), dependencyMap, `${debugInfo} dependencyMap`); + if (dependencyMap) { + verifyEquality(dependencyMap.documentPositionMapper, documentPositionMapper, equal, `${debugInfo} DocumentPositionMapper`); + } + } + + function verifyDocumentPositionMapperEqual( + session: TestSession, + dependencyMap: server.ScriptInfo | undefined, + documentPositionMapper: server.ScriptInfo["documentPositionMapper"], + debugInfo?: string, + ) { + verifyDocumentPositionMapper(session, dependencyMap, documentPositionMapper, /*equal*/ true, debugInfo); + } + + function verifyEquality(actual: T, expected: T, equal: boolean, debugInfo?: string) { + if (equal) { + assert.strictEqual(actual, expected, debugInfo); + } + else { + assert.notStrictEqual(actual, expected, debugInfo); + } + } + + function verifyAllFnAction( + session: TestSession, + host: TestServerHost, + action: (fn: number) => Action, + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + existingDependencyMap: server.ScriptInfo | undefined, + existingDocumentPositionMapper: server.ScriptInfo["documentPositionMapper"], + existingMapEqual: boolean, + existingDocumentPositionMapperEqual: boolean, + skipMapPathInDtsInfo?: boolean + ) { + let sourceMapPath: server.ScriptInfo["sourceMapFilePath"] | undefined; + let dependencyMap: server.ScriptInfo | undefined; + let documentPositionMapper: server.ScriptInfo["documentPositionMapper"]; + for (let fn = 1; fn <= 5; fn++) { + const fnAction = action(fn); + verifyAction(session, fnAction); + const debugInfo = `${fnAction.reqName}:: ${fn}`; + checkScriptInfos(session.getProjectService(), expectedInfos, debugInfo); + checkWatchedFiles(host, expectedWatchedFiles, debugInfo); + const dtsInfo = session.getProjectService().getScriptInfoForPath(dtsPath); + const dtsMapInfo = session.getProjectService().getScriptInfoForPath(dtsMapPath); + + if (fn === 1) { + if (dtsInfo) { + if (!skipMapPathInDtsInfo) { + if (dtsMapInfo) { + assert.equal(dtsInfo.sourceMapFilePath, dtsMapPath, `${debugInfo} sourceMapFilePath`); + } + else { + assert.isNotString(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); + assert.isNotFalse(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); + assert.isDefined(dtsInfo.sourceMapFilePath, `${debugInfo} sourceMapFilePath`); + } + } + } + verifyEquality(dtsMapInfo, existingDependencyMap, existingMapEqual, `${debugInfo} dependencyMap`); + verifyEquality(existingDocumentPositionMapper, dtsMapInfo?.documentPositionMapper, existingDocumentPositionMapperEqual, `${debugInfo} DocumentPositionMapper`); + sourceMapPath = dtsInfo?.sourceMapFilePath; + dependencyMap = dtsMapInfo; + documentPositionMapper = dependencyMap?.documentPositionMapper; + } + else { + assert.equal(dtsInfo?.sourceMapFilePath, sourceMapPath, `${debugInfo} sourceMapFilePath`); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper, debugInfo); + } + } + } + + function verifyScriptInfoCollectionWith( + session: TestSession, + host: TestServerHost, + openFiles: readonly File[], + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + ) { + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + + // Collecting at this point retains dependency.d.ts and map + closeFilesForSession([randomFile], session); + openFilesForSession([randomFile], session); + + checkScriptInfos(session.getProjectService(), expectedInfos); + checkWatchedFiles(host, expectedWatchedFiles); + // If map is not collected, document position mapper shouldnt change + if (session.getProjectService().filenameToScriptInfo.has(dtsMapPath)) { + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + } + + // Closing open file, removes dependencies too + closeFilesForSession([...openFiles, randomFile], session); + openFilesForSession([randomFile], session); + verifyOnlyRandomInfos(session, host); + } + + type OnHostCreate = (host: TestServerHost) => void; + type CreateSessionFn = (onHostCreate?: OnHostCreate) => { host: TestServerHost; session: TestSession; }; + function setupWith(createSession: CreateSessionFn, openFiles: readonly File[], onHostCreate: OnHostCreate | undefined) { + const result = createSession(onHostCreate); + openFilesForSession(openFiles, result.session); + return result; + } + + function setupWithMainTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [mainTs, randomFile], onHostCreate); + } + + function setupWithDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [dependencyTs, randomFile], onHostCreate); + } + + function setupWithMainTsAndDependencyTs(createSession: CreateSessionFn, onHostCreate: OnHostCreate | undefined) { + return setupWith(createSession, [mainTs, dependencyTs, randomFile], onHostCreate); + } + + function createSessionWithoutProjectReferences(onHostCreate?: OnHostCreate) { + const host = createHostWithSolutionBuild(files, [mainConfig.path]); + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { composite: true, declarationMap: true } + })); + onHostCreate?.(host); + const session = createSession(host); + return { host, session }; + } + + function createSessionWithProjectReferences(onHostCreate?: OnHostCreate) { + const host = createHostWithSolutionBuild(files, [mainConfig.path]); + onHostCreate?.(host); + const session = createSession(host); + return { host, session }; + } + + function createSessionWithDisabledProjectReferences(onHostCreate?: OnHostCreate) { + const host = createHostWithSolutionBuild(files, [mainConfig.path]); + // Erase project reference + host.writeFile(mainConfig.path, JSON.stringify({ + compilerOptions: { + composite: true, + declarationMap: true, + disableSourceOfProjectReferenceRedirect: true + }, + references: [{ path: "../dependency" }] + })); + onHostCreate?.(host); + const session = createSession(host); + return { host, session }; + } + + function getDocumentPositionMapper(session: TestSession) { + const dependencyMap = session.getProjectService().filenameToScriptInfo.get(dtsMapPath); + const documentPositionMapper = dependencyMap?.documentPositionMapper; + return { dependencyMap, documentPositionMapper }; + } + + function checkMainProjectWithoutProjectReferences(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dtsPath]); + } + + function checkMainProjectWithoutProjectReferencesWithoutDts(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path]); + } + + function checkMainProjectWithProjectReferences(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dependencyTs.path]); + } + + function checkMainProjectWithDisabledProjectReferences(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path, dtsPath]); + } + + function checkMainProjectWithDisabledProjectReferencesWithoutDts(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(mainConfig.path)!, [mainTs.path, libFile.path, mainConfig.path]); + } + + function checkDependencyProjectWith(session: TestSession) { + checkProjectActualFiles(session.getProjectService().configuredProjects.get(dependencyConfig.path)!, [dependencyTs.path, libFile.path, dependencyConfig.path]); + } + + function makeChangeToMainTs(session: TestSession) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: mainTs.path, + line: 14, + offset: 1, + endLine: 14, + endOffset: 1, + insertString: "const x = 10;" + } + }); + } + + function makeChangeToDependencyTs(session: TestSession) { + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, + line: 6, + offset: 1, + endLine: 6, + endOffset: 1, + insertString: "const x = 10;" + } + }); + } + + describe("from project that uses dependency: goToDef", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(goToDefFromMainTs(1).request); + return { ...result, ...getDocumentPositionMapper(result.session) }; + } + + function verifyScriptInfoCollection( + session: TestSession, + host: TestServerHost, + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + ) { + return verifyScriptInfoCollectionWith(session, host, [mainTs], expectedInfos, expectedWatchedFiles); + } + + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithoutProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithoutProjectReferences(session); + } + + function checkProjectsWithoutDts(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithoutProjectReferencesWithoutDts(session); + } + + function expectedScriptInfosWhenMapped() { + return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; + } + + function expectedWatchedFilesWhenMapped() { + return [dependencyTsPath, libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path]; + } + + function expectedScriptInfosWhenNoMap() { + // Because map is deleted, map and dependency are released + return removePath(expectedScriptInfosWhenMapped(), dtsMapPath, dependencyTsPath); + } + + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file + return removePath(expectedWatchedFilesWhenMapped(), dependencyTsPath); + } + + function expectedScriptInfosWhenNoDts() { + // No dts, no map, no dependency + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); + } + + function expectedWatchedFilesWhenNoDts() { + return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); + } + + it("can go to definition correctly", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + // The script info for map is collected only after file open + expectedScriptInfosWhenNoMap().concat(dependencyTs.path), + expectedWatchedFilesWhenNoMap().concat(dependencyTsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjectsWithoutDts(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + // The script info for map is collected only after file open + expectedScriptInfosWhenNoDts().concat(dependencyTs.path, dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dependencyTsPath, dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithProjectReferences(session); + } + + function expectedScriptInfos() { + return [dependencyTs.path, dependencyConfig.path, libFile.path, mainTs.path, randomFile.path]; + } + + function expectedWatchedFiles() { + return [dependencyTsPath, dependencyConfig.path, libFile.path, mainConfig.path, randomConfig.path]; + } + + it("can go to definition correctly", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + host.writeFile(dependencyTs.path, `function fooBar() { } +${dependencyTs.content}`); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithDependencyChange, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session } = setupWithAction(); + + // change + // Make change, without rebuild of solution + host.writeFile(dependencyTs.path, `function fooBar() { } +${dependencyTs.content}`); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithDependencyChange, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + it("when projects are not built", () => { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([mainTs, randomFile], session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTs(createSessionWithDisabledProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithDisabledProjectReferences(session); + } + + function checkProjectsWithoutDts(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkMainProjectWithDisabledProjectReferencesWithoutDts(session); + } + + function expectedScriptInfosWhenMapped() { + return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path]; + } + + function expectedWatchedFilesWhenMapped() { + return [dependencyTsPath, libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; + } + + function expectedScriptInfosWhenNoMap() { + // Because map is deleted, map and dependency are released + return removePath(expectedScriptInfosWhenMapped(), dtsMapPath, dependencyTsPath); + } + + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file + return removePath(expectedWatchedFilesWhenMapped(), dependencyTsPath); + } + + function expectedScriptInfosWhenNoDts() { + // No dts, no map, no dependency + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); + } + + function expectedWatchedFilesWhenNoDts() { + return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath, dependencyTsPath); + } + + it("can go to definition correctly", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + // The script info for map is collected only after file open + expectedScriptInfosWhenNoMap().concat(dependencyTs.path), + expectedWatchedFilesWhenNoMap().concat(dependencyTsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjectsWithoutDts(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + // The script info for map is collected only after file open + expectedScriptInfosWhenNoDts().concat(dependencyTs.path, dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dependencyTsPath, dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + }); + + describe("from defining project: rename", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(renameFromDependencyTs(1).request); + return { ...result, ...getDocumentPositionMapper(result.session) }; + } + + function verifyScriptInfoCollection( + session: TestSession, + host: TestServerHost, + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + ) { + return verifyScriptInfoCollectionWith(session, host, [dependencyTs], expectedInfos, expectedWatchedFiles); + } + + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); + checkDependencyProjectWith(session); + } + + function expectedScriptInfos() { + return [libFile.path, dtsLocation, dtsMapLocation, dependencyTs.path, randomFile.path]; + } + + function expectedWatchedFiles() { + return [libFile.path, dtsPath, dtsMapPath, dependencyConfig.path, randomConfig.path]; + } + + function expectedScriptInfosWhenNoMap() { + // No map + return removePath(expectedScriptInfos(), dtsMapPath); + } + + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file = map file + return expectedWatchedFiles(); + } + + function expectedScriptInfosWhenNoDts() { + // no dts or map since dts itself doesnt exist + return removePath(expectedScriptInfos(), dtsPath, dtsMapPath); + } + + function expectedWatchedFilesWhenNoDts() { + // watch deleted file + return removePath(expectedWatchedFiles(), dtsMapPath); + } + + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithoutProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + it("rename locations from dependency", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // Map is collected after file open + expectedScriptInfosWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dtsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + it("rename locations from dependency", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // Map is collected after file open + expectedScriptInfosWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dtsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } +`} + }); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithDependencyChange, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } +`} + }); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithDependencyChange, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it("when projects are not built", () => { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([dependencyTs, randomFile], session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + it("rename locations from dependency", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfos(), + expectedWatchedFiles(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfos(), + expectedWatchedFiles() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // Map is collected after file open + expectedScriptInfosWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesWhenNoDts().concat(dtsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoDts(), + expectedWatchedFilesWhenNoDts(), + ); + }); + }); + }); + + describe("when opening depedency and usage project: goToDef and rename", () => { + function setupWithActionWith(setup: (onHostCreate?: OnHostCreate) => ReturnType, onHostCreate: OnHostCreate | undefined) { + const result = setup(onHostCreate); + result.session.executeCommandSeq(goToDefFromMainTs(1).request); + result.session.executeCommandSeq(renameFromDependencyTsWithBothProjectsOpen(1).request); + return { ...result, ...getDocumentPositionMapper(result.session) }; + } + + function verifyScriptInfoCollection( + session: TestSession, + host: TestServerHost, + expectedInfos: readonly string[], + expectedWatchedFiles: readonly string[], + ) { + return verifyScriptInfoCollectionWith(session, host, [mainTs, dependencyTs], expectedInfos, expectedWatchedFiles); + } + + describe("when main tsconfig doesnt have project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithoutProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithoutProjectReferences(session); + checkDependencyProjectWith(session); + } + + function checkProjectsWithoutDts(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithoutProjectReferencesWithoutDts(session); + checkDependencyProjectWith(session); + } + + function expectedScriptInfosWhenMapped() { + return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation]; + } + + function expectedWatchedFilesWhenMapped() { + return [libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; + } + + function expectedScriptInfosWhenNoMap() { + // Because map is deleted, map and dependency are released + return removePath(expectedScriptInfosWhenMapped(), dtsMapPath); + } + + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file + return expectedWatchedFilesWhenMapped(); + } + + function expectedScriptInfosAfterGotoDefWhenNoDts() { + // No dts, no map + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); + } + + function expectedWatchedFilesAfterGotoDefWhenNoDts() { + return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath); + } + + function expectedScriptInfosAfterRenameWhenNoDts() { + // No dts, no map + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); + } + + function expectedWatchedFilesAfterRenameWhenNoDts() { + // Watches dts file but not map file + return removePath(expectedWatchedFilesWhenMapped(), dtsMapPath); + } + + it("goto Definition in usage and rename locations from defining project", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjectsWithoutDts(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + expectedScriptInfosAfterGotoDefWhenNoDts(), + expectedWatchedFilesAfterGotoDefWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + // The script info for map is collected only after file open + expectedScriptInfosAfterGotoDefWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterGotoDefWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // The script info for map is collected only after file open + expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithProjectReferences(session); + checkDependencyProjectWith(session); + } + + function expectedScriptInfosAfterGotoDef() { + return [dependencyTs.path, dependencyConfig.path, libFile.path, mainTs.path, randomFile.path]; + } + + function expectedWatchedFilesAfterGotoDef() { + return [dependencyConfig.path, libFile.path, mainConfig.path, randomConfig.path]; + } + + function expectedScriptInfosAfterRenameWhenMapped() { + return expectedScriptInfosAfterGotoDef().concat(dtsLocation, dtsMapLocation); + } + + function expectedWatchedFilesAfterRenameWhenMapped() { + return expectedWatchedFilesAfterGotoDef().concat(dtsPath, dtsMapPath); + } + + function expectedScriptInfosAfterRenameWhenNoMap() { + // Map file is not present + return removePath(expectedScriptInfosAfterRenameWhenMapped(), dtsMapPath); + } + + function expectedWatchedFilesAfterRenameWhenNoMap() { + // Watches map file + return expectedWatchedFilesAfterRenameWhenMapped(); + } + + function expectedScriptInfosAfterRenameWhenNoDts() { + // map and dts not present + return removePath(expectedScriptInfosAfterRenameWhenMapped(), dtsPath, dtsMapPath); + } + + function expectedWatchedFilesAfterRenameWhenNoDts() { + // Watches dts file but not map + return removePath(expectedWatchedFilesAfterRenameWhenMapped(), dtsMapPath); + } + + it("goto Definition in usage and rename locations from defining project", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + expectedWatchedFilesAfterGotoDef(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + expectedWatchedFilesAfterGotoDef(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenNoMap(), + expectedWatchedFilesAfterRenameWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoMap(), + expectedWatchedFilesAfterRenameWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenNoMap(), + // Map file is reset so its not watched any more, as this action doesnt need map + removePath(expectedWatchedFilesAfterRenameWhenNoMap(), dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true, + /*skipMapPathInDtsInfo*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterRenameWhenNoMap(), + // Map file is reset so its not watched any more, as this action doesnt need map + removePath(expectedWatchedFilesAfterRenameWhenNoMap(), dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false, + /*skipMapPathInDtsInfo*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenNoMap(), + expectedWatchedFilesAfterRenameWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoMap(), + expectedWatchedFilesAfterRenameWhenNoMap(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + expectedWatchedFilesAfterGotoDef(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + // Since the project for dependency is not updated, the watcher from rename for dts still there + expectedWatchedFilesAfterGotoDef().concat(dtsPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + // Map collection after file open + expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), + // not watching dts since this operation doesnt need it + removePath(expectedWatchedFilesAfterRenameWhenNoDts(), dtsPath).concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + // Map collection after file open + expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + + it(`when defining project source changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } +`} + }); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithDependencyChange, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpenWithDependencyChange, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when defining project source changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + // Make change, without rebuild of solution + session.executeCommandSeq({ + command: protocol.CommandTypes.Change, + arguments: { + file: dependencyTs.path, line: 1, offset: 1, endLine: 1, endOffset: 1, insertString: `function fooBar() { } +`} + }); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithDependencyChange, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpenWithDependencyChange, + expectedScriptInfosAfterRenameWhenMapped(), + expectedWatchedFilesAfterRenameWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + it("when projects are not built", () => { + const host = createServerHost(files); + const session = createSession(host); + openFilesForSession([mainTs, dependencyTs, randomFile], session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosAfterGotoDef(), + expectedWatchedFilesAfterGotoDef(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + }); + describe("when main tsconfig has disableSourceOfProjectReferenceRedirect along with project reference", () => { + function setup(onHostCreate?: OnHostCreate) { + return setupWithMainTsAndDependencyTs(createSessionWithDisabledProjectReferences, onHostCreate); + } + + function setupWithAction(onHostCreate?: OnHostCreate) { + return setupWithActionWith(setup, onHostCreate); + } + + function checkProjects(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithDisabledProjectReferences(session); + checkDependencyProjectWith(session); + } + + function checkProjectsWithoutDts(session: TestSession) { + checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 }); + checkMainProjectWithDisabledProjectReferencesWithoutDts(session); + checkDependencyProjectWith(session); + } + + function expectedScriptInfosWhenMapped() { + return [mainTs.path, randomFile.path, dependencyTs.path, libFile.path, dtsPath, dtsMapLocation, dependencyConfig.path]; + } + + function expectedWatchedFilesWhenMapped() { + return [libFile.path, dtsPath, dtsMapPath, mainConfig.path, randomConfig.path, dependencyConfig.path]; + } + + function expectedScriptInfosWhenNoMap() { + // Because map is deleted, map and dependency are released + return removePath(expectedScriptInfosWhenMapped(), dtsMapPath); + } + + function expectedWatchedFilesWhenNoMap() { + // Watches deleted file + return expectedWatchedFilesWhenMapped(); + } + + function expectedScriptInfosAfterGotoDefWhenNoDts() { + // No dts, no map + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); + } + + function expectedWatchedFilesAfterGotoDefWhenNoDts() { + return removePath(expectedWatchedFilesWhenMapped(), dtsPath, dtsMapPath); + } + + function expectedScriptInfosAfterRenameWhenNoDts() { + // No dts, no map + return removePath(expectedScriptInfosWhenMapped(), dtsPath, dtsMapPath); + } + + function expectedWatchedFilesAfterRenameWhenNoDts() { + // Watches dts file but not map file + return removePath(expectedWatchedFilesWhenMapped(), dtsMapPath); + } + + it("goto Definition in usage and rename locations from defining project", () => { + const { host, session } = setup(); + checkProjects(session); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap, documentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + ); + }); + + // Edit + it(`when usage file changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when usage file changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + makeChangeToMainTs(session); + makeChangeToDependencyTs(session); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit dts to add new fn + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency .d.ts changes, document position mapper doesnt change, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + // Edit map file to represent added new line + it(`when dependency file's map changes, when timeout occurs before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + host.runQueuedTimeoutCallbacks(); + checkProjects(session); + verifyDocumentPositionMapperEqual(session, dependencyMap, documentPositionMapper); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + it(`when dependency file's map changes, when timeout does not occur before request`, () => { + // Create DocumentPositionMapper + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // change + changeDtsMapFile(host); + + // action + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ false + ); + const { documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + }); + + it(`with depedency files map file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsMapLocation)); + checkProjects(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + it(`with depedency files map file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsMapLocation)!; + host.deleteFile(dtsMapLocation); + }); + + host.writeFile(dtsMapLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency files map file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsMapLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoMap, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + + // Script info collection should behave as fileNotPresentKey + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenNoMap(), + expectedWatchedFilesWhenNoMap(), + ); + }); + + it(`with depedency .d.ts file, when file is not present`, () => { + const { host, session } = setup(host => host.deleteFile(dtsLocation)); + checkProjectsWithoutDts(session); + + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + expectedScriptInfosAfterGotoDefWhenNoDts(), + expectedWatchedFilesAfterGotoDefWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + /*existingDependencyMap*/ undefined, + /*existingDocumentPositionMapper*/ undefined, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + it(`with depedency .d.ts file, when file is created after actions on projects`, () => { + let fileContents: string; + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(host => { + fileContents = host.readFile(dtsLocation)!; + host.deleteFile(dtsLocation); + }); + + host.writeFile(dtsLocation, fileContents!); + verifyAllFnAction( + session, + host, + goToDefFromMainTs, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ false, + /*existingDocumentPositionMapperEqual*/ false + ); + const { dependencyMap: newDependencyMap, documentPositionMapper: newDocumentPositionMapper } = getDocumentPositionMapper(session); + verifyAllFnAction( + session, + host, + renameFromDependencyTsWithBothProjectsOpen, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped(), + newDependencyMap, + newDocumentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjects(session); + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosWhenMapped(), + expectedWatchedFilesWhenMapped() + ); + }); + it(`with depedency .d.ts file, when file is deleted after actions on the projects`, () => { + const { host, session, dependencyMap, documentPositionMapper } = setupWithAction(); + + // The dependency file is deleted when orphan files are collected + host.deleteFile(dtsLocation); + verifyAllFnAction( + session, + host, + goToDefFromMainTsWithNoDts, + // The script info for map is collected only after file open + expectedScriptInfosAfterGotoDefWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterGotoDefWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + verifyAllFnAction( + session, + host, + renameFromDependencyTs, + // The script info for map is collected only after file open + expectedScriptInfosAfterRenameWhenNoDts().concat(dtsMapLocation), + expectedWatchedFilesAfterRenameWhenNoDts().concat(dtsMapPath), + dependencyMap, + documentPositionMapper, + /*existingMapEqual*/ true, + /*existingDocumentPositionMapperEqual*/ true + ); + checkProjectsWithoutDts(session); + + // Script info collection should behave as "noDts" + verifyScriptInfoCollection( + session, + host, + expectedScriptInfosAfterRenameWhenNoDts(), + expectedWatchedFilesAfterRenameWhenNoDts(), + ); + }); + }); + }); + }); +} \ No newline at end of file