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

This commit is contained in:
Sheetal Nandi
2023-05-09 13:42:26 -07:00
committed by GitHub
parent a4009a335a
commit 8b825f7aaa
16 changed files with 10019 additions and 116 deletions
+91 -4
View File
@@ -22,6 +22,7 @@ import {
convertToOptionsWithAbsolutePaths,
createBuildInfo,
createGetCanonicalFileName,
createModuleNotFoundChain,
createProgram,
CustomTransformers,
Debug,
@@ -68,8 +69,11 @@ import {
ProjectReference,
ReadBuildProgramHost,
ReadonlyCollection,
RepopulateDiagnosticChainInfo,
RepopulateModuleNotFoundDiagnosticChain,
returnFalse,
returnUndefined,
sameMap,
SemanticDiagnosticsBuilderProgram,
skipTypeChecking,
some,
@@ -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
@@ -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();
}
@@ -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;
@@ -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),
};
}
@@ -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,
+3 -50
View File
@@ -110,6 +110,7 @@ import {
createGetCanonicalFileName,
createGetSymbolWalker,
createModeAwareCacheKey,
createModuleNotFoundChain,
createMultiMap,
createPrinterWithDefaults,
createPrinterWithRemoveComments,
@@ -359,7 +360,6 @@ import {
getThisParameter,
getTrailingSemicolonDeferringWriter,
getTypeParameterFromJsDoc,
getTypesPackageName,
getUseDefineForClassFields,
group,
hasAbstractModifier,
@@ -806,7 +806,6 @@ import {
LiteralExpression,
LiteralType,
LiteralTypeNode,
mangleScopedPackageName,
map,
mapDefined,
MappedSymbol,
@@ -815,7 +814,6 @@ import {
MatchingKeys,
maybeBind,
MemberOverrideStatus,
memoize,
MetaProperty,
MethodDeclaration,
MethodSignature,
@@ -853,7 +851,6 @@ import {
nodeIsPresent,
nodeIsSynthesized,
NodeLinks,
nodeModulesPathPart,
nodeStartsNewLexicalEnvironment,
NodeWithTypeArguments,
NonNullChain,
@@ -1392,22 +1389,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) => {
@@ -5068,31 +5049,9 @@ 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,
@@ -5100,12 +5059,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
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;
+8 -8
View File
@@ -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));
@@ -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[];
}
/**
-2
View File
@@ -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);
+32
View File
@@ -153,6 +153,7 @@ import {
getTsBuildInfoEmitOutputFilePath,
getTsConfigObjectLiteralExpression,
getTsConfigPropArrayElementValue,
getTypesPackageName,
HasChangedAutomaticTypeDirectiveNames,
hasChangesInResolutions,
hasExtension,
@@ -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.
@@ -1862,6 +1866,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
redirectTargetsMap,
usesUriStyleNodeCoreModules,
resolvedLibReferences,
getCurrentPackagesMap: () => packageMap,
typesPackageExists,
packageBundlesTypes,
isEmittedFile,
getConfigFileParsingDiagnostics,
getProjectReferences,
@@ -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({
@@ -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;
}
+64 -51
View File
@@ -153,6 +153,7 @@ export interface ResolutionWithFailedLookupLocations {
refCount?: number;
// Files that have this resolution using
files?: Set<Path>;
node10Result?: string;
}
interface ResolutionWithResolvedFileName {
@@ -956,43 +957,48 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
(resolution.files ??= new Set()).add(filePath);
}
function watchFailedLookupLocation(failedLookupLocation: string, setAtRoot: boolean) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const toWatch = getDirectoryToWatchFailedLookupLocation(
failedLookupLocation,
failedLookupLocationPath,
rootDir,
rootPath,
rootPathComponents,
getCurrentDirectory,
);
if (toWatch) {
const { dir, dirPath, nonRecursive } = toWatch;
if (dirPath === rootPath) {
Debug.assert(nonRecursive);
setAtRoot = true;
}
else {
setDirectoryWatcher(dir, dirPath, nonRecursive);
}
}
return setAtRoot;
}
function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
Debug.assert(!!resolution.refCount);
const { failedLookupLocations, affectingLocations } = resolution;
if (!failedLookupLocations?.length && !affectingLocations?.length) return;
if (failedLookupLocations?.length) resolutionsWithFailedLookups.add(resolution);
const { failedLookupLocations, affectingLocations, node10Result } = resolution;
if (!failedLookupLocations?.length && !affectingLocations?.length && !node10Result) return;
if (failedLookupLocations?.length || node10Result) resolutionsWithFailedLookups.add(resolution);
let setAtRoot = false;
if (failedLookupLocations) {
for (const failedLookupLocation of failedLookupLocations) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const toWatch = getDirectoryToWatchFailedLookupLocation(
failedLookupLocation,
failedLookupLocationPath,
rootDir,
rootPath,
rootPathComponents,
getCurrentDirectory,
);
if (toWatch) {
const { dir, dirPath, nonRecursive } = toWatch;
if (dirPath === rootPath) {
Debug.assert(nonRecursive);
setAtRoot = true;
}
else {
setDirectoryWatcher(dir, dirPath, nonRecursive);
}
}
}
if (setAtRoot) {
// This is always non recursive
setDirectoryWatcher(rootDir, rootPath, /*nonRecursive*/ true); // TODO: GH#18217
setAtRoot = watchFailedLookupLocation(failedLookupLocation, setAtRoot);
}
}
watchAffectingLocationsOfResolution(resolution, !failedLookupLocations?.length);
if (node10Result) setAtRoot = watchFailedLookupLocation(node10Result, setAtRoot);
if (setAtRoot) {
// This is always non recursive
setDirectoryWatcher(rootDir, rootPath, /*nonRecursive*/ true);
}
watchAffectingLocationsOfResolution(resolution, !failedLookupLocations?.length && !node10Result);
}
function watchAffectingLocationsOfResolution(resolution: ResolutionWithFailedLookupLocations, addToResolutionsWithOnlyAffectingLocations: boolean) {
@@ -1080,6 +1086,28 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
}
}
function stopWatchFailedLookupLocation(failedLookupLocation: string, removeAtRoot: boolean) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const toWatch = getDirectoryToWatchFailedLookupLocation(
failedLookupLocation,
failedLookupLocationPath,
rootDir,
rootPath,
rootPathComponents,
getCurrentDirectory,
);
if (toWatch) {
const { dirPath } = toWatch;
if (dirPath === rootPath) {
removeAtRoot = true;
}
else {
removeDirectoryWatcher(dirPath);
}
}
return removeAtRoot;
}
function stopWatchFailedLookupLocationOfResolution<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
resolution: T,
filePath: Path,
@@ -1097,32 +1125,16 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
if (resolutions?.delete(resolution) && !resolutions.size) resolvedFileToResolution.delete(key);
}
const { failedLookupLocations, affectingLocations } = resolution;
const { failedLookupLocations, affectingLocations, node10Result } = resolution;
if (resolutionsWithFailedLookups.delete(resolution)) {
let removeAtRoot = false;
for (const failedLookupLocation of failedLookupLocations!) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const toWatch = getDirectoryToWatchFailedLookupLocation(
failedLookupLocation,
failedLookupLocationPath,
rootDir,
rootPath,
rootPathComponents,
getCurrentDirectory,
);
if (toWatch) {
const { dirPath } = toWatch;
if (dirPath === rootPath) {
removeAtRoot = true;
}
else {
removeDirectoryWatcher(dirPath);
}
if (failedLookupLocations) {
for (const failedLookupLocation of failedLookupLocations) {
removeAtRoot = stopWatchFailedLookupLocation(failedLookupLocation, removeAtRoot);
}
}
if (removeAtRoot) {
removeDirectoryWatcher(rootPath);
}
if (node10Result) removeAtRoot = stopWatchFailedLookupLocation(node10Result, removeAtRoot);
if (removeAtRoot) removeDirectoryWatcher(rootPath);
}
else if (affectingLocations?.length) {
resolutionsWithOnlyAffectingLocations.delete(resolution);
@@ -1313,7 +1325,8 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) {
if (canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution)) return true;
if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) return false;
return resolution.failedLookupLocations?.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location)));
return resolution.failedLookupLocations?.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location))) ||
(!!resolution.node10Result && isInvalidatedFailedLookup(resolutionHost.toPath(resolution.node10Result)));
}
function isInvalidatedFailedLookup(locationPath: Path) {
+16
View File
@@ -4815,6 +4815,7 @@ export interface Program extends ScriptReferenceHost {
* @internal
*/
resolvedLibReferences: Map<string, LibResolution> | undefined;
/** @internal */ getCurrentPackagesMap(): Map<string, boolean> | undefined;
/**
* Is the file emitted file
*
@@ -4950,6 +4951,9 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost {
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
readonly redirectTargetsMap: RedirectTargetsMap;
typesPackageExists(packageName: string): boolean;
packageBundlesTypes(packageName: string): boolean;
}
export interface TypeChecker {
@@ -6923,6 +6927,16 @@ export interface DiagnosticMessage {
elidedInCompatabilityPyramid?: boolean;
}
/** @internal */
export interface RepopulateModuleNotFoundDiagnosticChain {
moduleReference: string;
mode: ResolutionMode;
packageName: string | undefined;
}
/** @internal */
export type RepopulateDiagnosticChainInfo = RepopulateModuleNotFoundDiagnosticChain;
/**
* A linked list of formatted diagnostic messages to be used as part of a multiline message.
* It is built from the bottom up, leaving the head to be the "main" diagnostic.
@@ -6934,6 +6948,8 @@ export interface DiagnosticMessageChain {
category: DiagnosticCategory;
code: number;
next?: DiagnosticMessageChain[];
/** @internal */
repopulateInfo?: () => RepopulateDiagnosticChainInfo;
}
export interface Diagnostic extends DiagnosticRelatedInformation {
+33 -1
View File
@@ -379,6 +379,7 @@ import {
LiteralLikeNode,
LogicalOperator,
LogicalOrCoalescingAssignmentOperator,
mangleScopedPackageName,
map,
mapDefined,
MapLike,
@@ -526,6 +527,7 @@ import {
TypeAliasDeclaration,
TypeAssertion,
TypeChecker,
TypeCheckerHost,
TypeElement,
TypeFlags,
TypeLiteralNode,
@@ -781,7 +783,37 @@ export function moduleResolutionIsEqualTo(oldResolution: ResolvedModuleWithFaile
oldResolution.resolvedModule.extension === newResolution.resolvedModule.extension &&
oldResolution.resolvedModule.resolvedFileName === newResolution.resolvedModule.resolvedFileName &&
oldResolution.resolvedModule.originalPath === newResolution.resolvedModule.originalPath &&
packageIdIsEqual(oldResolution.resolvedModule.packageId, newResolution.resolvedModule.packageId);
packageIdIsEqual(oldResolution.resolvedModule.packageId, newResolution.resolvedModule.packageId) &&
oldResolution.node10Result === newResolution.node10Result;
}
/** @internal */
export function createModuleNotFoundChain(sourceFile: SourceFile, host: TypeCheckerHost, moduleReference: string, mode: ResolutionMode, packageName: string) {
const node10Result = sourceFile.resolvedModules?.get(moduleReference, mode)?.node10Result;
const result = 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(packageName)}` : packageName)
: host.typesPackageExists(packageName)
? 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,
packageName, mangleScopedPackageName(packageName))
: host.packageBundlesTypes(packageName)
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
packageName,
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(packageName));
if (result) result.repopulateInfo = () => ({ moduleReference, mode, packageName: packageName === moduleReference ? undefined : packageName });
return result;
}
function packageIdIsEqual(a: PackageId | undefined, b: PackageId | undefined): boolean {
+1
View File
@@ -110,6 +110,7 @@ import "./unittests/tsc/forceConsistentCasingInFileNames";
import "./unittests/tsc/incremental";
import "./unittests/tsc/libraryResolution";
import "./unittests/tsc/listFilesOnly";
import "./unittests/tsc/moduleResolution";
import "./unittests/tsc/projectReferences";
import "./unittests/tsc/projectReferencesConfig";
import "./unittests/tsc/redirect";
@@ -0,0 +1,85 @@
import { dedent } from "../../_namespaces/Utils";
import { FsContents } from "./contents";
import { libFile } from "./virtualFileSystemWithWatch";
export function getFsConentsForNode10ResultAtTypesPackageJson(packageName: string, addTypesCondition: boolean) {
return JSON.stringify({
name: `@types/${packageName}`,
version: "1.0.0",
types: "index.d.ts",
exports: {
".": {
...(addTypesCondition ? { types: "./index.d.ts" } : {}),
require: "./index.d.ts"
}
}
}, undefined, " ");
}
export function getFsContentsForNode10ResultPackageJson(packageName: string, addTypes: boolean, addTypesCondition: boolean) {
return JSON.stringify({
name: packageName,
version: "1.0.0",
main: "index.js",
...(addTypes ? { types: "index.d.ts" } : {}),
exports: {
".": {
...(addTypesCondition ? { types: "./index.d.ts" } : {}),
import: "./index.mjs",
require: "./index.js"
}
}
}, undefined, " ");
}
export function getFsContentsForNode10ResultDts(packageName: string) {
return `export declare const ${packageName}: number;`;
}
function js(packageName: string) {
return `module.exports = { ${packageName}: 1 };`;
}
function mjs(packageName: string) {
return `export const ${packageName} = 1;`;
}
export function getFsContentsForNode10Result(): FsContents {
return {
"/home/src/projects/project/node_modules/@types/bar/package.json": getFsConentsForNode10ResultAtTypesPackageJson("bar", /*addTypesCondition*/ false),
"/home/src/projects/project/node_modules/@types/bar/index.d.ts": getFsContentsForNode10ResultDts("bar"),
"/home/src/projects/project/node_modules/bar/package.json": getFsContentsForNode10ResultPackageJson("bar", /*addTypes*/ false, /*addTypesCondition*/ false),
"/home/src/projects/project/node_modules/bar/index.js": js("bar"),
"/home/src/projects/project/node_modules/bar/index.mjs": mjs("bar"),
"/home/src/projects/project/node_modules/foo/package.json": getFsContentsForNode10ResultPackageJson("foo", /*addTypes*/ true, /*addTypesCondition*/ false),
"/home/src/projects/project/node_modules/foo/index.js": js("foo"),
"/home/src/projects/project/node_modules/foo/index.mjs": mjs("foo"),
"/home/src/projects/project/node_modules/foo/index.d.ts": getFsContentsForNode10ResultDts("foo"),
"/home/src/projects/project/node_modules/@types/bar2/package.json": getFsConentsForNode10ResultAtTypesPackageJson("bar2", /*addTypesCondition*/ true),
"/home/src/projects/project/node_modules/@types/bar2/index.d.ts": getFsContentsForNode10ResultDts("bar2"),
"/home/src/projects/project/node_modules/bar2/package.json": getFsContentsForNode10ResultPackageJson("bar2", /*addTypes*/ false, /*addTypesCondition*/ false),
"/home/src/projects/project/node_modules/bar2/index.js": js("bar2"),
"/home/src/projects/project/node_modules/bar2/index.mjs": mjs("bar2"),
"/home/src/projects/project/node_modules/foo2/package.json": getFsContentsForNode10ResultPackageJson("foo2", /*addTypes*/ true, /*addTypesCondition*/ true),
"/home/src/projects/project/node_modules/foo2/index.js": js("foo2"),
"/home/src/projects/project/node_modules/foo2/index.mjs": mjs("foo2"),
"/home/src/projects/project/node_modules/foo2/index.d.ts": getFsContentsForNode10ResultDts("foo2"),
"/home/src/projects/project/index.mts": dedent`
import { foo } from "foo";
import { bar } from "bar";
import { foo2 } from "foo2";
import { bar2 } from "bar2";
`,
"/home/src/projects/project/tsconfig.json": JSON.stringify({
compilerOptions: {
moduleResolution: "node16",
traceResolution: true,
incremental: true,
strict: true,
types: [],
},
files: ["index.mts"]
}),
[libFile.path]: libFile.content,
};
}
@@ -0,0 +1,63 @@
import { getFsConentsForNode10ResultAtTypesPackageJson, getFsContentsForNode10Result, getFsContentsForNode10ResultDts, getFsContentsForNode10ResultPackageJson } from "../helpers/node10Result";
import { verifyTsc } from "../helpers/tsc";
import { loadProjectFromFiles } from "../helpers/vfs";
describe("unittests:: tsc:: moduleResolution::", () => {
verifyTsc({
scenario: "moduleResolution",
subScenario: "node10Result",
fs: () => loadProjectFromFiles(getFsContentsForNode10Result()),
commandLineArgs: ["-p", "/home/src/projects/project"],
baselinePrograms: true,
edits: [
{
caption: "delete the node10Result in @types",
edit: fs => fs.unlinkSync("/home/src/projects/project/node_modules/@types/bar/index.d.ts"),
},
{
caption: "delete the ndoe10Result in package/types",
edit: fs => fs.unlinkSync("/home/src/projects/project/node_modules/foo/index.d.ts"),
},
{
caption: "add the node10Result in @types",
edit: fs => fs.writeFileSync("/home/src/projects/project/node_modules/@types/bar/index.d.ts", getFsContentsForNode10ResultDts("bar")),
},
{
caption: "add the ndoe10Result in package/types",
edit: fs => fs.writeFileSync("/home/src/projects/project/node_modules/foo/index.d.ts", getFsContentsForNode10ResultDts("foo")),
},
{
caption: "update package.json from @types so error is fixed",
edit: fs => fs.writeFileSync("/home/src/projects/project/node_modules/@types/bar/package.json", getFsConentsForNode10ResultAtTypesPackageJson("bar", /*addTypesCondition*/ true)),
},
{
caption: "update package.json so error is fixed",
edit: fs => fs.writeFileSync("/home/src/projects/project/node_modules/foo/package.json", getFsContentsForNode10ResultPackageJson("foo", /*addTypes*/ true, /*addTypesCondition*/ true)),
},
{
caption: "update package.json from @types so error is introduced",
edit: fs => fs.writeFileSync("/home/src/projects/project/node_modules/@types/bar2/package.json", getFsConentsForNode10ResultAtTypesPackageJson("bar2", /*addTypesCondition*/ false)),
},
{
caption: "update package.json so error is introduced",
edit: fs => fs.writeFileSync("/home/src/projects/project/node_modules/foo2/package.json", getFsContentsForNode10ResultPackageJson("foo2", /*addTypes*/ true, /*addTypesCondition*/ false)),
},
{
caption: "delete the node10Result in @types",
edit: fs => fs.unlinkSync("/home/src/projects/project/node_modules/@types/bar2/index.d.ts"),
},
{
caption: "delete the ndoe10Result in package/types",
edit: fs => fs.unlinkSync("/home/src/projects/project/node_modules/foo2/index.d.ts"),
},
{
caption: "add the node10Result in @types",
edit: fs => fs.writeFileSync("/home/src/projects/project/node_modules/@types/bar2/index.d.ts", getFsContentsForNode10ResultDts("bar2")),
},
{
caption: "add the ndoe10Result in package/types",
edit: fs => fs.writeFileSync("/home/src/projects/project/node_modules/foo2/index.d.ts", getFsContentsForNode10ResultDts("foo2")),
},
]
});
});
@@ -1,4 +1,5 @@
import * as Utils from "../../_namespaces/Utils";
import { getFsConentsForNode10ResultAtTypesPackageJson, getFsContentsForNode10Result, getFsContentsForNode10ResultDts, getFsContentsForNode10ResultPackageJson } from "../helpers/node10Result";
import { verifyTscWatch } from "../helpers/tscWatch";
import {
createWatchedSystem,
@@ -432,4 +433,109 @@ describe("unittests:: tsc-watch:: moduleResolution", () => {
}
]
});
verifyTscWatch({
scenario: "moduleResolution",
subScenario: "node10Result",
sys: () => createWatchedSystem(getFsContentsForNode10Result(), { currentDirectory: "/home/src/projects/project" }),
commandLineArgs: ["-w", "--extendedDiagnostics"],
edits: [
{
caption: "delete the node10Result in @types",
edit: sys => sys.deleteFile("/home/src/projects/project/node_modules/@types/bar/index.d.ts"),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "delete the ndoe10Result in package/types",
edit: sys => sys.deleteFile("/home/src/projects/project/node_modules/foo/index.d.ts"),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "add the node10Result in @types",
edit: sys => sys.writeFile("/home/src/projects/project/node_modules/@types/bar/index.d.ts", getFsContentsForNode10ResultDts("bar")),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "add the ndoe10Result in package/types",
edit: sys => sys.writeFile("/home/src/projects/project/node_modules/foo/index.d.ts", getFsContentsForNode10ResultDts("foo")),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "update package.json from @types so error is fixed",
edit: sys => sys.writeFile("/home/src/projects/project/node_modules/@types/bar/package.json", getFsConentsForNode10ResultAtTypesPackageJson("bar", /*addTypesCondition*/ true)),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "update package.json so error is fixed",
edit: sys => sys.writeFile("/home/src/projects/project/node_modules/foo/package.json", getFsContentsForNode10ResultPackageJson("foo", /*addTypes*/ true, /*addTypesCondition*/ true)),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "update package.json from @types so error is introduced",
edit: sys => sys.writeFile("/home/src/projects/project/node_modules/@types/bar2/package.json", getFsConentsForNode10ResultAtTypesPackageJson("bar2", /*addTypesCondition*/ false)),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "update package.json so error is introduced",
edit: sys => sys.writeFile("/home/src/projects/project/node_modules/foo2/package.json", getFsContentsForNode10ResultPackageJson("foo2", /*addTypes*/ true, /*addTypesCondition*/ false)),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "delete the node10Result in @types",
edit: sys => sys.deleteFile("/home/src/projects/project/node_modules/@types/bar2/index.d.ts"),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "delete the ndoe10Result in package/types",
edit: sys => sys.deleteFile("/home/src/projects/project/node_modules/foo2/index.d.ts"),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "add the node10Result in @types",
edit: sys => sys.writeFile("/home/src/projects/project/node_modules/@types/bar2/index.d.ts", getFsContentsForNode10ResultDts("bar2")),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
{
caption: "add the ndoe10Result in package/types",
edit: sys => sys.writeFile("/home/src/projects/project/node_modules/foo2/index.d.ts", getFsContentsForNode10ResultDts("foo2")),
timeouts: sys => {
sys.runQueuedTimeoutCallbacks();
sys.runQueuedTimeoutCallbacks();
},
},
]
});
});
@@ -1,4 +1,5 @@
import * as Utils from "../../_namespaces/Utils";
import { getFsConentsForNode10ResultAtTypesPackageJson, getFsContentsForNode10Result, getFsContentsForNode10ResultDts, getFsContentsForNode10ResultPackageJson } from "../helpers/node10Result";
import {
baselineTsserverLogs,
createLoggerWithInMemoryLogs,
@@ -130,4 +131,49 @@ describe("unittests:: tsserver:: moduleResolution", () => {
baselineTsserverLogs("moduleResolution", "package json file is edited when package json with type module exists", session);
});
});
it("node10Result", () => {
const host = createServerHost(getFsContentsForNode10Result());
const session = createSession(host, { canUseEvents: true, logger: createLoggerWithInMemoryLogs(host) });
openFilesForSession(["/home/src/projects/project/index.mts"], session);
verifyGetErrRequest({
files: ["/home/src/projects/project/index.mts"],
session,
});
host.deleteFile("/home/src/projects/project/node_modules/@types/bar/index.d.ts");
verifyErrors();
host.deleteFile("/home/src/projects/project/node_modules/foo/index.d.ts");
verifyErrors();
host.writeFile("/home/src/projects/project/node_modules/@types/bar/index.d.ts", getFsContentsForNode10ResultDts("bar"));
verifyErrors();
host.writeFile("/home/src/projects/project/node_modules/foo/index.d.ts", getFsContentsForNode10ResultDts("foo"));
verifyErrors();
host.writeFile("/home/src/projects/project/node_modules/@types/bar/package.json", getFsConentsForNode10ResultAtTypesPackageJson("bar", /*addTypesCondition*/ true));
verifyErrors();
host.writeFile("/home/src/projects/project/node_modules/foo/package.json", getFsContentsForNode10ResultPackageJson("foo", /*addTypes*/ true, /*addTypesCondition*/ true));
verifyErrors();
host.writeFile("/home/src/projects/project/node_modules/@types/bar2/package.json", getFsConentsForNode10ResultAtTypesPackageJson("bar2", /*addTypesCondition*/ false));
verifyErrors();
host.writeFile("/home/src/projects/project/node_modules/foo2/package.json", getFsContentsForNode10ResultPackageJson("foo2", /*addTypes*/ true, /*addTypesCondition*/ false));
verifyErrors();
host.deleteFile("/home/src/projects/project/node_modules/@types/bar2/index.d.ts");
verifyErrors();
host.deleteFile("/home/src/projects/project/node_modules/foo2/index.d.ts");
verifyErrors();
host.writeFile("/home/src/projects/project/node_modules/@types/bar2/index.d.ts", getFsContentsForNode10ResultDts("bar2"));
verifyErrors();
host.writeFile("/home/src/projects/project/node_modules/foo2/index.d.ts", getFsContentsForNode10ResultDts("foo2"));
verifyErrors();
baselineTsserverLogs("moduleResolution", "node10Result", session);
function verifyErrors() {
host.runQueuedTimeoutCallbacks();
host.runQueuedImmediateCallbacks();
verifyGetErrRequest({
files: ["/home/src/projects/project/index.mts"],
session,
});
}
});
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff