Skip to content

Commit d94b8e4

Browse files
authored
Fixes calculating resolved project reference to redirect for module resolution (#40954)
* Add test for #38711 * Fixes calculating resolved project reference to redirect for module resolution Fixes #38711 * Update src/compiler/program.ts
1 parent 28469fb commit d94b8e4

File tree

5 files changed

+524
-27
lines changed

5 files changed

+524
-27
lines changed

src/compiler/program.ts

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -989,22 +989,60 @@ namespace ts {
989989

990990
return program;
991991

992-
function resolveModuleNamesWorker(moduleNames: string[], containingFile: string, reusedNames?: string[], redirectedReference?: ResolvedProjectReference) {
992+
function resolveModuleNamesWorker(moduleNames: string[], containingFile: SourceFile, reusedNames: string[] | undefined): readonly ResolvedModuleFull[] {
993+
if (!moduleNames.length) return emptyArray;
994+
const containingFileName = getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory);
995+
const redirectedReference = getRedirectReferenceForResolution(containingFile);
993996
performance.mark("beforeResolveModule");
994-
const result = actualResolveModuleNamesWorker(moduleNames, containingFile, reusedNames, redirectedReference);
997+
const result = actualResolveModuleNamesWorker(moduleNames, containingFileName, reusedNames, redirectedReference);
995998
performance.mark("afterResolveModule");
996999
performance.measure("ResolveModule", "beforeResolveModule", "afterResolveModule");
9971000
return result;
9981001
}
9991002

1000-
function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[], containingFile: string, redirectedReference?: ResolvedProjectReference) {
1003+
function resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames: string[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] {
1004+
if (!typeDirectiveNames.length) return [];
10011005
performance.mark("beforeResolveTypeReference");
1002-
const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFile, redirectedReference);
1006+
const containingFileName = !isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile;
1007+
const redirectedReference = !isString(containingFile) ? getRedirectReferenceForResolution(containingFile) : undefined;
1008+
const result = actualResolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFileName, redirectedReference);
10031009
performance.mark("afterResolveTypeReference");
10041010
performance.measure("ResolveTypeReference", "beforeResolveTypeReference", "afterResolveTypeReference");
10051011
return result;
10061012
}
10071013

1014+
function getRedirectReferenceForResolution(file: SourceFile) {
1015+
const redirect = getResolvedProjectReferenceToRedirect(file.originalFileName);
1016+
if (redirect || !fileExtensionIs(file.originalFileName, Extension.Dts)) return redirect;
1017+
1018+
// The originalFileName could not be actual source file name if file found was d.ts from referecned project
1019+
// So in this case try to look up if this is output from referenced project, if it is use the redirected project in that case
1020+
const resultFromDts = getRedirectReferenceForResolutionFromSourceOfProject(file.originalFileName, file.path);
1021+
if (resultFromDts) return resultFromDts;
1022+
1023+
// If preserveSymlinks is true, module resolution wont jump the symlink
1024+
// but the resolved real path may be the .d.ts from project reference
1025+
// Note:: Currently we try the real path only if the
1026+
// file is from node_modules to avoid having to run real path on all file paths
1027+
if (!host.realpath || !options.preserveSymlinks || !stringContains(file.originalFileName, nodeModulesPathPart)) return undefined;
1028+
const realDeclarationFileName = host.realpath(file.originalFileName);
1029+
const realDeclarationPath = toPath(realDeclarationFileName);
1030+
return realDeclarationPath === file.path ? undefined : getRedirectReferenceForResolutionFromSourceOfProject(realDeclarationFileName, realDeclarationPath);
1031+
}
1032+
1033+
function getRedirectReferenceForResolutionFromSourceOfProject(fileName: string, filePath: Path) {
1034+
const source = getSourceOfProjectReferenceRedirect(fileName);
1035+
if (isString(source)) return getResolvedProjectReferenceToRedirect(source);
1036+
if (!source) return undefined;
1037+
// Output of .d.ts file so return resolved ref that matches the out file name
1038+
return forEachResolvedProjectReference(resolvedRef => {
1039+
if (!resolvedRef) return undefined;
1040+
const out = outFile(resolvedRef.commandLine.options);
1041+
if (!out) return undefined;
1042+
return toPath(out) === filePath ? resolvedRef : undefined;
1043+
});
1044+
}
1045+
10081046
function compareDefaultLibFiles(a: SourceFile, b: SourceFile) {
10091047
return compareValues(getDefaultLibFilePriority(a), getDefaultLibFilePriority(b));
10101048
}
@@ -1068,14 +1106,14 @@ namespace ts {
10681106
return classifiableNames;
10691107
}
10701108

1071-
function resolveModuleNamesReusingOldState(moduleNames: string[], containingFile: string, file: SourceFile) {
1109+
function resolveModuleNamesReusingOldState(moduleNames: string[], file: SourceFile): readonly ResolvedModuleFull[] {
10721110
if (structuralIsReused === StructureIsReused.Not && !file.ambientModuleNames.length) {
10731111
// If the old program state does not permit reusing resolutions and `file` does not contain locally defined ambient modules,
10741112
// the best we can do is fallback to the default logic.
1075-
return resolveModuleNamesWorker(moduleNames, containingFile, /*reusedNames*/ undefined, getResolvedProjectReferenceToRedirect(file.originalFileName));
1113+
return resolveModuleNamesWorker(moduleNames, file, /*reusedNames*/ undefined);
10761114
}
10771115

1078-
const oldSourceFile = oldProgram && oldProgram.getSourceFile(containingFile);
1116+
const oldSourceFile = oldProgram && oldProgram.getSourceFile(file.fileName);
10791117
if (oldSourceFile !== file && file.resolvedModules) {
10801118
// `file` was created for the new program.
10811119
//
@@ -1120,7 +1158,7 @@ namespace ts {
11201158
const oldResolvedModule = getResolvedModule(oldSourceFile, moduleName);
11211159
if (oldResolvedModule) {
11221160
if (isTraceEnabled(options, host)) {
1123-
trace(host, Diagnostics.Reusing_resolution_of_module_0_to_file_1_from_old_program, moduleName, containingFile);
1161+
trace(host, Diagnostics.Reusing_resolution_of_module_0_to_file_1_from_old_program, moduleName, getNormalizedAbsolutePath(file.originalFileName, currentDirectory));
11241162
}
11251163
(result || (result = new Array(moduleNames.length)))[i] = oldResolvedModule;
11261164
(reusedNames || (reusedNames = [])).push(moduleName);
@@ -1135,7 +1173,7 @@ namespace ts {
11351173
if (contains(file.ambientModuleNames, moduleName)) {
11361174
resolvesToAmbientModuleInNonModifiedFile = true;
11371175
if (isTraceEnabled(options, host)) {
1138-
trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, containingFile);
1176+
trace(host, Diagnostics.Module_0_was_resolved_as_locally_declared_ambient_module_in_file_1, moduleName, getNormalizedAbsolutePath(file.originalFileName, currentDirectory));
11391177
}
11401178
}
11411179
else {
@@ -1152,7 +1190,7 @@ namespace ts {
11521190
}
11531191

11541192
const resolutions = unknownModuleNames && unknownModuleNames.length
1155-
? resolveModuleNamesWorker(unknownModuleNames, containingFile, reusedNames, getResolvedProjectReferenceToRedirect(file.originalFileName))
1193+
? resolveModuleNamesWorker(unknownModuleNames, file, reusedNames)
11561194
: emptyArray;
11571195

11581196
// Combine results of resolutions and predicted results
@@ -1397,9 +1435,8 @@ namespace ts {
13971435
}
13981436
// try to verify results of module resolution
13991437
for (const { oldFile: oldSourceFile, newFile: newSourceFile } of modifiedSourceFiles) {
1400-
const newSourceFilePath = getNormalizedAbsolutePath(newSourceFile.originalFileName, currentDirectory);
14011438
const moduleNames = getModuleNames(newSourceFile);
1402-
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFilePath, newSourceFile);
1439+
const resolutions = resolveModuleNamesReusingOldState(moduleNames, newSourceFile);
14031440
// ensure that module resolution results are still correct
14041441
const resolutionsChanged = hasChangesInResolutions(moduleNames, resolutions, oldSourceFile.resolvedModules, moduleResolutionIsEqualTo);
14051442
if (resolutionsChanged) {
@@ -1409,19 +1446,17 @@ namespace ts {
14091446
else {
14101447
newSourceFile.resolvedModules = oldSourceFile.resolvedModules;
14111448
}
1412-
if (resolveTypeReferenceDirectiveNamesWorker) {
1413-
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
1414-
const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName));
1415-
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFilePath, getResolvedProjectReferenceToRedirect(newSourceFile.originalFileName));
1416-
// ensure that types resolutions are still correct
1417-
const resolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, resolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
1418-
if (resolutionsChanged) {
1419-
oldProgram.structureIsReused = StructureIsReused.SafeModules;
1420-
newSourceFile.resolvedTypeReferenceDirectiveNames = zipToMap(typesReferenceDirectives, resolutions);
1421-
}
1422-
else {
1423-
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
1424-
}
1449+
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
1450+
const typesReferenceDirectives = map(newSourceFile.typeReferenceDirectives, ref => toFileNameLowerCase(ref.fileName));
1451+
const typeReferenceResolutions = resolveTypeReferenceDirectiveNamesWorker(typesReferenceDirectives, newSourceFile);
1452+
// ensure that types resolutions are still correct
1453+
const typeReferenceEesolutionsChanged = hasChangesInResolutions(typesReferenceDirectives, typeReferenceResolutions, oldSourceFile.resolvedTypeReferenceDirectiveNames, typeDirectiveIsEqualTo);
1454+
if (typeReferenceEesolutionsChanged) {
1455+
oldProgram.structureIsReused = StructureIsReused.SafeModules;
1456+
newSourceFile.resolvedTypeReferenceDirectiveNames = zipToMap(typesReferenceDirectives, typeReferenceResolutions);
1457+
}
1458+
else {
1459+
newSourceFile.resolvedTypeReferenceDirectiveNames = oldSourceFile.resolvedTypeReferenceDirectiveNames;
14251460
}
14261461
}
14271462

@@ -2685,7 +2720,7 @@ namespace ts {
26852720
return;
26862721
}
26872722

2688-
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file.originalFileName, getResolvedProjectReferenceToRedirect(file.originalFileName));
2723+
const resolutions = resolveTypeReferenceDirectiveNamesWorker(typeDirectives, file);
26892724

26902725
for (let i = 0; i < typeDirectives.length; i++) {
26912726
const ref = file.typeReferenceDirectives[i];
@@ -2823,7 +2858,7 @@ namespace ts {
28232858
if (file.imports.length || file.moduleAugmentations.length) {
28242859
// Because global augmentation doesn't have string literal name, we can check for global augmentation as such.
28252860
const moduleNames = getModuleNames(file);
2826-
const resolutions = resolveModuleNamesReusingOldState(moduleNames, getNormalizedAbsolutePath(file.originalFileName, currentDirectory), file);
2861+
const resolutions = resolveModuleNamesReusingOldState(moduleNames, file);
28272862
Debug.assert(resolutions.length === moduleNames.length);
28282863
for (let i = 0; i < moduleNames.length; i++) {
28292864
const resolution = resolutions[i];

src/testRunner/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@
120120
"unittests/tsbuild/inferredTypeFromTransitiveModule.ts",
121121
"unittests/tsbuild/javascriptProjectEmit.ts",
122122
"unittests/tsbuild/lateBoundSymbol.ts",
123+
"unittests/tsbuild/moduleResolution.ts",
123124
"unittests/tsbuild/moduleSpecifiers.ts",
124125
"unittests/tsbuild/noEmitOnError.ts",
125126
"unittests/tsbuild/outFile.ts",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
namespace ts.tscWatch {
2+
describe("unittests:: tsbuild:: moduleResolution:: handles the modules and options from referenced project correctly", () => {
3+
function sys(optionsToExtend?: CompilerOptions) {
4+
return createWatchedSystem([
5+
{
6+
path: `${projectRoot}/packages/pkg1/index.ts`,
7+
content: Utils.dedent`
8+
import type { TheNum } from 'pkg2'
9+
export const theNum: TheNum = 42;`
10+
},
11+
{
12+
path: `${projectRoot}/packages/pkg1/tsconfig.json`,
13+
content: JSON.stringify({
14+
compilerOptions: { outDir: "build", ...optionsToExtend },
15+
references: [{ path: "../pkg2" }]
16+
})
17+
},
18+
{
19+
path: `${projectRoot}/packages/pkg2/const.ts`,
20+
content: `export type TheNum = 42;`
21+
},
22+
{
23+
path: `${projectRoot}/packages/pkg2/index.ts`,
24+
content: `export type { TheNum } from 'const';`
25+
},
26+
{
27+
path: `${projectRoot}/packages/pkg2/tsconfig.json`,
28+
content: JSON.stringify({
29+
compilerOptions: {
30+
composite: true,
31+
outDir: "build",
32+
baseUrl: ".",
33+
...optionsToExtend
34+
}
35+
})
36+
},
37+
{
38+
path: `${projectRoot}/packages/pkg2/package.json`,
39+
content: JSON.stringify({
40+
name: "pkg2",
41+
version: "1.0.0",
42+
main: "build/index.js",
43+
})
44+
},
45+
{
46+
path: `${projectRoot}/node_modules/pkg2`,
47+
symLink: `${projectRoot}/packages/pkg2`,
48+
},
49+
libFile
50+
], { currentDirectory: projectRoot });
51+
}
52+
53+
verifyTscWatch({
54+
scenario: "moduleResolution",
55+
subScenario: `resolves specifier in output declaration file from referenced project correctly`,
56+
sys,
57+
commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"],
58+
changes: emptyArray
59+
});
60+
61+
verifyTscWatch({
62+
scenario: "moduleResolution",
63+
subScenario: `resolves specifier in output declaration file from referenced project correctly with preserveSymlinks`,
64+
sys: () => sys({ preserveSymlinks: true }),
65+
commandLineArgs: ["-b", "packages/pkg1", "--verbose", "--traceResolution"],
66+
changes: emptyArray
67+
});
68+
});
69+
}

0 commit comments

Comments
 (0)