Skip to content

Commit 8b825f7

Browse files
authored
Handle module node found error reporting in incremental and watch scneario (microsoft#54115)
1 parent a4009a3 commit 8b825f7

File tree

16 files changed

+10019
-116
lines changed

16 files changed

+10019
-116
lines changed

src/compiler/builder.ts

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
convertToOptionsWithAbsolutePaths,
2323
createBuildInfo,
2424
createGetCanonicalFileName,
25+
createModuleNotFoundChain,
2526
createProgram,
2627
CustomTransformers,
2728
Debug,
@@ -68,8 +69,11 @@ import {
6869
ProjectReference,
6970
ReadBuildProgramHost,
7071
ReadonlyCollection,
72+
RepopulateDiagnosticChainInfo,
73+
RepopulateModuleNotFoundDiagnosticChain,
7174
returnFalse,
7275
returnUndefined,
76+
sameMap,
7377
SemanticDiagnosticsBuilderProgram,
7478
skipTypeChecking,
7579
some,
@@ -103,7 +107,18 @@ export interface ReusableDiagnosticRelatedInformation {
103107
}
104108

105109
/** @internal */
106-
export type ReusableDiagnosticMessageChain = DiagnosticMessageChain;
110+
export interface ReusableRepopulateModuleNotFoundChain {
111+
info: RepopulateModuleNotFoundDiagnosticChain;
112+
next?: ReusableDiagnosticMessageChain[];
113+
}
114+
115+
/** @internal */
116+
export type SerializedDiagnosticMessageChain = Omit<DiagnosticMessageChain, "next" | "repopulateInfo"> & {
117+
next?: ReusableDiagnosticMessageChain[];
118+
};
119+
120+
/** @internal */
121+
export type ReusableDiagnosticMessageChain = SerializedDiagnosticMessageChain | ReusableRepopulateModuleNotFoundChain;
107122

108123
/**
109124
* Signature (Hash of d.ts emitted), is string if it was emitted using same d.ts.map option as what compilerOptions indicate, otherwise tuple of string
@@ -364,7 +379,12 @@ function createBuilderProgramState(newProgram: Program, oldState: Readonly<Reusa
364379
// Unchanged file copy diagnostics
365380
const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
366381
if (diagnostics) {
367-
state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram) : diagnostics as readonly Diagnostic[]);
382+
state.semanticDiagnosticsPerFile!.set(
383+
sourceFilePath,
384+
oldState!.hasReusableDiagnostic ?
385+
convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram) :
386+
repopulateDiagnostics(diagnostics as readonly Diagnostic[], newProgram)
387+
);
368388
if (!state.semanticDiagnosticsFromOldState) {
369389
state.semanticDiagnosticsFromOldState = new Set();
370390
}
@@ -448,6 +468,43 @@ function getEmitSignatureFromOldSignature(options: CompilerOptions, oldOptions:
448468
isString(oldEmitSignature) ? [oldEmitSignature] : oldEmitSignature[0];
449469
}
450470

471+
function repopulateDiagnostics(diagnostics: readonly Diagnostic[], newProgram: Program): readonly Diagnostic[] {
472+
if (!diagnostics.length) return diagnostics;
473+
return sameMap(diagnostics, diag => {
474+
if (isString(diag.messageText)) return diag;
475+
const repopulatedChain = convertOrRepopulateDiagnosticMessageChain(diag.messageText, diag.file, newProgram, chain => chain.repopulateInfo?.());
476+
return repopulatedChain === diag.messageText ?
477+
diag :
478+
{ ...diag, messageText: repopulatedChain };
479+
});
480+
}
481+
482+
function convertOrRepopulateDiagnosticMessageChain<T extends DiagnosticMessageChain | ReusableDiagnosticMessageChain>(
483+
chain: T,
484+
sourceFile: SourceFile | undefined,
485+
newProgram: Program,
486+
repopulateInfo: (chain: T) => RepopulateDiagnosticChainInfo | undefined,
487+
): DiagnosticMessageChain {
488+
const info = repopulateInfo(chain);
489+
if (info) {
490+
return {
491+
...createModuleNotFoundChain(sourceFile!, newProgram, info.moduleReference, info.mode, info.packageName || info.moduleReference),
492+
next: convertOrRepopulateDiagnosticMessageChainArray(chain.next as T[], sourceFile, newProgram, repopulateInfo),
493+
};
494+
}
495+
const next = convertOrRepopulateDiagnosticMessageChainArray(chain.next as T[], sourceFile, newProgram, repopulateInfo);
496+
return next === chain.next ? chain as DiagnosticMessageChain : { ...chain as DiagnosticMessageChain, next };
497+
}
498+
499+
function convertOrRepopulateDiagnosticMessageChainArray<T extends DiagnosticMessageChain | ReusableDiagnosticMessageChain>(
500+
array: T[] | undefined,
501+
sourceFile: SourceFile | undefined,
502+
newProgram: Program,
503+
repopulateInfo: (chain: T) => RepopulateDiagnosticChainInfo | undefined,
504+
): DiagnosticMessageChain[] | undefined {
505+
return sameMap(array, chain => convertOrRepopulateDiagnosticMessageChain(chain, sourceFile, newProgram, repopulateInfo));
506+
}
507+
451508
function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program): readonly Diagnostic[] {
452509
if (!diagnostics.length) return emptyArray;
453510
let buildInfoDirectory: string | undefined;
@@ -474,9 +531,13 @@ function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newPro
474531

475532
function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation {
476533
const { file } = diagnostic;
534+
const sourceFile = file ? newProgram.getSourceFileByPath(toPath(file)) : undefined;
477535
return {
478536
...diagnostic,
479-
file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined
537+
file: sourceFile,
538+
messageText: isString(diagnostic.messageText) ?
539+
diagnostic.messageText :
540+
convertOrRepopulateDiagnosticMessageChain(diagnostic.messageText, sourceFile, newProgram, chain => (chain as ReusableRepopulateModuleNotFoundChain).info),
480541
};
481542
}
482543

@@ -1232,10 +1293,36 @@ function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRel
12321293
const { file } = diagnostic;
12331294
return {
12341295
...diagnostic,
1235-
file: file ? relativeToBuildInfo(file.resolvedPath) : undefined
1296+
file: file ? relativeToBuildInfo(file.resolvedPath) : undefined,
1297+
messageText: isString(diagnostic.messageText) ? diagnostic.messageText : convertToReusableDiagnosticMessageChain(diagnostic.messageText),
12361298
};
12371299
}
12381300

1301+
function convertToReusableDiagnosticMessageChain(chain: DiagnosticMessageChain): ReusableDiagnosticMessageChain {
1302+
if (chain.repopulateInfo) {
1303+
return {
1304+
info: chain.repopulateInfo(),
1305+
next: convertToReusableDiagnosticMessageChainArray(chain.next),
1306+
};
1307+
}
1308+
const next = convertToReusableDiagnosticMessageChainArray(chain.next);
1309+
return next === chain.next ? chain : { ...chain, next };
1310+
}
1311+
1312+
function convertToReusableDiagnosticMessageChainArray(array: DiagnosticMessageChain[] | undefined): ReusableDiagnosticMessageChain[] | undefined {
1313+
if (!array) return array;
1314+
return forEach(array, (chain, index) => {
1315+
const reusable = convertToReusableDiagnosticMessageChain(chain);
1316+
if (chain === reusable) return undefined;
1317+
const result: ReusableDiagnosticMessageChain[] = index > 0 ? array.slice(0, index - 1) : [];
1318+
result.push(reusable);
1319+
for (let i = index + 1; i < array.length; i++) {
1320+
result.push(convertToReusableDiagnosticMessageChain(array[i]));
1321+
}
1322+
return result;
1323+
}) || array;
1324+
}
1325+
12391326
/** @internal */
12401327
export enum BuilderProgramKind {
12411328
SemanticDiagnosticsBuilderProgram,

src/compiler/checker.ts

Lines changed: 3 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ import {
110110
createGetCanonicalFileName,
111111
createGetSymbolWalker,
112112
createModeAwareCacheKey,
113+
createModuleNotFoundChain,
113114
createMultiMap,
114115
createPrinterWithDefaults,
115116
createPrinterWithRemoveComments,
@@ -359,7 +360,6 @@ import {
359360
getThisParameter,
360361
getTrailingSemicolonDeferringWriter,
361362
getTypeParameterFromJsDoc,
362-
getTypesPackageName,
363363
getUseDefineForClassFields,
364364
group,
365365
hasAbstractModifier,
@@ -806,7 +806,6 @@ import {
806806
LiteralExpression,
807807
LiteralType,
808808
LiteralTypeNode,
809-
mangleScopedPackageName,
810809
map,
811810
mapDefined,
812811
MappedSymbol,
@@ -815,7 +814,6 @@ import {
815814
MatchingKeys,
816815
maybeBind,
817816
MemberOverrideStatus,
818-
memoize,
819817
MetaProperty,
820818
MethodDeclaration,
821819
MethodSignature,
@@ -853,7 +851,6 @@ import {
853851
nodeIsPresent,
854852
nodeIsSynthesized,
855853
NodeLinks,
856-
nodeModulesPathPart,
857854
nodeStartsNewLexicalEnvironment,
858855
NodeWithTypeArguments,
859856
NonNullChain,
@@ -1392,22 +1389,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
13921389
// Why var? It avoids TDZ checks in the runtime which can be costly.
13931390
// See: https://github.com/microsoft/TypeScript/issues/52924
13941391
/* eslint-disable no-var */
1395-
var getPackagesMap = memoize(() => {
1396-
// A package name maps to true when we detect it has .d.ts files.
1397-
// This is useful as an approximation of whether a package bundles its own types.
1398-
// Note: we only look at files already found by module resolution,
1399-
// so there may be files we did not consider.
1400-
var map = new Map<string, boolean>();
1401-
host.getSourceFiles().forEach(sf => {
1402-
if (!sf.resolvedModules) return;
1403-
1404-
sf.resolvedModules.forEach(({ resolvedModule }) => {
1405-
if (resolvedModule?.packageId) map.set(resolvedModule.packageId.name, resolvedModule.extension === Extension.Dts || !!map.get(resolvedModule.packageId.name));
1406-
});
1407-
});
1408-
return map;
1409-
});
1410-
14111392
var deferredDiagnosticsCallbacks: (() => void)[] = [];
14121393

14131394
var addLazyDiagnostic = (arg: () => void) => {
@@ -5068,44 +5049,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
50685049
}
50695050

50705051
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
5071-
let errorInfo;
5052+
let errorInfo: DiagnosticMessageChain | undefined;
50725053
if (!isExternalModuleNameRelative(moduleReference) && packageId) {
5073-
const node10Result = sourceFile.resolvedModules?.get(moduleReference, mode)?.node10Result;
5074-
errorInfo = node10Result
5075-
? chainDiagnosticMessages(
5076-
/*details*/ undefined,
5077-
Diagnostics.There_are_types_at_0_but_this_result_could_not_be_resolved_when_respecting_package_json_exports_The_1_library_may_need_to_update_its_package_json_or_typings,
5078-
node10Result,
5079-
node10Result.indexOf(nodeModulesPathPart + "@types/") > -1 ? `@types/${mangleScopedPackageName(packageId.name)}` : packageId.name)
5080-
: typesPackageExists(packageId.name)
5081-
? chainDiagnosticMessages(
5082-
/*details*/ undefined,
5083-
Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1,
5084-
packageId.name, mangleScopedPackageName(packageId.name))
5085-
: packageBundlesTypes(packageId.name)
5086-
? chainDiagnosticMessages(
5087-
/*details*/ undefined,
5088-
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
5089-
packageId.name,
5090-
moduleReference)
5091-
: chainDiagnosticMessages(
5092-
/*details*/ undefined,
5093-
Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
5094-
moduleReference,
5095-
mangleScopedPackageName(packageId.name));
5054+
errorInfo = createModuleNotFoundChain(sourceFile, host, moduleReference, mode, packageId.name);
50965055
}
50975056
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
50985057
errorInfo,
50995058
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
51005059
moduleReference,
51015060
resolvedFileName));
51025061
}
5103-
function typesPackageExists(packageName: string): boolean {
5104-
return getPackagesMap().has(getTypesPackageName(packageName));
5105-
}
5106-
function packageBundlesTypes(packageName: string): boolean {
5107-
return !!getPackagesMap().get(packageName);
5108-
}
51095062

51105063
function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol;
51115064
function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined;

src/compiler/core.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -364,21 +364,21 @@ export function *mapIterator<T, U>(iter: Iterable<T>, mapFn: (x: T) => U) {
364364
* Maps from T to T and avoids allocation if all elements map to themselves
365365
*
366366
* @internal */
367-
export function sameMap<T>(array: T[], f: (x: T, i: number) => T): T[];
367+
export function sameMap<T, U = T>(array: T[], f: (x: T, i: number) => U): U[];
368368
/** @internal */
369-
export function sameMap<T>(array: readonly T[], f: (x: T, i: number) => T): readonly T[];
369+
export function sameMap<T, U = T>(array: readonly T[], f: (x: T, i: number) => U): readonly U[];
370370
/** @internal */
371-
export function sameMap<T>(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined;
371+
export function sameMap<T, U = T>(array: T[] | undefined, f: (x: T, i: number) => U): U[] | undefined;
372372
/** @internal */
373-
export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined;
373+
export function sameMap<T, U = T>(array: readonly T[] | undefined, f: (x: T, i: number) => U): readonly U[] | undefined;
374374
/** @internal */
375-
export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined {
375+
export function sameMap<T, U = T>(array: readonly T[] | undefined, f: (x: T, i: number) => U): readonly U[] | undefined {
376376
if (array) {
377377
for (let i = 0; i < array.length; i++) {
378378
const item = array[i];
379379
const mapped = f(item, i);
380-
if (item !== mapped) {
381-
const result = array.slice(0, i);
380+
if (item as unknown !== mapped) {
381+
const result: U[] = array.slice(0, i) as unknown[] as U[];
382382
result.push(mapped);
383383
for (i++; i < array.length; i++) {
384384
result.push(f(array[i], i));
@@ -387,7 +387,7 @@ export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number)
387387
}
388388
}
389389
}
390-
return array;
390+
return array as unknown[] as U[];
391391
}
392392

393393
/**

src/compiler/moduleNameResolver.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1758,8 +1758,6 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
17581758
const diagnosticState = {
17591759
...state,
17601760
features: state.features & ~NodeResolutionFeatures.Exports,
1761-
failedLookupLocations: [],
1762-
affectingLocations: [],
17631761
reportDiagnostic: noop,
17641762
};
17651763
const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState);

src/compiler/program.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ import {
153153
getTsBuildInfoEmitOutputFilePath,
154154
getTsConfigObjectLiteralExpression,
155155
getTsConfigPropArrayElementValue,
156+
getTypesPackageName,
156157
HasChangedAutomaticTypeDirectiveNames,
157158
hasChangesInResolutions,
158159
hasExtension,
@@ -1505,6 +1506,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
15051506
let resolvedLibReferences: Map<string, LibResolution> | undefined;
15061507
let resolvedLibProcessing: Map<string, LibResolution> | undefined;
15071508

1509+
let packageMap: Map<string, boolean> | undefined;
1510+
1511+
15081512
// The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules.
15091513
// This works as imported modules are discovered recursively in a depth first manner, specifically:
15101514
// - For each root file, findSourceFile is called.
@@ -1862,6 +1866,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
18621866
redirectTargetsMap,
18631867
usesUriStyleNodeCoreModules,
18641868
resolvedLibReferences,
1869+
getCurrentPackagesMap: () => packageMap,
1870+
typesPackageExists,
1871+
packageBundlesTypes,
18651872
isEmittedFile,
18661873
getConfigFileParsingDiagnostics,
18671874
getProjectReferences,
@@ -1908,6 +1915,30 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
19081915

19091916
return program;
19101917

1918+
function getPackagesMap() {
1919+
if (packageMap) return packageMap;
1920+
packageMap = new Map();
1921+
// A package name maps to true when we detect it has .d.ts files.
1922+
// This is useful as an approximation of whether a package bundles its own types.
1923+
// Note: we only look at files already found by module resolution,
1924+
// so there may be files we did not consider.
1925+
files.forEach(sf => {
1926+
if (!sf.resolvedModules) return;
1927+
1928+
sf.resolvedModules.forEach(({ resolvedModule }) => {
1929+
if (resolvedModule?.packageId) packageMap!.set(resolvedModule.packageId.name, resolvedModule.extension === Extension.Dts || !!packageMap!.get(resolvedModule.packageId.name));
1930+
});
1931+
});
1932+
return packageMap;
1933+
}
1934+
1935+
function typesPackageExists(packageName: string): boolean {
1936+
return getPackagesMap().has(getTypesPackageName(packageName));
1937+
}
1938+
function packageBundlesTypes(packageName: string): boolean {
1939+
return !!getPackagesMap().get(packageName);
1940+
}
1941+
19111942
function addResolutionDiagnostics(resolution: ResolvedModuleWithFailedLookupLocations | ResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
19121943
if (!resolution.resolutionDiagnostics?.length) return;
19131944
(fileProcessingDiagnostics ??= []).push({
@@ -2536,6 +2567,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
25362567
redirectTargetsMap = oldProgram.redirectTargetsMap;
25372568
usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules;
25382569
resolvedLibReferences = oldProgram.resolvedLibReferences;
2570+
packageMap = oldProgram.getCurrentPackagesMap();
25392571

25402572
return StructureIsReused.Completely;
25412573
}

0 commit comments

Comments
 (0)