diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index 315bc394e56..c09c9542055 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -345,7 +345,7 @@ namespace ts { } let result: SearchResult | undefined; if (!isExternalModuleNameRelative(typeReferenceDirectiveName)) { - result = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined); + result = loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeReferenceDirectiveName, initialLocationForSecondaryLookup, moduleResolutionState, /*cache*/ undefined, /*redirectedReference*/ undefined); } else { const { path: candidate } = normalizePathAndParts(combinePaths(initialLocationForSecondaryLookup, typeReferenceDirectiveName)); @@ -415,7 +415,7 @@ namespace ts { * This assumes that any module id will have the same resolution for sibling files located in the same folder. */ export interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { - getOrCreateCacheForDirectory(directoryName: string): Map; + getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map; } /** @@ -423,7 +423,7 @@ namespace ts { * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. */ export interface NonRelativeModuleNameResolutionCache { - getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache; + getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; } export interface PerModuleNameCache { @@ -433,40 +433,78 @@ namespace ts { export function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache { return createModuleResolutionCacheWithMaps( - createMap>(), - createMap(), + createCacheWithRedirects(), + createCacheWithRedirects(), currentDirectory, getCanonicalFileName ); } + /*@internal*/ + export interface CacheWithRedirects { + ownMap: Map; + redirectsMap: Map>; + getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map; + clear(): void; + } + + /*@internal*/ + export function createCacheWithRedirects(): CacheWithRedirects { + const ownMap: Map = createMap(); + const redirectsMap: Map> = createMap(); + return { + ownMap, + redirectsMap, + getOrCreateMapOfCacheRedirects, + clear + }; + + function getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined) { + if (!redirectedReference) { + return ownMap; + } + const path = redirectedReference.sourceFile.path; + let redirects = redirectsMap.get(path); + if (!redirects) { + redirects = createMap(); + redirectsMap.set(path, redirects); + } + return redirects; + } + + function clear() { + ownMap.clear(); + redirectsMap.clear(); + } + } + /*@internal*/ export function createModuleResolutionCacheWithMaps( - directoryToModuleNameMap: Map>, - moduleNameToDirectoryMap: Map, + directoryToModuleNameMap: CacheWithRedirects>, + moduleNameToDirectoryMap: CacheWithRedirects, currentDirectory: string, getCanonicalFileName: GetCanonicalFileName): ModuleResolutionCache { return { getOrCreateCacheForDirectory, getOrCreateCacheForModuleName }; - function getOrCreateCacheForDirectory(directoryName: string) { + function getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference) { const path = toPath(directoryName, currentDirectory, getCanonicalFileName); - let perFolderCache = directoryToModuleNameMap.get(path); - if (!perFolderCache) { - perFolderCache = createMap(); - directoryToModuleNameMap.set(path, perFolderCache); - } - return perFolderCache; + return getOrCreateCache>(directoryToModuleNameMap, redirectedReference, path, createMap); } - function getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache { + function getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache { Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName)); - let perModuleNameCache = moduleNameToDirectoryMap.get(nonRelativeModuleName); - if (!perModuleNameCache) { - perModuleNameCache = createPerModuleNameCache(); - moduleNameToDirectoryMap.set(nonRelativeModuleName, perModuleNameCache); + return getOrCreateCache(moduleNameToDirectoryMap, redirectedReference, nonRelativeModuleName, createPerModuleNameCache); + } + + function getOrCreateCache(cacheWithRedirects: CacheWithRedirects, redirectedReference: ResolvedProjectReference | undefined, key: string, create: () => T): T { + const cache = cacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); + let result = cache.get(key); + if (!result) { + result = create(); + cache.set(key, result); } - return perModuleNameCache; + return result; } function createPerModuleNameCache(): PerModuleNameCache { @@ -560,7 +598,7 @@ namespace ts { } } const containingDirectory = getDirectoryPath(containingFile); - const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory); + const perFolderCache = cache && cache.getOrCreateCacheForDirectory(containingDirectory, redirectedReference); let result = perFolderCache && perFolderCache.get(moduleName); if (result) { @@ -584,10 +622,10 @@ namespace ts { switch (moduleResolution) { case ModuleResolutionKind.NodeJs: - result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache); + result = nodeModuleNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); break; case ModuleResolutionKind.Classic: - result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache); + result = classicNameResolver(moduleName, containingFile, compilerOptions, host, cache, redirectedReference); break; default: return Debug.fail(`Unexpected moduleResolution: ${moduleResolution}`); @@ -597,7 +635,7 @@ namespace ts { perFolderCache.set(moduleName, result); if (!isExternalModuleNameRelative(moduleName)) { // put result in per-module name cache - cache!.getOrCreateCacheForModuleName(moduleName).set(containingDirectory, result); + cache!.getOrCreateCacheForModuleName(moduleName, redirectedReference).set(containingDirectory, result); } } } @@ -817,14 +855,14 @@ namespace ts { } function tryResolveJSModuleWorker(moduleName: string, initialDir: string, host: ModuleResolutionHost): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*jsOnly*/ true); + return nodeModuleNameResolverWorker(moduleName, initialDir, { moduleResolution: ModuleResolutionKind.NodeJs, allowJs: true }, host, /*cache*/ undefined, /*redirectedReference*/ undefined, /*jsOnly*/ true); } - export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations { - return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, /*jsOnly*/ false); + export function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { + return nodeModuleNameResolverWorker(moduleName, getDirectoryPath(containingFile), compilerOptions, host, cache, redirectedReference, /*jsOnly*/ false); } - function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, jsOnly: boolean): ResolvedModuleWithFailedLookupLocations { + function nodeModuleNameResolverWorker(moduleName: string, containingDirectory: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined, jsOnly: boolean): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); const failedLookupLocations: string[] = []; @@ -852,7 +890,7 @@ namespace ts { if (traceEnabled) { trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_type_1, moduleName, Extensions[extensions]); } - const resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache); + const resolved = loadModuleFromNearestNodeModulesDirectory(extensions, moduleName, containingDirectory, state, cache, redirectedReference); if (!resolved) return undefined; let resolvedValue = resolved.value; @@ -1190,17 +1228,17 @@ namespace ts { return idx === -1 ? { packageName: moduleName, rest: "" } : { packageName: moduleName.slice(0, idx), rest: moduleName.slice(idx + 1) }; } - function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult { - return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache); + function loadModuleFromNearestNodeModulesDirectory(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference); } function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult { // Extensions parameter here doesn't actually matter, because typesOnly ensures we're just doing @types lookup, which is always DtsOnly. - return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined); + return loadModuleFromNearestNodeModulesDirectoryWorker(Extensions.DtsOnly, moduleName, directory, state, /*typesScopeOnly*/ true, /*cache*/ undefined, /*redirectedReference*/ undefined); } - function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined): SearchResult { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName); + function loadModuleFromNearestNodeModulesDirectoryWorker(extensions: Extensions, moduleName: string, directory: string, state: ModuleResolutionState, typesScopeOnly: boolean, cache: NonRelativeModuleNameResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): SearchResult { + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); return forEachAncestorDirectory(normalizeSlashes(directory), ancestorDirectory => { if (getBaseFileName(ancestorDirectory) !== "node_modules") { const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, ancestorDirectory, state); @@ -1368,7 +1406,7 @@ namespace ts { } } - export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations { + export function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations { const traceEnabled = isTraceEnabled(compilerOptions, host); const failedLookupLocations: string[] = []; const state: ModuleResolutionState = { compilerOptions, host, traceEnabled, failedLookupLocations }; @@ -1385,7 +1423,7 @@ namespace ts { } if (!isExternalModuleNameRelative(moduleName)) { - const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName); + const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName, redirectedReference); // Climb up parent directories looking for a module. const resolved = forEachAncestorDirectory(containingDirectory, directory => { const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, state); diff --git a/src/compiler/resolutionCache.ts b/src/compiler/resolutionCache.ts index 809a649520d..42df1eb9a24 100644 --- a/src/compiler/resolutionCache.ts +++ b/src/compiler/resolutionCache.ts @@ -90,17 +90,17 @@ namespace ts { // The key in the map is source file's path. // The values are Map of resolutions with key being name lookedup. const resolvedModuleNames = createMap>(); - const perDirectoryResolvedModuleNames = createMap>(); - const nonRelaticeModuleNameCache = createMap(); + const perDirectoryResolvedModuleNames: CacheWithRedirects> = createCacheWithRedirects(); + const nonRelativeModuleNameCache: CacheWithRedirects = createCacheWithRedirects(); const moduleResolutionCache = createModuleResolutionCacheWithMaps( perDirectoryResolvedModuleNames, - nonRelaticeModuleNameCache, + nonRelativeModuleNameCache, getCurrentDirectory(), resolutionHost.getCanonicalFileName ); const resolvedTypeReferenceDirectives = createMap>(); - const perDirectoryResolvedTypeReferenceDirectives = createMap>(); + const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects> = createCacheWithRedirects(); /** * These are the extensions that failed lookup files will have by default, @@ -199,7 +199,7 @@ namespace ts { function clearPerDirectoryResolutions() { perDirectoryResolvedModuleNames.clear(); - nonRelaticeModuleNameCache.clear(); + nonRelativeModuleNameCache.clear(); perDirectoryResolvedTypeReferenceDirectives.clear(); nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions); nonRelativeExternalModuleResolutions.clear(); @@ -244,7 +244,7 @@ namespace ts { containingFile: string, redirectedReference: ResolvedProjectReference | undefined, cache: Map>, - perDirectoryCache: Map>, + perDirectoryCacheWithRedirects: CacheWithRedirects>, loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference) => T, getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName, reusedNames: string[] | undefined, @@ -253,6 +253,7 @@ namespace ts { const path = resolutionHost.toPath(containingFile); const resolutionsInFile = cache.get(path) || cache.set(path, createMap()).get(path)!; const dirPath = getDirectoryPath(path); + const perDirectoryCache = perDirectoryCacheWithRedirects.getOrCreateMapOfCacheRedirects(redirectedReference); let perDirectoryResolution = perDirectoryCache.get(dirPath); if (!perDirectoryResolution) { perDirectoryResolution = createMap(); diff --git a/src/testRunner/unittests/tsbuild.ts b/src/testRunner/unittests/tsbuild.ts index bb219ced37c..34e83eab43e 100644 --- a/src/testRunner/unittests/tsbuild.ts +++ b/src/testRunner/unittests/tsbuild.ts @@ -392,6 +392,7 @@ export class cNew {}`); ...getLibs(), "/src/a.d.ts", "/src/b.d.ts", + "/src/refs/a.d.ts", "/src/c.ts" ]); }); diff --git a/src/testRunner/unittests/tsbuildWatchMode.ts b/src/testRunner/unittests/tsbuildWatchMode.ts index 2ffe51189d8..87116a28202 100644 --- a/src/testRunner/unittests/tsbuildWatchMode.ts +++ b/src/testRunner/unittests/tsbuildWatchMode.ts @@ -26,8 +26,23 @@ namespace ts.tscWatch { type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile]; const root = Harness.IO.getWorkspaceRoot(); + function getProjectPath(project: string) { + return `${projectsLocation}/${project}`; + } + + function getFilePathInProject(project: string, file: string) { + return `${projectsLocation}/${project}/${file}`; + } + + function getFileFromProject(project: string, file: string): File { + return { + path: getFilePathInProject(project, file), + content: Harness.IO.readFile(`${root}/tests/projects/${project}/${file}`)! + }; + } + function projectPath(subProject: SubProject) { - return `${projectsLocation}/${project}/${subProject}`; + return getFilePathInProject(project, subProject); } function projectFilePath(subProject: SubProject, baseFileName: string) { @@ -39,10 +54,7 @@ namespace ts.tscWatch { } function projectFile(subProject: SubProject, baseFileName: string): File { - return { - path: projectFilePath(subProject, baseFileName), - content: Harness.IO.readFile(`${root}/tests/projects/${project}/${subProject}/${baseFileName}`)! - }; + return getFileFromProject(project, `${subProject}/${baseFileName}`); } function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles { @@ -97,7 +109,7 @@ namespace ts.tscWatch { const tests = subProjectFiles(SubProject.tests); const ui = subProjectFiles(SubProject.ui); const allFiles: ReadonlyArray = [libFile, ...core, ...logic, ...tests, ...ui]; - const testProjectExpectedWatchedFiles = [core[0], core[1], core[2], ...logic, ...tests].map(f => f.path); + const testProjectExpectedWatchedFiles = [core[0], core[1], core[2], ...logic, ...tests].map(f => f.path.toLowerCase()); const testProjectExpectedWatchedDirectoriesRecursive = [projectPath(SubProject.core), projectPath(SubProject.logic)]; function createSolutionInWatchMode(allFiles: ReadonlyArray, defaultOptions?: BuildOptions, disableConsoleClears?: boolean) { @@ -244,7 +256,7 @@ export class someClass2 { }`); const allFiles = [libFile, ...core, logic[1], ...tests]; const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]); - checkWatchedFiles(host, [core[0], core[1], core[2], logic[0], ...tests].map(f => f.path)); + checkWatchedFiles(host, [core[0], core[1], core[2], logic[0], ...tests].map(f => f.path.toLowerCase())); checkWatchedDirectories(host, emptyArray, /*recursive*/ false); checkWatchedDirectories(host, [projectPath(SubProject.core)], /*recursive*/ true); checkOutputErrorsInitial(host, [ @@ -395,99 +407,161 @@ let x: string = 10;`); }); describe("tsc-watch works with project references", () => { - const coreIndexDts = projectFileName(SubProject.core, "index.d.ts"); - const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts"); - const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts"); - const expectedWatchedFiles = [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase())); - const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests)); - - function createSolution() { - const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation }); - const solutionBuilder = createSolutionBuilder(host, [`${project}/${SubProject.tests}`], {}); - return { host, solutionBuilder }; - } - - function createBuiltSolution() { - const result = createSolution(); - const { host, solutionBuilder } = result; - solutionBuilder.buildAllProjects(); - const outputFileStamps = getOutputFileStamps(host); - for (const stamp of outputFileStamps) { - assert.isDefined(stamp[1], `${stamp[0]} expected to be present`); - } - return result; - } - - function verifyWatches(host: WatchedSystem) { - checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); - checkWatchedDirectories(host, emptyArray, /*recursive*/ false); - checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true); - } - - function createSolutionAndWatchMode() { - // Build the composite project - const { host, solutionBuilder } = createBuiltSolution(); - - // Build in watch mode - const watch = createWatchOfConfigFileReturningBuilder(tests[0].path, host); - checkOutputErrorsInitial(host, emptyArray); - - return { host, solutionBuilder, watch }; - } - - function verifyDependencies(watch: () => BuilderProgram, filePath: string, expected: ReadonlyArray) { - checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!), expected); - } - describe("invoking when references are already built", () => { - it("verifies dependencies and watches", () => { - const { host, watch } = createSolutionAndWatchMode(); + function verifyWatchesOfProject(host: WatchedSystem, expectedWatchedFiles: ReadonlyArray, expectedWatchedDirectoriesRecursive: ReadonlyArray) { + checkWatchedFilesDetailed(host, expectedWatchedFiles, 1); + checkWatchedDirectories(host, emptyArray, /*recursive*/ false); + checkWatchedDirectoriesDetailed(host, expectedWatchedDirectoriesRecursive, 1, /*recursive*/ true); + } - verifyWatches(host); - verifyDependencies(watch, coreIndexDts, [coreIndexDts]); - verifyDependencies(watch, coreAnotherModuleDts, [coreAnotherModuleDts]); - verifyDependencies(watch, logicIndexDts, [logicIndexDts, coreAnotherModuleDts]); - verifyDependencies(watch, tests[1].path, [tests[1].path, coreAnotherModuleDts, logicIndexDts, coreAnotherModuleDts]); - }); + function createSolutionAndWatchModeOfProject( + allFiles: ReadonlyArray, + currentDirectory: string, + solutionBuilderconfig: string, + watchConfig: string, + getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray) { + // Build the composite project + const host = createWatchedSystem(allFiles, { currentDirectory }); + const solutionBuilder = createSolutionBuilder(host, [solutionBuilderconfig], {}); + solutionBuilder.buildAllProjects(); + const outputFileStamps = getOutputFileStamps(host); + for (const stamp of outputFileStamps) { + assert.isDefined(stamp[1], `${stamp[0]} expected to be present`); + } - it("local edit in ts file, result in watch compilation because logic.d.ts is written", () => { - const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); - host.writeFile(logic[1].path, `${logic[1].content} + // Build in watch mode + const watch = createWatchOfConfigFileReturningBuilder(watchConfig, host); + checkOutputErrorsInitial(host, emptyArray); + + return { host, solutionBuilder, watch }; + } + + function verifyDependencies(watch: () => BuilderProgram, filePath: string, expected: ReadonlyArray) { + checkArray(`${filePath} dependencies`, watch().getAllDependencies(watch().getSourceFile(filePath)!), expected); + } + + describe("on sample project", () => { + const coreIndexDts = projectFileName(SubProject.core, "index.d.ts"); + const coreAnotherModuleDts = projectFileName(SubProject.core, "anotherModule.d.ts"); + const logicIndexDts = projectFileName(SubProject.logic, "index.d.ts"); + const expectedWatchedFiles = [core[0], logic[0], ...tests, libFile].map(f => f.path).concat([coreIndexDts, coreAnotherModuleDts, logicIndexDts].map(f => f.toLowerCase())); + const expectedWatchedDirectoriesRecursive = projectSystem.getTypeRootsFromLocation(projectPath(SubProject.tests)); + + function createSolutionAndWatchMode() { + return createSolutionAndWatchModeOfProject(allFiles, projectsLocation, `${project}/${SubProject.tests}`, tests[0].path, getOutputFileStamps); + } + + function verifyWatches(host: WatchedSystem) { + verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive); + } + + it("verifies dependencies and watches", () => { + const { host, watch } = createSolutionAndWatchMode(); + + verifyWatches(host); + verifyDependencies(watch, coreIndexDts, [coreIndexDts]); + verifyDependencies(watch, coreAnotherModuleDts, [coreAnotherModuleDts]); + verifyDependencies(watch, logicIndexDts, [logicIndexDts, coreAnotherModuleDts]); + verifyDependencies(watch, tests[1].path, [tests[1].path, coreAnotherModuleDts, logicIndexDts, coreAnotherModuleDts]); + }); + + it("local edit in ts file, result in watch compilation because logic.d.ts is written", () => { + const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); + host.writeFile(logic[1].path, `${logic[1].content} function foo() { }`); - solutionBuilder.invalidateProject(`${project}/${SubProject.logic}`); - solutionBuilder.buildInvalidatedProject(); + solutionBuilder.invalidateProject(`${project}/${SubProject.logic}`); + solutionBuilder.buildInvalidatedProject(); - host.checkTimeoutQueueLengthAndRun(1); // not ideal, but currently because of d.ts but no new file is written - checkOutputErrorsIncremental(host, emptyArray); - checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts]); - }); + host.checkTimeoutQueueLengthAndRun(1); // not ideal, but currently because of d.ts but no new file is written + checkOutputErrorsIncremental(host, emptyArray); + checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts]); + }); - it("non local edit in ts file, rebuilds in watch compilation", () => { - const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); - host.writeFile(logic[1].path, `${logic[1].content} + it("non local edit in ts file, rebuilds in watch compilation", () => { + const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); + host.writeFile(logic[1].path, `${logic[1].content} export function gfoo() { }`); - solutionBuilder.invalidateProject(logic[0].path); - solutionBuilder.buildInvalidatedProject(); + solutionBuilder.invalidateProject(logic[0].path); + solutionBuilder.buildInvalidatedProject(); - host.checkTimeoutQueueLengthAndRun(1); - checkOutputErrorsIncremental(host, emptyArray); - checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts]); + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, logicIndexDts]); + }); + + it("change in project reference config file builds correctly", () => { + const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); + host.writeFile(logic[0].path, JSON.stringify({ + compilerOptions: { composite: true, declaration: true, declarationDir: "decls" }, + references: [{ path: "../core" }] + })); + solutionBuilder.invalidateProject(logic[0].path, ConfigFileProgramReloadLevel.Full); + solutionBuilder.buildInvalidatedProject(); + + host.checkTimeoutQueueLengthAndRun(1); + checkOutputErrorsIncremental(host, emptyArray); + checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]); + }); }); - it("change in project reference config file builds correctly", () => { - const { host, solutionBuilder, watch } = createSolutionAndWatchMode(); - host.writeFile(logic[0].path, JSON.stringify({ - compilerOptions: { composite: true, declaration: true, declarationDir: "decls" }, - references: [{ path: "../core" }] - })); - solutionBuilder.invalidateProject(logic[0].path, ConfigFileProgramReloadLevel.Full); - solutionBuilder.buildInvalidatedProject(); + describe("on transitive references", () => { + const project = "transitiveReferences"; + const aTs = getFileFromProject(project, "a.ts"); + const bTs = getFileFromProject(project, "b.ts"); + const cTs = getFileFromProject(project, "c.ts"); - host.checkTimeoutQueueLengthAndRun(1); - checkOutputErrorsIncremental(host, emptyArray); - checkProgramActualFiles(watch().getProgram(), [tests[1].path, libFile.path, coreIndexDts, coreAnotherModuleDts, projectFilePath(SubProject.logic, "decls/index.d.ts")]); + const configToBuild = "tsconfig.c.json"; + const aTsconfig = getFileFromProject(project, "tsconfig.a.json"); + const bTsconfig = getFileFromProject(project, "tsconfig.b.json"); + const cTsconfig = getFileFromProject(project, configToBuild); + const refs = getFileFromProject(project, "refs/a.d.ts"); + const allFiles = [libFile, aTs, bTs, cTs, aTsconfig, bTsconfig, cTsconfig, refs]; + const aDts = dtsFile("a"), bDts = dtsFile("b"); + const expectedFiles = [jsFile("a"), aDts, jsFile("b"), bDts, jsFile("c")]; + const expectedProgramFiles = [cTs.path, libFile.path, aDts, refs.path, bDts]; + const expectedWatchedFiles = expectedProgramFiles.concat(cTsconfig.path, bTsconfig.path, aTsconfig.path).map(s => s.toLowerCase()); + const expectedWatchedDirectoriesRecursive = [ + getFilePathInProject(project, "refs").toLowerCase(), // Failed lookup since refs/a.ts does not exist + ...projectSystem.getTypeRootsFromLocation(getProjectPath(project).toLowerCase())]; + + function jsFile(extensionLessFile: string) { + return getFilePathInProject(project, `${extensionLessFile}.js`); + } + + function dtsFile(extensionLessFile: string) { + return getFilePathInProject(project, `${extensionLessFile}.d.ts`); + } + + function createSolutionAndWatchMode() { + return createSolutionAndWatchModeOfProject(allFiles, getProjectPath(project), configToBuild, configToBuild, getOutputFileStamps); + } + + function getOutputFileStamps(host: WatchedSystem) { + return expectedFiles.map(file => [file, host.getModifiedTime(file)] as OutputFileStamp); + } + + function verifyWatches(host: WatchedSystem) { + verifyWatchesOfProject(host, expectedWatchedFiles, expectedWatchedDirectoriesRecursive); + } + + function verifyProgram(host: WatchedSystem, watch: () => BuilderProgram) { + verifyWatches(host); + + verifyDependencies(watch, aDts, [aDts]); + verifyDependencies(watch, refs.path, [refs.path]); + verifyDependencies(watch, bDts, [bDts, aDts]); + verifyDependencies(watch, cTs.path, [cTs.path, refs.path, bDts]); + + checkProgramActualFiles(watch().getProgram(), expectedProgramFiles); + } + + it("verifies dependencies and watches", () => { + // Initial build + const { host, watch } = createSolutionAndWatchMode(); + verifyProgram(host, watch); + }); }); }); }); diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 942b3978e75..0df8c4643ff 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2688,11 +2688,11 @@ declare namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; readDirectory?(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray | undefined, includes: ReadonlyArray, depth?: number): string[]; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): (ResolvedModule | undefined)[]; /** * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[]; getEnvironmentVariable?(name: string): string | undefined; createHash?(data: string): string; } @@ -3606,7 +3606,7 @@ declare namespace ts { * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups * is assumed to be the same as root directory of the project. */ - function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations; + function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirectiveWithFailedLookupLocations; /** * Given a set of options, returns the set of type directive names * that should be included for this program automatically. @@ -3621,14 +3621,14 @@ declare namespace ts { * This assumes that any module id will have the same resolution for sibling files located in the same folder. */ interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { - getOrCreateCacheForDirectory(directoryName: string): Map; + getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map; } /** * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. */ interface NonRelativeModuleNameResolutionCache { - getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache; + getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; } interface PerModuleNameCache { get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; @@ -3636,9 +3636,9 @@ declare namespace ts { } function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache; function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined; - function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations; - function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations; - function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations; + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; + function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; + function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; } declare namespace ts { function createNodeArray(elements?: ReadonlyArray, hasTrailingComma?: boolean): NodeArray; @@ -4384,9 +4384,9 @@ declare namespace ts { /** If provided is used to get the environment variable */ getEnvironmentVariable?(name: string): string | undefined; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): ResolvedModule[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[]; } /** * Host to create watch with root files and options @@ -4654,9 +4654,9 @@ declare namespace ts { realpath?(path: string): string; fileExists?(path: string): boolean; getTypeRootsVersion?(): number; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): ResolvedModule[]; getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[]; getDirectories?(directoryName: string): string[]; /** * Gets a set of custom transformers to use during emit. @@ -8129,9 +8129,9 @@ declare namespace ts.server { readFile(fileName: string): string | undefined; writeFile(fileName: string, content: string): void; fileExists(file: string): boolean; - resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModuleFull[]; + resolveModuleNames(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): ResolvedModuleFull[]; getResolvedModuleWithFailedLookupLocationsFromCache(moduleName: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[]; directoryExists(path: string): boolean; getDirectories(path: string): string[]; log(s: string): void; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index ee9ce336ad9..55b4b10e5c2 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2688,11 +2688,11 @@ declare namespace ts { useCaseSensitiveFileNames(): boolean; getNewLine(): string; readDirectory?(rootDir: string, extensions: ReadonlyArray, excludes: ReadonlyArray | undefined, includes: ReadonlyArray, depth?: number): string[]; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): (ResolvedModule | undefined)[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): (ResolvedModule | undefined)[]; /** * This method is a companion for 'resolveModuleNames' and is used to resolve 'types' references to actual type declaration files */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[]; getEnvironmentVariable?(name: string): string | undefined; createHash?(data: string): string; } @@ -3606,7 +3606,7 @@ declare namespace ts { * This is possible in case if resolution is performed for directives specified via 'types' parameter. In this case initial path for secondary lookups * is assumed to be the same as root directory of the project. */ - function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost): ResolvedTypeReferenceDirectiveWithFailedLookupLocations; + function resolveTypeReferenceDirective(typeReferenceDirectiveName: string, containingFile: string | undefined, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirectiveWithFailedLookupLocations; /** * Given a set of options, returns the set of type directive names * that should be included for this program automatically. @@ -3621,14 +3621,14 @@ declare namespace ts { * This assumes that any module id will have the same resolution for sibling files located in the same folder. */ interface ModuleResolutionCache extends NonRelativeModuleNameResolutionCache { - getOrCreateCacheForDirectory(directoryName: string): Map; + getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map; } /** * Stored map from non-relative module name to a table: directory -> result of module lookup in this directory * We support only non-relative module names because resolution of relative module names is usually more deterministic and thus less expensive. */ interface NonRelativeModuleNameResolutionCache { - getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache; + getOrCreateCacheForModuleName(nonRelativeModuleName: string, redirectedReference?: ResolvedProjectReference): PerModuleNameCache; } interface PerModuleNameCache { get(directory: string): ResolvedModuleWithFailedLookupLocations | undefined; @@ -3636,9 +3636,9 @@ declare namespace ts { } function createModuleResolutionCache(currentDirectory: string, getCanonicalFileName: (s: string) => string): ModuleResolutionCache; function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations | undefined; - function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations; - function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations; - function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache): ResolvedModuleWithFailedLookupLocations; + function resolveModuleName(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; + function nodeModuleNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; + function classicNameResolver(moduleName: string, containingFile: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: NonRelativeModuleNameResolutionCache, redirectedReference?: ResolvedProjectReference): ResolvedModuleWithFailedLookupLocations; } declare namespace ts { function createNodeArray(elements?: ReadonlyArray, hasTrailingComma?: boolean): NodeArray; @@ -4384,9 +4384,9 @@ declare namespace ts { /** If provided is used to get the environment variable */ getEnvironmentVariable?(name: string): string | undefined; /** If provided, used to resolve the module names, otherwise typescript's default module resolution */ - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): ResolvedModule[]; /** If provided, used to resolve type reference directives, otherwise typescript's default resolution */ - resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[]; } /** * Host to create watch with root files and options @@ -4654,9 +4654,9 @@ declare namespace ts { realpath?(path: string): string; fileExists?(path: string): boolean; getTypeRootsVersion?(): number; - resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[]; + resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference): ResolvedModule[]; getResolvedModuleWithFailedLookupLocationsFromCache?(modulename: string, containingFile: string): ResolvedModuleWithFailedLookupLocations | undefined; - resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[]; + resolveTypeReferenceDirectives?(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference): ResolvedTypeReferenceDirective[]; getDirectories?(directoryName: string): string[]; /** * Gets a set of custom transformers to use during emit. diff --git a/tests/projects/transitiveReferences/c.ts b/tests/projects/transitiveReferences/c.ts index b436b3db99c..d45b2621f3e 100644 --- a/tests/projects/transitiveReferences/c.ts +++ b/tests/projects/transitiveReferences/c.ts @@ -1,2 +1,4 @@ import {b} from './b'; -console.log(b); \ No newline at end of file +import {X} from "@ref/a"; +b; +X; \ No newline at end of file diff --git a/tests/projects/transitiveReferences/refs/a.d.ts b/tests/projects/transitiveReferences/refs/a.d.ts new file mode 100644 index 00000000000..9a5b34f9a2b --- /dev/null +++ b/tests/projects/transitiveReferences/refs/a.d.ts @@ -0,0 +1,2 @@ +export class X {} +export class A {} diff --git a/tests/projects/transitiveReferences/tsconfig.c.json b/tests/projects/transitiveReferences/tsconfig.c.json index 1f919a04cd9..c9ddec97111 100644 --- a/tests/projects/transitiveReferences/tsconfig.c.json +++ b/tests/projects/transitiveReferences/tsconfig.c.json @@ -1 +1,10 @@ -{"files": ["c.ts"], "references": [{"path": "tsconfig.b.json"}]} +{ + "files": [ "c.ts" ], + "compilerOptions": { + "baseUrl": "./", + "paths": { + "@ref/*": [ "./refs/*" ] + } + }, + "references": [ { "path": "tsconfig.b.json" } ] +}