Skip to content

Commit 21b2d0b

Browse files
committed
Handle auto type reference directive resolutions
1 parent cddc045 commit 21b2d0b

File tree

3 files changed

+481
-25
lines changed

3 files changed

+481
-25
lines changed

src/compiler/program.ts

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1670,32 +1670,33 @@ namespace ts {
16701670
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: readonly FileReference[], containingFile: SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[];
16711671
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: string[], containingFile: string): readonly (ResolvedTypeReferenceDirective | undefined)[];
16721672
function resolveTypeReferenceDirectiveNamesReusingOldState(typeDirectiveNames: string[] | readonly FileReference[], containingFile: string | SourceFile): readonly (ResolvedTypeReferenceDirective | undefined)[] {
1673-
// TODO: (shkamat) reuse auto type reference resolutions
1674-
if (structureIsReused === StructureIsReused.Not || isString(containingFile)) {
1673+
if (structureIsReused === StructureIsReused.Not) {
16751674
return resolveTypeReferenceDirectiveNamesWorker(typeDirectiveNames, containingFile, /*partialResolutionInfo*/ undefined);
16761675
}
16771676

1678-
const oldSourceFile = oldProgram && oldProgram.getSourceFile(containingFile.fileName);
1679-
if (oldSourceFile !== containingFile && containingFile.resolvedTypeReferenceDirectiveNames) {
1680-
// `file` was created for the new program.
1681-
//
1682-
// We only set `file.resolvedTypeReferenceDirectiveNames` via work from the current function,
1683-
// so it is defined iff we already called the current function on `file`.
1684-
// That call happened no later than the creation of the `file` object,
1685-
// which per above occurred during the current program creation.
1686-
// Since we assume the filesystem does not change during program creation,
1687-
// it is safe to reuse resolutions from the earlier call.
1688-
const result: ResolvedTypeReferenceDirective[] = [];
1689-
for (const typeDirectiveName of typeDirectiveNames as readonly FileReference[]) {
1690-
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
1691-
const resolvedTypeReferenceDirective = containingFile.resolvedTypeReferenceDirectiveNames.get(typeDirectiveName.fileName.toLowerCase(), typeDirectiveName.resolutionMode || containingFile.impliedNodeFormat)!;
1692-
result.push(resolvedTypeReferenceDirective);
1677+
const oldSourceFile = !isString(containingFile) ? oldProgram && oldProgram.getSourceFile(containingFile.fileName) : undefined;
1678+
if (!isString(containingFile)) {
1679+
if (oldSourceFile !== containingFile && containingFile.resolvedTypeReferenceDirectiveNames) {
1680+
// `file` was created for the new program.
1681+
//
1682+
// We only set `file.resolvedTypeReferenceDirectiveNames` via work from the current function,
1683+
// so it is defined iff we already called the current function on `file`.
1684+
// That call happened no later than the creation of the `file` object,
1685+
// which per above occurred during the current program creation.
1686+
// Since we assume the filesystem does not change during program creation,
1687+
// it is safe to reuse resolutions from the earlier call.
1688+
const result: ResolvedTypeReferenceDirective[] = [];
1689+
for (const typeDirectiveName of typeDirectiveNames as readonly FileReference[]) {
1690+
// We lower-case all type references because npm automatically lowercases all packages. See GH#9824.
1691+
const resolvedTypeReferenceDirective = containingFile.resolvedTypeReferenceDirectiveNames.get(typeDirectiveName.fileName.toLowerCase(), typeDirectiveName.resolutionMode || containingFile.impliedNodeFormat)!;
1692+
result.push(resolvedTypeReferenceDirective);
1693+
}
1694+
return result;
16931695
}
1694-
return result;
16951696
}
16961697

16971698
/** An ordered list of module names for which we cannot recover the resolution. */
1698-
let unknownTypeReferenceDirectiveNames: FileReference[] | undefined;
1699+
let unknownTypeReferenceDirectiveNames: string [] | FileReference[] | undefined;
16991700
let unknownTypeReferenceDirectiveNamesIndex: number[] | undefined;
17001701
/**
17011702
* The indexing of elements in this list matches that of `moduleNames`.
@@ -1705,12 +1706,15 @@ namespace ts {
17051706
*/
17061707
let result: (ResolvedTypeReferenceDirective | undefined)[] | undefined;
17071708
let reusedNames: { name: string; mode: ModuleKind.CommonJS | ModuleKind.ESNext | undefined; }[] | undefined;
1709+
const canReuseResolutions = !isString(containingFile) ?
1710+
containingFile === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path) :
1711+
!hasInvalidatedResolution(toPath(containingFile));
17081712
for (let i = 0; i < typeDirectiveNames.length; i++) {
1709-
const entry = typeDirectiveNames[i] as FileReference;
1713+
const entry = typeDirectiveNames[i];
17101714
// If the source file is unchanged and doesnt have invalidated resolution, reuse the module resolutions
1711-
if (containingFile === oldSourceFile && !hasInvalidatedResolution(oldSourceFile.path)) {
1712-
const typeDirectiveName = entry.fileName.toLowerCase();
1713-
const mode = getModeForFileReference(entry, oldSourceFile.impliedNodeFormat);
1715+
if (canReuseResolutions) {
1716+
const typeDirectiveName = !isString(entry) ? entry.fileName.toLowerCase() : entry;
1717+
const mode = getModeForFileReference(entry, oldSourceFile?.impliedNodeFormat);
17141718
const oldResolvedTypeReferenceDirective = getResolvedTypeReferenceDirective(oldSourceFile, typeDirectiveName, mode);
17151719
if (oldResolvedTypeReferenceDirective) {
17161720
if (isTraceEnabled(options, host)) {
@@ -1719,7 +1723,7 @@ namespace ts {
17191723
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
17201724
Diagnostics.Reusing_resolution_of_type_reference_directive_0_from_1_of_old_program_it_was_successfully_resolved_to_2,
17211725
typeDirectiveName,
1722-
getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory),
1726+
!isString(containingFile) ? getNormalizedAbsolutePath(containingFile.originalFileName, currentDirectory) : containingFile,
17231727
oldResolvedTypeReferenceDirective.resolvedFileName,
17241728
oldResolvedTypeReferenceDirective.packageId && packageIdToString(oldResolvedTypeReferenceDirective.packageId)
17251729
);
@@ -1730,7 +1734,7 @@ namespace ts {
17301734
}
17311735
}
17321736
// Resolution failed in the old program, or resolved to an ambient module for which we can't reuse the result.
1733-
(unknownTypeReferenceDirectiveNames ??= []).push(entry);
1737+
(unknownTypeReferenceDirectiveNames ??= []).push(entry as FileReference & string);
17341738
(unknownTypeReferenceDirectiveNamesIndex ??= []).push(i);
17351739
}
17361740

src/testRunner/unittests/tscWatch/moduleResolution.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,5 +142,94 @@ namespace ts.tscWatch {
142142
}
143143
]
144144
});
145+
146+
verifyTscWatch({
147+
scenario: "moduleResolution",
148+
subScenario: "type reference resolutions reuse",
149+
sys: () => createWatchedSystem([
150+
{
151+
path: `${projectRoot}/tsconfig.json`,
152+
content: JSON.stringify({
153+
compilerOptions: { moduleResolution: "node16" },
154+
})
155+
},
156+
{
157+
path: `${projectRoot}/index.ts`,
158+
content: Utils.dedent`
159+
/// <reference types="pkg" resolution-mode="import"/>
160+
/// <reference types="pkg1" resolution-mode="require"/>
161+
export interface LocalInterface extends RequireInterface {}
162+
`
163+
},
164+
{
165+
path: `${projectRoot}/a.ts`,
166+
content: Utils.dedent`
167+
export const x = 10;
168+
`
169+
},
170+
{
171+
path: `${projectRoot}/node_modules/pkg/package.json`,
172+
content: JSON.stringify({
173+
name: "pkg",
174+
version: "0.0.1",
175+
exports: {
176+
import: "./import.js",
177+
require: "./require.js"
178+
}
179+
})
180+
},
181+
{
182+
path: `${projectRoot}/node_modules/pkg/import.d.ts`,
183+
content: Utils.dedent`
184+
export {};
185+
declare global {
186+
interface ImportInterface {}
187+
}
188+
`
189+
},
190+
{
191+
path: `${projectRoot}/node_modules/pkg/require.d.ts`,
192+
content: Utils.dedent`
193+
export {};
194+
declare global {
195+
interface RequireInterface {}
196+
}
197+
`
198+
},
199+
{
200+
path: `${projectRoot}/node_modules/pkg1/package.json`,
201+
content: JSON.stringify({
202+
name: "pkg1",
203+
version: "0.0.1",
204+
exports: {
205+
import: "./import.js",
206+
require: "./require.js"
207+
}
208+
})
209+
},
210+
{
211+
path: `${projectRoot}/node_modules/pkg1/import.d.ts`,
212+
content: Utils.dedent`
213+
export {};
214+
declare global {
215+
interface ImportInterface {}
216+
}
217+
`
218+
},
219+
{
220+
path: `${projectRoot}/node_modules/@types/pkg2/index.d.ts`,
221+
content: `export const x = 10;`
222+
},
223+
libFile
224+
], { currentDirectory: projectRoot }),
225+
commandLineArgs: ["-w", "--traceResolution"],
226+
changes: [
227+
{
228+
caption: "modify aFile by adding import",
229+
change: sys => sys.prependFile(`${projectRoot}/a.ts`, `/// <reference types="pkg" resolution-mode="import"/>\n`),
230+
timeouts: sys => sys.runQueuedTimeoutCallbacks(),
231+
}
232+
]
233+
});
145234
});
146235
}

0 commit comments

Comments
 (0)