Skip to content

Handle module node found error reporting in incremental and watch scneario #54115

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 91 additions & 4 deletions src/compiler/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
convertToOptionsWithAbsolutePaths,
createBuildInfo,
createGetCanonicalFileName,
createModuleNotFoundChain,
createProgram,
CustomTransformers,
Debug,
Expand Down Expand Up @@ -68,8 +69,11 @@ import {
ProjectReference,
ReadBuildProgramHost,
ReadonlyCollection,
RepopulateDiagnosticChainInfo,
RepopulateModuleNotFoundDiagnosticChain,
returnFalse,
returnUndefined,
sameMap,
SemanticDiagnosticsBuilderProgram,
skipTypeChecking,
some,
Expand Down Expand Up @@ -103,7 +107,18 @@ export interface ReusableDiagnosticRelatedInformation {
}

/** @internal */
export type ReusableDiagnosticMessageChain = DiagnosticMessageChain;
export interface ReusableRepopulateModuleNotFoundChain {
info: RepopulateModuleNotFoundDiagnosticChain;
next?: ReusableDiagnosticMessageChain[];
}

/** @internal */
export type SerializedDiagnosticMessageChain = Omit<DiagnosticMessageChain, "next" | "repopulateInfo"> & {
next?: ReusableDiagnosticMessageChain[];
};

/** @internal */
export type ReusableDiagnosticMessageChain = SerializedDiagnosticMessageChain | ReusableRepopulateModuleNotFoundChain;

/**
* 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
Expand Down Expand Up @@ -364,7 +379,12 @@ function createBuilderProgramState(newProgram: Program, oldState: Readonly<Reusa
// Unchanged file copy diagnostics
const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
if (diagnostics) {
state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram) : diagnostics as readonly Diagnostic[]);
state.semanticDiagnosticsPerFile!.set(
sourceFilePath,
oldState!.hasReusableDiagnostic ?
convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram) :
repopulateDiagnostics(diagnostics as readonly Diagnostic[], newProgram)
);
if (!state.semanticDiagnosticsFromOldState) {
state.semanticDiagnosticsFromOldState = new Set();
}
Expand Down Expand Up @@ -448,6 +468,43 @@ function getEmitSignatureFromOldSignature(options: CompilerOptions, oldOptions:
isString(oldEmitSignature) ? [oldEmitSignature] : oldEmitSignature[0];
}

function repopulateDiagnostics(diagnostics: readonly Diagnostic[], newProgram: Program): readonly Diagnostic[] {
if (!diagnostics.length) return diagnostics;
return sameMap(diagnostics, diag => {
if (isString(diag.messageText)) return diag;
const repopulatedChain = convertOrRepopulateDiagnosticMessageChain(diag.messageText, diag.file, newProgram, chain => chain.repopulateInfo?.());
return repopulatedChain === diag.messageText ?
diag :
{ ...diag, messageText: repopulatedChain };
});
}

function convertOrRepopulateDiagnosticMessageChain<T extends DiagnosticMessageChain | ReusableDiagnosticMessageChain>(
chain: T,
sourceFile: SourceFile | undefined,
newProgram: Program,
repopulateInfo: (chain: T) => RepopulateDiagnosticChainInfo | undefined,
): DiagnosticMessageChain {
const info = repopulateInfo(chain);
if (info) {
return {
...createModuleNotFoundChain(sourceFile!, newProgram, info.moduleReference, info.mode, info.packageName || info.moduleReference),
next: convertOrRepopulateDiagnosticMessageChainArray(chain.next as T[], sourceFile, newProgram, repopulateInfo),
};
}
const next = convertOrRepopulateDiagnosticMessageChainArray(chain.next as T[], sourceFile, newProgram, repopulateInfo);
return next === chain.next ? chain as DiagnosticMessageChain : { ...chain as DiagnosticMessageChain, next };
}

function convertOrRepopulateDiagnosticMessageChainArray<T extends DiagnosticMessageChain | ReusableDiagnosticMessageChain>(
array: T[] | undefined,
sourceFile: SourceFile | undefined,
newProgram: Program,
repopulateInfo: (chain: T) => RepopulateDiagnosticChainInfo | undefined,
): DiagnosticMessageChain[] | undefined {
return sameMap(array, chain => convertOrRepopulateDiagnosticMessageChain(chain, sourceFile, newProgram, repopulateInfo));
}

function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program): readonly Diagnostic[] {
if (!diagnostics.length) return emptyArray;
let buildInfoDirectory: string | undefined;
Expand All @@ -474,9 +531,13 @@ function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newPro

function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation {
const { file } = diagnostic;
const sourceFile = file ? newProgram.getSourceFileByPath(toPath(file)) : undefined;
return {
...diagnostic,
file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined
file: sourceFile,
messageText: isString(diagnostic.messageText) ?
diagnostic.messageText :
convertOrRepopulateDiagnosticMessageChain(diagnostic.messageText, sourceFile, newProgram, chain => (chain as ReusableRepopulateModuleNotFoundChain).info),
};
}

Expand Down Expand Up @@ -1232,10 +1293,36 @@ function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRel
const { file } = diagnostic;
return {
...diagnostic,
file: file ? relativeToBuildInfo(file.resolvedPath) : undefined
file: file ? relativeToBuildInfo(file.resolvedPath) : undefined,
messageText: isString(diagnostic.messageText) ? diagnostic.messageText : convertToReusableDiagnosticMessageChain(diagnostic.messageText),
};
}

function convertToReusableDiagnosticMessageChain(chain: DiagnosticMessageChain): ReusableDiagnosticMessageChain {
if (chain.repopulateInfo) {
return {
info: chain.repopulateInfo(),
next: convertToReusableDiagnosticMessageChainArray(chain.next),
};
}
const next = convertToReusableDiagnosticMessageChainArray(chain.next);
return next === chain.next ? chain : { ...chain, next };
}

function convertToReusableDiagnosticMessageChainArray(array: DiagnosticMessageChain[] | undefined): ReusableDiagnosticMessageChain[] | undefined {
if (!array) return array;
return forEach(array, (chain, index) => {
const reusable = convertToReusableDiagnosticMessageChain(chain);
if (chain === reusable) return undefined;
const result: ReusableDiagnosticMessageChain[] = index > 0 ? array.slice(0, index - 1) : [];
result.push(reusable);
for (let i = index + 1; i < array.length; i++) {
result.push(convertToReusableDiagnosticMessageChain(array[i]));
}
return result;
}) || array;
}

/** @internal */
export enum BuilderProgramKind {
SemanticDiagnosticsBuilderProgram,
Expand Down
53 changes: 3 additions & 50 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ import {
createGetCanonicalFileName,
createGetSymbolWalker,
createModeAwareCacheKey,
createModuleNotFoundChain,
createMultiMap,
createPrinterWithDefaults,
createPrinterWithRemoveComments,
Expand Down Expand Up @@ -359,7 +360,6 @@ import {
getThisParameter,
getTrailingSemicolonDeferringWriter,
getTypeParameterFromJsDoc,
getTypesPackageName,
getUseDefineForClassFields,
group,
hasAbstractModifier,
Expand Down Expand Up @@ -805,7 +805,6 @@ import {
LiteralExpression,
LiteralType,
LiteralTypeNode,
mangleScopedPackageName,
map,
mapDefined,
MappedSymbol,
Expand All @@ -814,7 +813,6 @@ import {
MatchingKeys,
maybeBind,
MemberOverrideStatus,
memoize,
MetaProperty,
MethodDeclaration,
MethodSignature,
Expand Down Expand Up @@ -852,7 +850,6 @@ import {
nodeIsPresent,
nodeIsSynthesized,
NodeLinks,
nodeModulesPathPart,
nodeStartsNewLexicalEnvironment,
NodeWithTypeArguments,
NonNullChain,
Expand Down Expand Up @@ -1391,22 +1388,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Why var? It avoids TDZ checks in the runtime which can be costly.
// See: https://github.com/microsoft/TypeScript/issues/52924
/* eslint-disable no-var */
var getPackagesMap = memoize(() => {
// A package name maps to true when we detect it has .d.ts files.
// This is useful as an approximation of whether a package bundles its own types.
// Note: we only look at files already found by module resolution,
// so there may be files we did not consider.
var map = new Map<string, boolean>();
host.getSourceFiles().forEach(sf => {
if (!sf.resolvedModules) return;

sf.resolvedModules.forEach(({ resolvedModule }) => {
if (resolvedModule?.packageId) map.set(resolvedModule.packageId.name, resolvedModule.extension === Extension.Dts || !!map.get(resolvedModule.packageId.name));
});
});
return map;
});

var deferredDiagnosticsCallbacks: (() => void)[] = [];

var addLazyDiagnostic = (arg: () => void) => {
Expand Down Expand Up @@ -5066,44 +5047,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}

function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
let errorInfo;
let errorInfo: DiagnosticMessageChain | undefined;
if (!isExternalModuleNameRelative(moduleReference) && packageId) {
const node10Result = sourceFile.resolvedModules?.get(moduleReference, mode)?.node10Result;
errorInfo = node10Result
? chainDiagnosticMessages(
/*details*/ undefined,
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,
node10Result,
node10Result.indexOf(nodeModulesPathPart + "@types/") > -1 ? `@types/${mangleScopedPackageName(packageId.name)}` : packageId.name)
: typesPackageExists(packageId.name)
? chainDiagnosticMessages(
/*details*/ undefined,
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,
packageId.name, mangleScopedPackageName(packageId.name))
: packageBundlesTypes(packageId.name)
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
packageId.name,
moduleReference)
: chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
moduleReference,
mangleScopedPackageName(packageId.name));
errorInfo = createModuleNotFoundChain(sourceFile, host, moduleReference, mode, packageId.name);
}
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
errorInfo,
Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type,
moduleReference,
resolvedFileName));
}
function typesPackageExists(packageName: string): boolean {
return getPackagesMap().has(getTypesPackageName(packageName));
}
function packageBundlesTypes(packageName: string): boolean {
return !!getPackagesMap().get(packageName);
}

function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol;
function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined;
Expand Down
16 changes: 8 additions & 8 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,21 +364,21 @@ export function *mapIterator<T, U>(iter: Iterable<T>, mapFn: (x: T) => U) {
* Maps from T to T and avoids allocation if all elements map to themselves
*
* @internal */
export function sameMap<T>(array: T[], f: (x: T, i: number) => T): T[];
export function sameMap<T, U = T>(array: T[], f: (x: T, i: number) => U): U[];
/** @internal */
export function sameMap<T>(array: readonly T[], f: (x: T, i: number) => T): readonly T[];
export function sameMap<T, U = T>(array: readonly T[], f: (x: T, i: number) => U): readonly U[];
/** @internal */
export function sameMap<T>(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined;
export function sameMap<T, U = T>(array: T[] | undefined, f: (x: T, i: number) => U): U[] | undefined;
/** @internal */
export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined;
export function sameMap<T, U = T>(array: readonly T[] | undefined, f: (x: T, i: number) => U): readonly U[] | undefined;
/** @internal */
export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined {
export function sameMap<T, U = T>(array: readonly T[] | undefined, f: (x: T, i: number) => U): readonly U[] | undefined {
if (array) {
for (let i = 0; i < array.length; i++) {
const item = array[i];
const mapped = f(item, i);
if (item !== mapped) {
const result = array.slice(0, i);
if (item as unknown !== mapped) {
const result: U[] = array.slice(0, i) as unknown[] as U[];
result.push(mapped);
for (i++; i < array.length; i++) {
result.push(f(array[i], i));
Expand All @@ -387,7 +387,7 @@ export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number)
}
}
}
return array;
return array as unknown[] as U[];
}

/**
Expand Down
2 changes: 0 additions & 2 deletions src/compiler/moduleNameResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1758,8 +1758,6 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
const diagnosticState = {
...state,
features: state.features & ~NodeResolutionFeatures.Exports,
failedLookupLocations: [],
affectingLocations: [],
reportDiagnostic: noop,
};
const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState);
Expand Down
32 changes: 32 additions & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ import {
getTsBuildInfoEmitOutputFilePath,
getTsConfigObjectLiteralExpression,
getTsConfigPropArrayElementValue,
getTypesPackageName,
HasChangedAutomaticTypeDirectiveNames,
hasChangesInResolutions,
hasExtension,
Expand Down Expand Up @@ -1505,6 +1506,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
let resolvedLibReferences: Map<string, LibResolution> | undefined;
let resolvedLibProcessing: Map<string, LibResolution> | undefined;

let packageMap: Map<string, boolean> | undefined;


// The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules.
// This works as imported modules are discovered recursively in a depth first manner, specifically:
// - For each root file, findSourceFile is called.
Expand Down Expand Up @@ -1862,6 +1866,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
redirectTargetsMap,
usesUriStyleNodeCoreModules,
resolvedLibReferences,
getCurrentPackagesMap: () => packageMap,
typesPackageExists,
packageBundlesTypes,
isEmittedFile,
getConfigFileParsingDiagnostics,
getProjectReferences,
Expand Down Expand Up @@ -1908,6 +1915,30 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg

return program;

function getPackagesMap() {
if (packageMap) return packageMap;
packageMap = new Map();
// A package name maps to true when we detect it has .d.ts files.
// This is useful as an approximation of whether a package bundles its own types.
// Note: we only look at files already found by module resolution,
// so there may be files we did not consider.
files.forEach(sf => {
if (!sf.resolvedModules) return;

sf.resolvedModules.forEach(({ resolvedModule }) => {
if (resolvedModule?.packageId) packageMap!.set(resolvedModule.packageId.name, resolvedModule.extension === Extension.Dts || !!packageMap!.get(resolvedModule.packageId.name));
});
});
return packageMap;
}

function typesPackageExists(packageName: string): boolean {
return getPackagesMap().has(getTypesPackageName(packageName));
}
function packageBundlesTypes(packageName: string): boolean {
return !!getPackagesMap().get(packageName);
}

function addResolutionDiagnostics(resolution: ResolvedModuleWithFailedLookupLocations | ResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
if (!resolution.resolutionDiagnostics?.length) return;
(fileProcessingDiagnostics ??= []).push({
Expand Down Expand Up @@ -2536,6 +2567,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
redirectTargetsMap = oldProgram.redirectTargetsMap;
usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules;
resolvedLibReferences = oldProgram.resolvedLibReferences;
packageMap = oldProgram.getCurrentPackagesMap();

return StructureIsReused.Completely;
}
Expand Down
Loading