Handle caching of module resolution depending on project references

This commit is contained in:
Sheetal Nandi 2018-10-01 16:07:52 -07:00
parent 0e4b10d726
commit 6923f2cdb0
9 changed files with 284 additions and 157 deletions

View File

@ -345,7 +345,7 @@ namespace ts {
}
let result: SearchResult<Resolved> | 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<ResolvedModuleWithFailedLookupLocations>;
getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map<ResolvedModuleWithFailedLookupLocations>;
}
/**
@ -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<Map<ResolvedModuleWithFailedLookupLocations>>(),
createMap<PerModuleNameCache>(),
createCacheWithRedirects(),
createCacheWithRedirects(),
currentDirectory,
getCanonicalFileName
);
}
/*@internal*/
export interface CacheWithRedirects<T> {
ownMap: Map<T>;
redirectsMap: Map<Map<T>>;
getOrCreateMapOfCacheRedirects(redirectedReference: ResolvedProjectReference | undefined): Map<T>;
clear(): void;
}
/*@internal*/
export function createCacheWithRedirects<T>(): CacheWithRedirects<T> {
const ownMap: Map<T> = createMap();
const redirectsMap: Map<Map<T>> = 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<Map<ResolvedModuleWithFailedLookupLocations>>,
moduleNameToDirectoryMap: Map<PerModuleNameCache>,
directoryToModuleNameMap: CacheWithRedirects<Map<ResolvedModuleWithFailedLookupLocations>>,
moduleNameToDirectoryMap: CacheWithRedirects<PerModuleNameCache>,
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<ResolvedModuleWithFailedLookupLocations>();
directoryToModuleNameMap.set(path, perFolderCache);
}
return perFolderCache;
return getOrCreateCache<Map<ResolvedModuleWithFailedLookupLocations>>(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<T>(cacheWithRedirects: CacheWithRedirects<T>, 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<Resolved> {
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<Resolved> {
return loadModuleFromNearestNodeModulesDirectoryWorker(extensions, moduleName, directory, state, /*typesScopeOnly*/ false, cache, redirectedReference);
}
function loadModuleFromNearestNodeModulesDirectoryTypesScope(moduleName: string, directory: string, state: ModuleResolutionState): SearchResult<Resolved> {
// 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<Resolved> {
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<Resolved> {
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);

View File

@ -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<Map<CachedResolvedModuleWithFailedLookupLocations>>();
const perDirectoryResolvedModuleNames = createMap<Map<CachedResolvedModuleWithFailedLookupLocations>>();
const nonRelaticeModuleNameCache = createMap<PerModuleNameCache>();
const perDirectoryResolvedModuleNames: CacheWithRedirects<Map<CachedResolvedModuleWithFailedLookupLocations>> = createCacheWithRedirects();
const nonRelativeModuleNameCache: CacheWithRedirects<PerModuleNameCache> = createCacheWithRedirects();
const moduleResolutionCache = createModuleResolutionCacheWithMaps(
perDirectoryResolvedModuleNames,
nonRelaticeModuleNameCache,
nonRelativeModuleNameCache,
getCurrentDirectory(),
resolutionHost.getCanonicalFileName
);
const resolvedTypeReferenceDirectives = createMap<Map<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
const perDirectoryResolvedTypeReferenceDirectives = createMap<Map<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>>();
const perDirectoryResolvedTypeReferenceDirectives: CacheWithRedirects<Map<CachedResolvedTypeReferenceDirectiveWithFailedLookupLocations>> = 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<Map<T>>,
perDirectoryCache: Map<Map<T>>,
perDirectoryCacheWithRedirects: CacheWithRedirects<Map<T>>,
loader: (name: string, containingFile: string, options: CompilerOptions, host: ModuleResolutionHost, redirectedReference?: ResolvedProjectReference) => T,
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
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();

View File

@ -392,6 +392,7 @@ export class cNew {}`);
...getLibs(),
"/src/a.d.ts",
"/src/b.d.ts",
"/src/refs/a.d.ts",
"/src/c.ts"
]);
});

View File

@ -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<File> = [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<File>, 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<string>) {
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<string>, expectedWatchedDirectoriesRecursive: ReadonlyArray<string>) {
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<File>,
currentDirectory: string,
solutionBuilderconfig: string,
watchConfig: string,
getOutputFileStamps: (host: WatchedSystem) => ReadonlyArray<OutputFileStamp>) {
// 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<string>) {
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);
});
});
});
});

View File

@ -2688,11 +2688,11 @@ declare namespace ts {
useCaseSensitiveFileNames(): boolean;
getNewLine(): string;
readDirectory?(rootDir: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string>, 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<ResolvedModuleWithFailedLookupLocations>;
getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map<ResolvedModuleWithFailedLookupLocations>;
}
/**
* 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<T extends Node>(elements?: ReadonlyArray<T>, hasTrailingComma?: boolean): NodeArray<T>;
@ -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;

View File

@ -2688,11 +2688,11 @@ declare namespace ts {
useCaseSensitiveFileNames(): boolean;
getNewLine(): string;
readDirectory?(rootDir: string, extensions: ReadonlyArray<string>, excludes: ReadonlyArray<string> | undefined, includes: ReadonlyArray<string>, 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<ResolvedModuleWithFailedLookupLocations>;
getOrCreateCacheForDirectory(directoryName: string, redirectedReference?: ResolvedProjectReference): Map<ResolvedModuleWithFailedLookupLocations>;
}
/**
* 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<T extends Node>(elements?: ReadonlyArray<T>, hasTrailingComma?: boolean): NodeArray<T>;
@ -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.

View File

@ -1,2 +1,4 @@
import {b} from './b';
console.log(b);
import {X} from "@ref/a";
b;
X;

View File

@ -0,0 +1,2 @@
export class X {}
export class A {}

View File

@ -1 +1,10 @@
{"files": ["c.ts"], "references": [{"path": "tsconfig.b.json"}]}
{
"files": [ "c.ts" ],
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@ref/*": [ "./refs/*" ]
}
},
"references": [ { "path": "tsconfig.b.json" } ]
}