Skip verifyCompilerOptions when possible on program updates (#60754)

Co-authored-by: Daniel Rosenwasser <DanielRosenwasser@users.noreply.github.com>
This commit is contained in:
Andrew Branch 2025-01-09 17:35:20 -08:00 committed by GitHub
parent df54a3d6b4
commit cbac1ddfc7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 581 additions and 431 deletions

View File

@ -61,6 +61,7 @@ export * from "../transformer.js";
export * from "../emitter.js";
export * from "../watchUtilities.js";
export * from "../program.js";
export * from "../programDiagnostics.js";
export * from "../builderStatePublic.js";
export * from "../builderState.js";
export * from "../builder.js";

View File

@ -33,17 +33,16 @@ import {
createCommentDirectivesMap,
createCompilerDiagnostic,
createCompilerDiagnosticFromMessageChain,
createDiagnosticCollection,
createDiagnosticForNodeFromMessageChain,
createDiagnosticForNodeInSourceFile,
createDiagnosticForRange,
createFileDiagnostic,
createFileDiagnosticFromMessageChain,
createGetCanonicalFileName,
createModeAwareCache,
createModeAwareCacheKey,
createModuleResolutionCache,
createMultiMap,
createProgramDiagnostics,
CreateProgramOptions,
createSourceFile,
CreateSourceFileOptions,
@ -75,7 +74,6 @@ import {
equateStringsCaseInsensitive,
equateStringsCaseSensitive,
exclusivelyPrefixedNodeCoreModules,
explainIfFileIsRedirectAndImpliedFormat,
ExportAssignment,
ExportDeclaration,
Extension,
@ -86,10 +84,7 @@ import {
fileExtensionIsOneOf,
FileIncludeKind,
FileIncludeReason,
fileIncludeReasonToDiagnostics,
FilePreprocessingDiagnostics,
FilePreprocessingDiagnosticsKind,
FilePreprocessingLibReferenceDiagnostic,
FileReference,
filter,
find,
@ -104,6 +99,8 @@ import {
forEachEmittedFile,
forEachEntry,
forEachKey,
forEachOptionsSyntaxByName,
forEachProjectReference,
forEachPropertyAssignment,
forEachResolvedProjectReference as ts_forEachResolvedProjectReference,
forEachTsConfigPropArray,
@ -126,11 +123,9 @@ import {
getIsolatedModules,
getJSXImplicitImportBase,
getJSXRuntimeImport,
getLibFileNameFromLibReference,
getLineAndCharacterOfPosition,
getLineStarts,
getMatchedFileSpec,
getMatchedIncludeSpec,
getNameOfScriptTarget,
getNewLineCharacter,
getNormalizedAbsolutePath,
getNormalizedAbsolutePathWithoutRoot,
@ -139,14 +134,12 @@ import {
getPackageScopeForPath,
getPathFromPathComponents,
getPositionOfLineAndCharacter,
getPropertyArrayElementValue,
getResolvedModuleFromResolution,
getResolvedTypeReferenceDirectiveFromResolution,
getResolveJsonModule,
getRootLength,
getSetExternalModuleIndicator,
getSourceFileOfNode,
getSpellingSuggestion,
getStrictOptionValue,
getSupportedExtensions,
getSupportedExtensionsWithJsonIfResolveJsonModule,
@ -155,7 +148,6 @@ import {
getTransformers,
getTsBuildInfoEmitOutputFilePath,
getTsConfigObjectLiteralExpression,
getTsConfigPropArrayElementValue,
getTypesPackageName,
HasChangedAutomaticTypeDirectiveNames,
hasChangesInResolutions,
@ -215,7 +207,6 @@ import {
JsonSourceFile,
JsxEmit,
length,
libMap,
LibResolution,
libs,
mapDefined,
@ -246,6 +237,7 @@ import {
noTransformers,
ObjectLiteralExpression,
OperationCanceledException,
optionDeclarations,
optionsHaveChanges,
PackageId,
packageIdToPackageName,
@ -317,7 +309,6 @@ import {
trace,
tracing,
tryCast,
TsConfigSourceFile,
TypeChecker,
typeDirectiveIsEqualTo,
TypeReferenceDirectiveResolutionCache,
@ -1142,60 +1133,6 @@ export function loadWithModeAwareCache<Entry, SourceFile, ResolutionCache, Resol
return resolutions;
}
/** @internal */
export function forEachResolvedProjectReference<T>(
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
cb: (resolvedProjectReference: ResolvedProjectReference, parent: ResolvedProjectReference | undefined) => T | undefined,
): T | undefined {
return forEachProjectReference(
/*projectReferences*/ undefined,
resolvedProjectReferences,
(resolvedRef, parent) => resolvedRef && cb(resolvedRef, parent),
);
}
function forEachProjectReference<T>(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, parent: ResolvedProjectReference | undefined, index: number) => T | undefined,
cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined,
): T | undefined {
let seenResolvedRefs: Set<Path> | undefined;
return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined);
function worker(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
parent: ResolvedProjectReference | undefined,
): T | undefined {
// Visit project references first
if (cbRef) {
const result = cbRef(projectReferences, parent);
if (result) return result;
}
let skipChildren: Set<ResolvedProjectReference> | undefined;
return forEach(
resolvedProjectReferences,
(resolvedRef, index) => {
if (resolvedRef && seenResolvedRefs?.has(resolvedRef.sourceFile.path)) {
(skipChildren ??= new Set()).add(resolvedRef);
// ignore recursives
return undefined;
}
const result = cbResolvedRef(resolvedRef, parent, index);
if (result || !resolvedRef) return result;
(seenResolvedRefs ||= new Set()).add(resolvedRef.sourceFile.path);
},
) || forEach(
resolvedProjectReferences,
resolvedRef =>
resolvedRef && !skipChildren?.has(resolvedRef) ?
worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef) :
undefined,
);
}
}
/** @internal */
export const inferredTypesContainingFile = "__inferred type names__.ts";
@ -1220,15 +1157,6 @@ export function getLibraryNameFromLibFileName(libFileName: string): string {
return "@typescript/lib-" + path;
}
function getLibNameFromLibReference(libReference: FileReference) {
return toFileNameLowerCase(libReference.fileName);
}
function getLibFileNameFromLibReference(libReference: FileReference) {
const libName = getLibNameFromLibReference(libReference);
return libMap.get(libName);
}
/** @internal */
export function isReferencedFile(reason: FileIncludeReason | undefined): reason is ReferencedFile {
switch (reason?.kind) {
@ -1536,16 +1464,6 @@ const plainJSErrors = new Set<number>([
Diagnostics.This_condition_will_always_return_0_since_JavaScript_compares_objects_by_reference_not_value.code,
]);
interface LazyProgramDiagnosticExplainingFile {
file: SourceFile;
diagnostic: DiagnosticMessage;
args: DiagnosticArguments;
}
interface FileReasonToChainCache {
fileIncludeReasonDetails: DiagnosticMessageChain | undefined;
redirectInfo: DiagnosticMessageChain[] | undefined;
details?: DiagnosticMessageChain[];
}
/**
* Determine if source file needs to be re-created even if its text hasn't changed
*/
@ -1611,17 +1529,13 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
let processingOtherFiles: SourceFile[] | undefined;
let files: SourceFile[];
let symlinks: SymlinkCache | undefined;
let commonSourceDirectory: string;
let typeChecker: TypeChecker;
let classifiableNames: Set<__String>;
let fileReasons = createMultiMap<Path, FileIncludeReason>();
let filesWithReferencesProcessed: Set<Path> | undefined;
let fileReasonsToChain: Map<Path, FileReasonToChainCache> | undefined;
let reasonToRelatedInfo: Map<FileIncludeReason, DiagnosticWithLocation | false> | undefined;
let cachedBindAndCheckDiagnosticsForFile: Map<Path, readonly Diagnostic[]> | undefined;
let cachedDeclarationDiagnosticsForFile: Map<Path, readonly DiagnosticWithLocation[]> | undefined;
const programDiagnostics = createProgramDiagnostics(getCompilerOptionsObjectLiteralSyntax);
let fileProcessingDiagnostics: FilePreprocessingDiagnostics[] | undefined;
let automaticTypeDirectiveNames: string[] | undefined;
let automaticTypeDirectiveResolutions: ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
@ -1661,13 +1575,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
let skipDefaultLib = options.noLib;
const getDefaultLibraryFileName = memoize(() => host.getDefaultLibFileName(options));
const defaultLibraryPath = host.getDefaultLibLocation ? host.getDefaultLibLocation() : getDirectoryPath(getDefaultLibraryFileName());
/**
* Diagnostics for the program
* Only add diagnostics directly if it always would be done irrespective of program structure reuse.
* Otherwise fileProcessingDiagnostics is correct locations so that the diagnostics can be reported in all structure use scenarios
*/
const programDiagnostics = createDiagnosticCollection();
let lazyProgramDiagnosticExplainingFile: LazyProgramDiagnosticExplainingFile[] | undefined = [];
let skipVerifyCompilerOptions = false;
const currentDirectory = host.getCurrentDirectory();
const supportedExtensions = getSupportedExtensions(options);
const supportedExtensionsWithJsonIfResolveJsonModule = getSupportedExtensionsWithJsonIfResolveJsonModule(options, supportedExtensions);
@ -1990,7 +1899,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
getTypeCount: () => getTypeChecker().getTypeCount(),
getInstantiationCount: () => getTypeChecker().getInstantiationCount(),
getRelationCacheSizes: () => getTypeChecker().getRelationCacheSizes(),
getFileProcessingDiagnostics: () => fileProcessingDiagnostics,
getFileProcessingDiagnostics: () => programDiagnostics.getFileProcessingDiagnostics(),
getAutomaticTypeDirectiveNames: () => automaticTypeDirectiveNames!,
getAutomaticTypeDirectiveResolutions: () => automaticTypeDirectiveResolutions,
isSourceFileFromExternalLibrary,
@ -2006,6 +1915,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
resolvedModules,
resolvedTypeReferenceDirectiveNames,
resolvedLibReferences,
getProgramDiagnosticsContainer: () => programDiagnostics,
getResolvedModule,
getResolvedModuleFromModuleSpecifier,
getResolvedTypeReferenceDirective,
@ -2038,7 +1948,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
realpath: host.realpath?.bind(host),
useCaseSensitiveFileNames: () => host.useCaseSensitiveFileNames(),
getCanonicalFileName,
getFileIncludeReasons: () => fileReasons,
getFileIncludeReasons: () => programDiagnostics.getFileReasons(),
structureIsReused,
writeFile,
getGlobalTypingsCacheLocation: maybeBind(host, host.getGlobalTypingsCacheLocation),
@ -2046,63 +1956,15 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
onProgramCreateComplete();
verifyCompilerOptions();
if (!skipVerifyCompilerOptions) {
verifyCompilerOptions();
}
performance.mark("afterProgram");
performance.measure("Program", "beforeProgram", "afterProgram");
tracing?.pop();
return program;
function updateAndGetProgramDiagnostics() {
if (lazyProgramDiagnosticExplainingFile) {
// Add file processingDiagnostics
fileProcessingDiagnostics?.forEach(diagnostic => {
switch (diagnostic.kind) {
case FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic:
return programDiagnostics.add(
createDiagnosticExplainingFile(
diagnostic.file && getSourceFileByPath(diagnostic.file),
diagnostic.fileProcessingReason,
diagnostic.diagnostic,
diagnostic.args || emptyArray,
),
);
case FilePreprocessingDiagnosticsKind.FilePreprocessingLibReferenceDiagnostic:
return programDiagnostics.add(filePreprocessingLibreferenceDiagnostic(diagnostic));
case FilePreprocessingDiagnosticsKind.ResolutionDiagnostics:
return diagnostic.diagnostics.forEach(d => programDiagnostics.add(d));
default:
Debug.assertNever(diagnostic);
}
});
lazyProgramDiagnosticExplainingFile.forEach(({ file, diagnostic, args }) =>
programDiagnostics.add(
createDiagnosticExplainingFile(file, /*fileProcessingReason*/ undefined, diagnostic, args),
)
);
lazyProgramDiagnosticExplainingFile = undefined;
fileReasonsToChain = undefined;
reasonToRelatedInfo = undefined;
}
return programDiagnostics;
}
function filePreprocessingLibreferenceDiagnostic({ reason }: FilePreprocessingLibReferenceDiagnostic) {
const { file, pos, end } = getReferencedFileLocation(program, reason) as ReferenceFileLocation;
const libReference = file.libReferenceDirectives[reason.index];
const libName = getLibNameFromLibReference(libReference);
const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts");
const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity);
return createFileDiagnostic(
file,
Debug.checkDefined(pos),
Debug.checkDefined(end) - pos,
suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0,
libName,
suggestion!,
);
}
function getResolvedModule(file: SourceFile, moduleName: string, mode: ResolutionMode) {
return resolvedModules?.get(file.path)?.get(moduleName, mode);
}
@ -2170,7 +2032,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
function addResolutionDiagnostics(resolution: ResolvedModuleWithFailedLookupLocations | ResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
if (!resolution.resolutionDiagnostics?.length) return;
(fileProcessingDiagnostics ??= []).push({
programDiagnostics.addFileProcessingDiagnostic({
kind: FilePreprocessingDiagnosticsKind.ResolutionDiagnostics,
diagnostics: resolution.resolutionDiagnostics,
});
@ -2289,16 +2151,19 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
function getCommonSourceDirectory() {
if (commonSourceDirectory === undefined) {
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program));
commonSourceDirectory = ts_getCommonSourceDirectory(
options,
() => mapDefined(emittedFiles, file => file.isDeclarationFile ? undefined : file.fileName),
currentDirectory,
getCanonicalFileName,
commonSourceDirectory => checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory),
);
let commonSourceDirectory = programDiagnostics.getCommonSourceDirectory();
if (commonSourceDirectory !== undefined) {
return commonSourceDirectory;
}
const emittedFiles = filter(files, file => sourceFileMayBeEmitted(file, program));
commonSourceDirectory = ts_getCommonSourceDirectory(
options,
() => mapDefined(emittedFiles, file => file.isDeclarationFile ? undefined : file.fileName),
currentDirectory,
getCanonicalFileName,
commonSourceDirectory => checkSourceFilesBelongToPath(emittedFiles, commonSourceDirectory),
);
programDiagnostics.setCommonSourceDirectory(commonSourceDirectory);
return commonSourceDirectory;
}
@ -2722,9 +2587,12 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
filesByName.set(path, filesByName.get(oldFile.path));
});
const isConfigIdentical = oldOptions.configFile && oldOptions.configFile === options.configFile
|| !oldOptions.configFile && !options.configFile && !optionsHaveChanges(oldOptions, options, optionDeclarations);
programDiagnostics.reuseStateFromOldProgram(oldProgram.getProgramDiagnosticsContainer(), isConfigIdentical);
skipVerifyCompilerOptions = isConfigIdentical;
files = newSourceFiles;
fileReasons = oldProgram.getFileIncludeReasons();
fileProcessingDiagnostics = oldProgram.getFileProcessingDiagnostics();
automaticTypeDirectiveNames = oldProgram.getAutomaticTypeDirectiveNames();
automaticTypeDirectiveResolutions = oldProgram.getAutomaticTypeDirectiveResolutions();
@ -2985,7 +2853,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
return emptyArray;
}
const programDiagnosticsInFile = updateAndGetProgramDiagnostics().getDiagnostics(sourceFile.fileName);
const programDiagnosticsInFile = programDiagnostics.getCombinedDiagnostics(program).getDiagnostics(sourceFile.fileName);
if (!sourceFile.commentDirectives?.length) {
return programDiagnosticsInFile;
}
@ -3435,16 +3303,16 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
function getOptionsDiagnostics(): SortedReadonlyArray<Diagnostic> {
return sortAndDeduplicateDiagnostics(concatenate(
updateAndGetProgramDiagnostics().getGlobalDiagnostics(),
programDiagnostics.getCombinedDiagnostics(program).getGlobalDiagnostics(),
getOptionsDiagnosticsOfConfigFile(),
));
}
function getOptionsDiagnosticsOfConfigFile() {
if (!options.configFile) return emptyArray;
let diagnostics = updateAndGetProgramDiagnostics().getDiagnostics(options.configFile.fileName);
let diagnostics = programDiagnostics.getCombinedDiagnostics(program).getDiagnostics(options.configFile.fileName);
forEachResolvedProjectReference(resolvedRef => {
diagnostics = concatenate(diagnostics, updateAndGetProgramDiagnostics().getDiagnostics(resolvedRef.sourceFile.fileName));
diagnostics = concatenate(diagnostics, programDiagnostics.getCombinedDiagnostics(program).getDiagnostics(resolvedRef.sourceFile.fileName));
});
return diagnostics;
}
@ -3666,7 +3534,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
function reportFileNamesDifferOnlyInCasingError(fileName: string, existingFile: SourceFile, reason: FileIncludeReason): void {
const hasExistingReasonToReportErrorOn = !isReferencedFile(reason) && some(fileReasons.get(existingFile.path), isReferencedFile);
const hasExistingReasonToReportErrorOn = !isReferencedFile(reason) && some(programDiagnostics.getFileReasons().get(existingFile.path), isReferencedFile);
if (hasExistingReasonToReportErrorOn) {
addFilePreprocessingFileExplainingDiagnostic(existingFile, reason, Diagnostics.Already_included_file_name_0_differs_from_file_name_1_only_in_casing, [existingFile.fileName, fileName]);
}
@ -3881,7 +3749,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
function addFileIncludeReason(file: SourceFile | undefined, reason: FileIncludeReason, checkExisting: boolean) {
if (file && (!checkExisting || !isReferencedFile(reason) || !filesWithReferencesProcessed?.has(reason.file))) {
fileReasons.add(file.path, reason);
programDiagnostics.getFileReasons().add(file.path, reason);
return true;
}
return false;
@ -4115,7 +3983,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index });
}
else {
(fileProcessingDiagnostics ||= []).push({
programDiagnostics.addFileProcessingDiagnostic({
kind: FilePreprocessingDiagnosticsKind.FilePreprocessingLibReferenceDiagnostic,
reason: { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index },
});
@ -4202,10 +4070,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
if (!sourceFile.isDeclarationFile) {
const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory));
if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) {
addLazyProgramDiagnosticExplainingFile(
programDiagnostics.addLazyConfigDiagnostic(
sourceFile,
Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files,
[sourceFile.fileName, rootDirectory],
sourceFile.fileName,
rootDirectory,
);
allFilesBelongToPath = false;
}
@ -4308,7 +4177,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
const outputFile = options.outFile;
if (!options.tsBuildInfoFile && options.incremental && !outputFile && !options.configFilePath) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified));
programDiagnostics.addConfigDiagnostic(createCompilerDiagnostic(Diagnostics.Option_incremental_can_only_be_specified_using_tsconfig_emitting_to_single_file_or_when_option_tsBuildInfoFile_is_specified));
}
verifyDeprecatedCompilerOptions();
@ -4320,10 +4189,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
for (const file of files) {
// Ignore file that is not emitted
if (sourceFileMayBeEmitted(file, program) && !rootPaths.has(file.path)) {
addLazyProgramDiagnosticExplainingFile(
programDiagnostics.addLazyConfigDiagnostic(
file,
Diagnostics.File_0_is_not_listed_within_the_file_list_of_project_1_Projects_must_list_all_files_or_use_an_include_pattern,
[file.fileName, options.configFilePath || ""],
file.fileName,
options.configFilePath || "",
);
}
}
@ -4410,7 +4280,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
else if (firstNonAmbientExternalModuleSourceFile && languageVersion < ScriptTarget.ES2015 && options.module === ModuleKind.None) {
// We cannot use createDiagnosticFromNode because nodes do not have parents yet
const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, typeof firstNonAmbientExternalModuleSourceFile.externalModuleIndicator === "boolean" ? firstNonAmbientExternalModuleSourceFile : firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!);
programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none));
programDiagnostics.addConfigDiagnostic(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_use_imports_exports_or_module_augmentations_when_module_is_none));
}
// Cannot specify module gen that isn't amd or system with --out
@ -4420,7 +4290,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
else if (options.module === undefined && firstNonAmbientExternalModuleSourceFile) {
const span = getErrorSpanForNode(firstNonAmbientExternalModuleSourceFile, typeof firstNonAmbientExternalModuleSourceFile.externalModuleIndicator === "boolean" ? firstNonAmbientExternalModuleSourceFile : firstNonAmbientExternalModuleSourceFile.externalModuleIndicator!);
programDiagnostics.add(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, "outFile"));
programDiagnostics.addConfigDiagnostic(createFileDiagnostic(firstNonAmbientExternalModuleSourceFile, span.start, span.length, Diagnostics.Cannot_compile_modules_using_option_0_unless_the_module_flag_is_amd_or_system, "outFile"));
}
}
@ -4699,123 +4569,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
});
}
function createDiagnosticExplainingFile(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason | undefined, diagnostic: DiagnosticMessage, args: DiagnosticArguments): Diagnostic {
let seenReasons: Set<FileIncludeReason> | undefined;
const reasons = file && fileReasons.get(file.path);
let fileIncludeReasons: DiagnosticMessageChain[] | undefined;
let relatedInfo: DiagnosticWithLocation[] | undefined;
let locationReason = isReferencedFile(fileProcessingReason) ? fileProcessingReason : undefined;
let fileIncludeReasonDetails: DiagnosticMessageChain | undefined;
let redirectInfo: DiagnosticMessageChain[] | undefined;
let cachedChain = file && fileReasonsToChain?.get(file.path);
let chain: DiagnosticMessageChain | undefined;
if (cachedChain) {
if (cachedChain.fileIncludeReasonDetails) {
seenReasons = new Set(reasons);
reasons?.forEach(populateRelatedInfo);
}
else {
reasons?.forEach(processReason);
}
redirectInfo = cachedChain.redirectInfo;
}
else {
reasons?.forEach(processReason);
redirectInfo = file && explainIfFileIsRedirectAndImpliedFormat(file, getCompilerOptionsForFile(file));
}
if (fileProcessingReason) processReason(fileProcessingReason);
const processedExtraReason = seenReasons?.size !== reasons?.length;
// If we have location and there is only one reason file is in which is the location, dont add details for file include
if (locationReason && seenReasons?.size === 1) seenReasons = undefined;
if (seenReasons && cachedChain) {
if (cachedChain.details && !processedExtraReason) {
chain = chainDiagnosticMessages(cachedChain.details, diagnostic, ...args || emptyArray);
}
else if (cachedChain.fileIncludeReasonDetails) {
if (!processedExtraReason) {
if (!cachedFileIncludeDetailsHasProcessedExtraReason()) {
fileIncludeReasonDetails = cachedChain.fileIncludeReasonDetails;
}
else {
fileIncludeReasons = cachedChain.fileIncludeReasonDetails.next!.slice(0, reasons!.length);
}
}
else {
if (!cachedFileIncludeDetailsHasProcessedExtraReason()) {
fileIncludeReasons = [...cachedChain.fileIncludeReasonDetails.next!, fileIncludeReasons![0]];
}
else {
fileIncludeReasons = append(cachedChain.fileIncludeReasonDetails.next!.slice(0, reasons!.length), fileIncludeReasons![0]);
}
}
}
}
if (!chain) {
if (!fileIncludeReasonDetails) fileIncludeReasonDetails = seenReasons && chainDiagnosticMessages(fileIncludeReasons, Diagnostics.The_file_is_in_the_program_because_Colon);
chain = chainDiagnosticMessages(
redirectInfo ?
fileIncludeReasonDetails ? [fileIncludeReasonDetails, ...redirectInfo] : redirectInfo :
fileIncludeReasonDetails,
diagnostic,
...args || emptyArray,
);
}
// This is chain's next contains:
// - File is in program because:
// - Files reasons listed
// - extra reason if its not already processed - this happens in case sensitive file system where files differ in casing and we are giving reasons for two files so reason is not in file's reason
// fyi above whole secton is ommited if we have single reason and we are reporting at that reason's location
// - redirect and additional information about file
// So cache result if we havent ommited file include reasons
if (file) {
if (cachedChain) {
// Cache new fileIncludeDetails if we have update
// Or if we had cached with more details than the reasons
if (!cachedChain.fileIncludeReasonDetails || (!processedExtraReason && fileIncludeReasonDetails)) {
cachedChain.fileIncludeReasonDetails = fileIncludeReasonDetails;
}
}
else {
(fileReasonsToChain ??= new Map()).set(file.path, cachedChain = { fileIncludeReasonDetails, redirectInfo });
}
// If we didnt compute extra file include reason , cache the details to use directly
if (!cachedChain.details && !processedExtraReason) cachedChain.details = chain.next;
}
const location = locationReason && getReferencedFileLocation(program, locationReason);
return location && isReferenceFileLocation(location) ?
createFileDiagnosticFromMessageChain(location.file, location.pos, location.end - location.pos, chain, relatedInfo) :
createCompilerDiagnosticFromMessageChain(chain, relatedInfo);
function processReason(reason: FileIncludeReason) {
if (seenReasons?.has(reason)) return;
(seenReasons ??= new Set()).add(reason);
(fileIncludeReasons ??= []).push(fileIncludeReasonToDiagnostics(program, reason));
populateRelatedInfo(reason);
}
function populateRelatedInfo(reason: FileIncludeReason) {
if (!locationReason && isReferencedFile(reason)) {
// Report error at first reference file or file currently in processing and dont report in related information
locationReason = reason;
}
else if (locationReason !== reason) {
relatedInfo = append(relatedInfo, getFileIncludeReasonToRelatedInformation(reason));
}
}
function cachedFileIncludeDetailsHasProcessedExtraReason() {
return cachedChain!.fileIncludeReasonDetails!.next?.length !== reasons?.length;
}
}
function addFilePreprocessingFileExplainingDiagnostic(file: SourceFile | undefined, fileProcessingReason: FileIncludeReason, diagnostic: DiagnosticMessage, args: DiagnosticArguments) {
(fileProcessingDiagnostics ||= []).push({
programDiagnostics.addFileProcessingDiagnostic({
kind: FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic,
file: file && file.path,
fileProcessingReason,
@ -4824,111 +4579,6 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
});
}
function addLazyProgramDiagnosticExplainingFile(file: SourceFile, diagnostic: DiagnosticMessage, args: DiagnosticArguments) {
lazyProgramDiagnosticExplainingFile!.push({ file, diagnostic, args });
}
function getFileIncludeReasonToRelatedInformation(reason: FileIncludeReason) {
let relatedInfo = reasonToRelatedInfo?.get(reason);
if (relatedInfo === undefined) (reasonToRelatedInfo ??= new Map()).set(reason, relatedInfo = fileIncludeReasonToRelatedInformation(reason) ?? false);
return relatedInfo || undefined;
}
function fileIncludeReasonToRelatedInformation(reason: FileIncludeReason): DiagnosticWithLocation | undefined {
if (isReferencedFile(reason)) {
const referenceLocation = getReferencedFileLocation(program, reason);
let message: DiagnosticMessage;
switch (reason.kind) {
case FileIncludeKind.Import:
message = Diagnostics.File_is_included_via_import_here;
break;
case FileIncludeKind.ReferenceFile:
message = Diagnostics.File_is_included_via_reference_here;
break;
case FileIncludeKind.TypeReferenceDirective:
message = Diagnostics.File_is_included_via_type_library_reference_here;
break;
case FileIncludeKind.LibReferenceDirective:
message = Diagnostics.File_is_included_via_library_reference_here;
break;
default:
Debug.assertNever(reason);
}
return isReferenceFileLocation(referenceLocation) ? createFileDiagnostic(
referenceLocation.file,
referenceLocation.pos,
referenceLocation.end - referenceLocation.pos,
message,
) : undefined;
}
if (!options.configFile) return undefined;
let configFileNode: Node | undefined;
let message: DiagnosticMessage;
switch (reason.kind) {
case FileIncludeKind.RootFile:
if (!options.configFile.configFileSpecs) return undefined;
const fileName = getNormalizedAbsolutePath(rootNames[reason.index], currentDirectory);
const matchedByFiles = getMatchedFileSpec(program, fileName);
if (matchedByFiles) {
configFileNode = getTsConfigPropArrayElementValue(options.configFile, "files", matchedByFiles);
message = Diagnostics.File_is_matched_by_files_list_specified_here;
break;
}
const matchedByInclude = getMatchedIncludeSpec(program, fileName);
// Could be additional files specified as roots
if (!matchedByInclude || !isString(matchedByInclude)) return undefined;
configFileNode = getTsConfigPropArrayElementValue(options.configFile, "include", matchedByInclude);
message = Diagnostics.File_is_matched_by_include_pattern_specified_here;
break;
case FileIncludeKind.SourceFromProjectReference:
case FileIncludeKind.OutputFromProjectReference:
const referencedResolvedRef = Debug.checkDefined(resolvedProjectReferences?.[reason.index]);
const referenceInfo = forEachProjectReference(
projectReferences,
resolvedProjectReferences,
(resolvedRef, parent, index) =>
resolvedRef === referencedResolvedRef ?
{ sourceFile: parent?.sourceFile || options.configFile!, index } :
undefined,
);
if (!referenceInfo) return undefined;
const { sourceFile, index } = referenceInfo;
const referencesSyntax = forEachTsConfigPropArray(sourceFile as TsConfigSourceFile, "references", property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined);
return referencesSyntax && referencesSyntax.elements.length > index ?
createDiagnosticForNodeInSourceFile(
sourceFile,
referencesSyntax.elements[index],
reason.kind === FileIncludeKind.OutputFromProjectReference ?
Diagnostics.File_is_output_from_referenced_project_specified_here :
Diagnostics.File_is_source_from_referenced_project_specified_here,
) :
undefined;
case FileIncludeKind.AutomaticTypeDirectiveFile:
if (!options.types) return undefined;
configFileNode = getOptionsSyntaxByArrayElementValue("types", reason.typeReference);
message = Diagnostics.File_is_entry_point_of_type_library_specified_here;
break;
case FileIncludeKind.LibFile:
if (reason.index !== undefined) {
configFileNode = getOptionsSyntaxByArrayElementValue("lib", options.lib![reason.index]);
message = Diagnostics.File_is_library_specified_here;
break;
}
const target = getNameOfScriptTarget(getEmitScriptTarget(options));
configFileNode = target ? getOptionsSyntaxByValue("target", target) : undefined;
message = Diagnostics.File_is_default_library_for_target_specified_here;
break;
default:
Debug.assertNever(reason);
}
return configFileNode && createDiagnosticForNodeInSourceFile(
options.configFile,
configFileNode,
message,
);
}
function verifyProjectReferences() {
const buildInfoPath = !options.suppressOutputPathCheck ? getTsBuildInfoEmitOutputFilePath(options) : undefined;
forEachProjectReference(
@ -4966,7 +4616,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
forEachPropertyAssignment(pathProp.initializer, key, keyProps => {
const initializer = keyProps.initializer;
if (isArrayLiteralExpression(initializer) && initializer.elements.length > valueIndex) {
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, ...args));
programDiagnostics.addConfigDiagnostic(createDiagnosticForNodeInSourceFile(options.configFile!, initializer.elements[valueIndex], message, ...args));
needCompilerDiagnostic = false;
}
});
@ -4999,21 +4649,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
}
function forEachOptionsSyntaxByName<T>(name: string, callback: (prop: PropertyAssignment) => T | undefined): T | undefined {
return forEachPropertyAssignment(getCompilerOptionsObjectLiteralSyntax(), name, callback);
}
function forEachOptionPathsSyntax<T>(callback: (prop: PropertyAssignment) => T | undefined) {
return forEachOptionsSyntaxByName("paths", callback);
}
function getOptionsSyntaxByValue(name: string, value: string) {
return forEachOptionsSyntaxByName(name, property => isStringLiteral(property.initializer) && property.initializer.text === value ? property.initializer : undefined);
}
function getOptionsSyntaxByArrayElementValue(name: string, value: string) {
const compilerOptionsObjectLiteralSyntax = getCompilerOptionsObjectLiteralSyntax();
return compilerOptionsObjectLiteralSyntax && getPropertyArrayElementValue(compilerOptionsObjectLiteralSyntax, name, value);
return forEachOptionsSyntaxByName(getCompilerOptionsObjectLiteralSyntax(), "paths", callback);
}
function createDiagnosticForOptionName(message: DiagnosticMessage, option1: string, option2?: string, option3?: string) {
@ -5028,10 +4665,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
function createDiagnosticForReference(sourceFile: JsonSourceFile | undefined, index: number, message: DiagnosticMessage, ...args: DiagnosticArguments) {
const referencesSyntax = forEachTsConfigPropArray(sourceFile || options.configFile, "references", property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined);
if (referencesSyntax && referencesSyntax.elements.length > index) {
programDiagnostics.add(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, ...args));
programDiagnostics.addConfigDiagnostic(createDiagnosticForNodeInSourceFile(sourceFile || options.configFile!, referencesSyntax.elements[index], message, ...args));
}
else {
programDiagnostics.add(createCompilerDiagnostic(message, ...args));
programDiagnostics.addConfigDiagnostic(createCompilerDiagnostic(message, ...args));
}
}
@ -5055,18 +4692,18 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
if (compilerOptionsProperty) {
// eslint-disable-next-line local/no-in-operator
if ("messageText" in message) {
programDiagnostics.add(createDiagnosticForNodeFromMessageChain(options.configFile!, compilerOptionsProperty.name, message));
programDiagnostics.addConfigDiagnostic(createDiagnosticForNodeFromMessageChain(options.configFile!, compilerOptionsProperty.name, message));
}
else {
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, compilerOptionsProperty.name, message, ...args));
programDiagnostics.addConfigDiagnostic(createDiagnosticForNodeInSourceFile(options.configFile!, compilerOptionsProperty.name, message, ...args));
}
}
// eslint-disable-next-line local/no-in-operator
else if ("messageText" in message) {
programDiagnostics.add(createCompilerDiagnosticFromMessageChain(message));
programDiagnostics.addConfigDiagnostic(createCompilerDiagnosticFromMessageChain(message));
}
else {
programDiagnostics.add(createCompilerDiagnostic(message, ...args));
programDiagnostics.addConfigDiagnostic(createCompilerDiagnostic(message, ...args));
}
}
@ -5097,10 +4734,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
forEachPropertyAssignment(objectLiteral, key1, prop => {
// eslint-disable-next-line local/no-in-operator
if ("messageText" in message) {
programDiagnostics.add(createDiagnosticForNodeFromMessageChain(options.configFile!, onKey ? prop.name : prop.initializer, message));
programDiagnostics.addConfigDiagnostic(createDiagnosticForNodeFromMessageChain(options.configFile!, onKey ? prop.name : prop.initializer, message));
}
else {
programDiagnostics.add(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, ...args));
programDiagnostics.addConfigDiagnostic(createDiagnosticForNodeInSourceFile(options.configFile!, onKey ? prop.name : prop.initializer, message, ...args));
}
needsCompilerDiagnostic = true;
}, key2);
@ -5109,7 +4746,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
function blockEmittingOfFile(emitFileName: string, diag: Diagnostic) {
hasEmitBlockingDiagnostics.set(toPath(emitFileName), true);
programDiagnostics.add(diag);
programDiagnostics.addConfigDiagnostic(diag);
}
function isEmittedFile(file: string): boolean {

View File

@ -0,0 +1,426 @@
import {
append,
chainDiagnosticMessages,
createCompilerDiagnosticFromMessageChain,
createDiagnosticCollection,
createDiagnosticForNodeInSourceFile,
createFileDiagnostic,
createFileDiagnosticFromMessageChain,
createMultiMap,
Debug,
Diagnostic,
DiagnosticArguments,
DiagnosticCollection,
DiagnosticMessage,
DiagnosticMessageChain,
Diagnostics,
DiagnosticWithLocation,
emptyArray,
explainIfFileIsRedirectAndImpliedFormat,
FileIncludeKind,
FileIncludeReason,
fileIncludeReasonToDiagnostics,
FilePreprocessingDiagnostics,
FilePreprocessingDiagnosticsKind,
FilePreprocessingLibReferenceDiagnostic,
forEachProjectReference,
forEachTsConfigPropArray,
getEmitScriptTarget,
getLibNameFromLibReference,
getMatchedFileSpec,
getMatchedIncludeSpec,
getNameOfScriptTarget,
getNormalizedAbsolutePath,
getOptionsSyntaxByArrayElementValue,
getOptionsSyntaxByValue,
getReferencedFileLocation,
getSpellingSuggestion,
getTsConfigPropArrayElementValue,
identity,
isArrayLiteralExpression,
isReferencedFile,
isReferenceFileLocation,
isString,
libs,
MultiMap,
Node,
ObjectLiteralExpression,
Path,
Program,
ReferenceFileLocation,
removePrefix,
removeSuffix,
SourceFile,
TsConfigSourceFile,
} from "./_namespaces/ts.js";
interface FileReasonToChainCache {
fileIncludeReasonDetails: DiagnosticMessageChain | undefined;
redirectInfo: DiagnosticMessageChain[] | undefined;
details?: DiagnosticMessageChain[];
}
/** @internal */
export interface LazyConfigDiagnostic {
file: SourceFile;
diagnostic: DiagnosticMessage;
args: DiagnosticArguments;
}
/** @internal */
export interface ProgramDiagnostics {
addConfigDiagnostic(diag: Diagnostic): void;
addLazyConfigDiagnostic(file: SourceFile, message: DiagnosticMessage, ...args: DiagnosticArguments): void;
addFileProcessingDiagnostic(diag: FilePreprocessingDiagnostics): void;
setCommonSourceDirectory(directory: string): void;
reuseStateFromOldProgram(oldProgramDiagnostics: ProgramDiagnostics, isConfigIdentical: boolean): void;
getFileProcessingDiagnostics(): FilePreprocessingDiagnostics[] | undefined;
getFileReasons(): MultiMap<Path, FileIncludeReason>;
getConfigDiagnostics(): DiagnosticCollection | undefined;
getLazyConfigDiagnostics(): LazyConfigDiagnostic[] | undefined;
getCommonSourceDirectory(): string | undefined;
getCombinedDiagnostics(program: Program): DiagnosticCollection;
}
/** @internal */
export function createProgramDiagnostics(getCompilerOptionsObjectLiteralSyntax: () => ObjectLiteralExpression | undefined): ProgramDiagnostics {
// Only valid for a single program. The individual components can be reused under certain
// circumstances (described below), but during `getCombinedDiagnostics`, position information
// is applied to the diagnostics, so they cannot be shared between programs.
let computedDiagnostics: DiagnosticCollection | undefined;
// Valid between programs if `StructureIsReused.Completely`.
let fileReasons = createMultiMap<Path, FileIncludeReason>();
let fileProcessingDiagnostics: FilePreprocessingDiagnostics[] | undefined;
// Valid between programs if `StructureIsReused.Completely` and config file is identical.
let commonSourceDirectory: string | undefined;
let configDiagnostics: DiagnosticCollection | undefined;
let lazyConfigDiagnostics: LazyConfigDiagnostic[] | undefined;
// Local state, only used during getDiagnostics
let fileReasonsToChain: Map<Path, FileReasonToChainCache> | undefined;
let reasonToRelatedInfo: Map<FileIncludeReason, DiagnosticWithLocation | false> | undefined;
return {
addConfigDiagnostic(diag) {
Debug.assert(computedDiagnostics === undefined, "Cannot modify program diagnostic state after requesting combined diagnostics");
(configDiagnostics ??= createDiagnosticCollection()).add(diag);
},
addLazyConfigDiagnostic(file, message, ...args) {
Debug.assert(computedDiagnostics === undefined, "Cannot modify program diagnostic state after requesting combined diagnostics");
(lazyConfigDiagnostics ??= []).push({ file, diagnostic: message, args });
},
addFileProcessingDiagnostic(diag) {
Debug.assert(computedDiagnostics === undefined, "Cannot modify program diagnostic state after requesting combined diagnostics");
(fileProcessingDiagnostics ??= []).push(diag);
},
setCommonSourceDirectory(directory) {
commonSourceDirectory = directory;
},
reuseStateFromOldProgram(oldProgramDiagnostics, isConfigIdentical) {
fileReasons = oldProgramDiagnostics.getFileReasons();
fileProcessingDiagnostics = oldProgramDiagnostics.getFileProcessingDiagnostics();
if (isConfigIdentical) {
commonSourceDirectory = oldProgramDiagnostics.getCommonSourceDirectory();
configDiagnostics = oldProgramDiagnostics.getConfigDiagnostics();
lazyConfigDiagnostics = oldProgramDiagnostics.getLazyConfigDiagnostics();
}
},
getFileProcessingDiagnostics() {
return fileProcessingDiagnostics;
},
getFileReasons() {
return fileReasons;
},
getCommonSourceDirectory() {
return commonSourceDirectory;
},
getConfigDiagnostics() {
return configDiagnostics;
},
getLazyConfigDiagnostics() {
return lazyConfigDiagnostics;
},
getCombinedDiagnostics(program: Program) {
if (computedDiagnostics) {
return computedDiagnostics;
}
computedDiagnostics = createDiagnosticCollection();
configDiagnostics?.getDiagnostics().forEach(d => computedDiagnostics!.add(d));
fileProcessingDiagnostics?.forEach(diagnostic => {
switch (diagnostic.kind) {
case FilePreprocessingDiagnosticsKind.FilePreprocessingFileExplainingDiagnostic:
return computedDiagnostics!.add(
createDiagnosticExplainingFile(
program,
diagnostic.file && program.getSourceFileByPath(diagnostic.file),
diagnostic.fileProcessingReason,
diagnostic.diagnostic,
diagnostic.args || emptyArray,
),
);
case FilePreprocessingDiagnosticsKind.FilePreprocessingLibReferenceDiagnostic:
return computedDiagnostics!.add(filePreprocessingLibreferenceDiagnostic(program, diagnostic));
case FilePreprocessingDiagnosticsKind.ResolutionDiagnostics:
return diagnostic.diagnostics.forEach(d => computedDiagnostics!.add(d));
default:
Debug.assertNever(diagnostic);
}
});
lazyConfigDiagnostics?.forEach(({ file, diagnostic, args }) =>
computedDiagnostics!.add(
createDiagnosticExplainingFile(program, file, /*fileProcessingReason*/ undefined, diagnostic, args),
)
);
fileReasonsToChain = undefined;
reasonToRelatedInfo = undefined;
return computedDiagnostics;
},
};
function filePreprocessingLibreferenceDiagnostic(program: Program, { reason }: FilePreprocessingLibReferenceDiagnostic) {
const { file, pos, end } = getReferencedFileLocation(program, reason) as ReferenceFileLocation;
const libReference = file.libReferenceDirectives[reason.index];
const libName = getLibNameFromLibReference(libReference);
const unqualifiedLibName = removeSuffix(removePrefix(libName, "lib."), ".d.ts");
const suggestion = getSpellingSuggestion(unqualifiedLibName, libs, identity);
return createFileDiagnostic(
file,
Debug.checkDefined(pos),
Debug.checkDefined(end) - pos,
suggestion ? Diagnostics.Cannot_find_lib_definition_for_0_Did_you_mean_1 : Diagnostics.Cannot_find_lib_definition_for_0,
libName,
suggestion!,
);
}
function createDiagnosticExplainingFile(program: Program, file: SourceFile | undefined, fileProcessingReason: FileIncludeReason | undefined, diagnostic: DiagnosticMessage, args: DiagnosticArguments): Diagnostic {
let seenReasons: Set<FileIncludeReason> | undefined;
let fileIncludeReasons: DiagnosticMessageChain[] | undefined;
let relatedInfo: DiagnosticWithLocation[] | undefined;
let fileIncludeReasonDetails: DiagnosticMessageChain | undefined;
let redirectInfo: DiagnosticMessageChain[] | undefined;
let chain: DiagnosticMessageChain | undefined;
const reasons = file && fileReasons.get(file.path);
let locationReason = isReferencedFile(fileProcessingReason) ? fileProcessingReason : undefined;
let cachedChain = file && fileReasonsToChain?.get(file.path);
if (cachedChain) {
if (cachedChain.fileIncludeReasonDetails) {
seenReasons = new Set(reasons);
reasons?.forEach(populateRelatedInfo);
}
else {
reasons?.forEach(processReason);
}
redirectInfo = cachedChain.redirectInfo;
}
else {
reasons?.forEach(processReason);
redirectInfo = file && explainIfFileIsRedirectAndImpliedFormat(file, program.getCompilerOptionsForFile(file));
}
if (fileProcessingReason) processReason(fileProcessingReason);
const processedExtraReason = seenReasons?.size !== reasons?.length;
// If we have location and there is only one reason file is in which is the location, dont add details for file include
if (locationReason && seenReasons?.size === 1) seenReasons = undefined;
if (seenReasons && cachedChain) {
if (cachedChain.details && !processedExtraReason) {
chain = chainDiagnosticMessages(cachedChain.details, diagnostic, ...args ?? emptyArray);
}
else if (cachedChain.fileIncludeReasonDetails) {
if (!processedExtraReason) {
if (!cachedFileIncludeDetailsHasProcessedExtraReason()) {
fileIncludeReasonDetails = cachedChain.fileIncludeReasonDetails;
}
else {
fileIncludeReasons = cachedChain.fileIncludeReasonDetails.next!.slice(0, reasons!.length);
}
}
else {
if (!cachedFileIncludeDetailsHasProcessedExtraReason()) {
fileIncludeReasons = [...cachedChain.fileIncludeReasonDetails.next!, fileIncludeReasons![0]];
}
else {
fileIncludeReasons = append(cachedChain.fileIncludeReasonDetails.next!.slice(0, reasons!.length), fileIncludeReasons![0]);
}
}
}
}
if (!chain) {
if (!fileIncludeReasonDetails) fileIncludeReasonDetails = seenReasons && chainDiagnosticMessages(fileIncludeReasons, Diagnostics.The_file_is_in_the_program_because_Colon);
chain = chainDiagnosticMessages(
redirectInfo ?
fileIncludeReasonDetails ? [fileIncludeReasonDetails, ...redirectInfo] : redirectInfo :
fileIncludeReasonDetails,
diagnostic,
...args || emptyArray,
);
}
// This is chain's next contains:
// - File is in program because:
// - Files reasons listed
// - extra reason if its not already processed - this happens in case sensitive file system where files differ in casing and we are giving reasons for two files so reason is not in file's reason
// fyi above whole secton is ommited if we have single reason and we are reporting at that reason's location
// - redirect and additional information about file
// So cache result if we havent ommited file include reasons
if (file) {
if (cachedChain) {
// Cache new fileIncludeDetails if we have update
// Or if we had cached with more details than the reasons
if (!cachedChain.fileIncludeReasonDetails || (!processedExtraReason && fileIncludeReasonDetails)) {
cachedChain.fileIncludeReasonDetails = fileIncludeReasonDetails;
}
}
else {
(fileReasonsToChain ??= new Map()).set(file.path, cachedChain = { fileIncludeReasonDetails, redirectInfo });
}
// If we didnt compute extra file include reason , cache the details to use directly
if (!cachedChain.details && !processedExtraReason) cachedChain.details = chain.next;
}
const location = locationReason && getReferencedFileLocation(program, locationReason);
return location && isReferenceFileLocation(location) ?
createFileDiagnosticFromMessageChain(location.file, location.pos, location.end - location.pos, chain, relatedInfo) :
createCompilerDiagnosticFromMessageChain(chain, relatedInfo);
function processReason(reason: FileIncludeReason) {
if (seenReasons?.has(reason)) return;
(seenReasons ??= new Set()).add(reason);
(fileIncludeReasons ??= []).push(fileIncludeReasonToDiagnostics(program, reason));
populateRelatedInfo(reason);
}
function populateRelatedInfo(reason: FileIncludeReason) {
if (!locationReason && isReferencedFile(reason)) {
// Report error at first reference file or file currently in processing and dont report in related information
locationReason = reason;
}
else if (locationReason !== reason) {
relatedInfo = append(relatedInfo, getFileIncludeReasonToRelatedInformation(program, reason));
}
}
function cachedFileIncludeDetailsHasProcessedExtraReason() {
return cachedChain!.fileIncludeReasonDetails!.next?.length !== reasons?.length;
}
}
function getFileIncludeReasonToRelatedInformation(program: Program, reason: FileIncludeReason) {
let relatedInfo = reasonToRelatedInfo?.get(reason);
if (relatedInfo === undefined) (reasonToRelatedInfo ??= new Map()).set(reason, relatedInfo = fileIncludeReasonToRelatedInformation(program, reason) ?? false);
return relatedInfo || undefined;
}
function fileIncludeReasonToRelatedInformation(program: Program, reason: FileIncludeReason): DiagnosticWithLocation | undefined {
if (isReferencedFile(reason)) {
const referenceLocation = getReferencedFileLocation(program, reason);
let message: DiagnosticMessage;
switch (reason.kind) {
case FileIncludeKind.Import:
message = Diagnostics.File_is_included_via_import_here;
break;
case FileIncludeKind.ReferenceFile:
message = Diagnostics.File_is_included_via_reference_here;
break;
case FileIncludeKind.TypeReferenceDirective:
message = Diagnostics.File_is_included_via_type_library_reference_here;
break;
case FileIncludeKind.LibReferenceDirective:
message = Diagnostics.File_is_included_via_library_reference_here;
break;
default:
Debug.assertNever(reason);
}
return isReferenceFileLocation(referenceLocation) ? createFileDiagnostic(
referenceLocation.file,
referenceLocation.pos,
referenceLocation.end - referenceLocation.pos,
message,
) : undefined;
}
const currentDirectory = program.getCurrentDirectory();
const rootNames = program.getRootFileNames();
const options = program.getCompilerOptions();
if (!options.configFile) return undefined;
let configFileNode: Node | undefined;
let message: DiagnosticMessage;
switch (reason.kind) {
case FileIncludeKind.RootFile:
if (!options.configFile.configFileSpecs) return undefined;
const fileName = getNormalizedAbsolutePath(rootNames[reason.index], currentDirectory);
const matchedByFiles = getMatchedFileSpec(program, fileName);
if (matchedByFiles) {
configFileNode = getTsConfigPropArrayElementValue(options.configFile, "files", matchedByFiles);
message = Diagnostics.File_is_matched_by_files_list_specified_here;
break;
}
const matchedByInclude = getMatchedIncludeSpec(program, fileName);
// Could be additional files specified as roots
if (!matchedByInclude || !isString(matchedByInclude)) return undefined;
configFileNode = getTsConfigPropArrayElementValue(options.configFile, "include", matchedByInclude);
message = Diagnostics.File_is_matched_by_include_pattern_specified_here;
break;
case FileIncludeKind.SourceFromProjectReference:
case FileIncludeKind.OutputFromProjectReference:
const resolvedProjectReferences = program.getResolvedProjectReferences();
const projectReferences = program.getProjectReferences();
const referencedResolvedRef = Debug.checkDefined(resolvedProjectReferences?.[reason.index]);
const referenceInfo = forEachProjectReference(
projectReferences,
resolvedProjectReferences,
(resolvedRef, parent, index) =>
resolvedRef === referencedResolvedRef ?
{ sourceFile: parent?.sourceFile || options.configFile!, index } :
undefined,
);
if (!referenceInfo) return undefined;
const { sourceFile, index } = referenceInfo;
const referencesSyntax = forEachTsConfigPropArray(sourceFile as TsConfigSourceFile, "references", property => isArrayLiteralExpression(property.initializer) ? property.initializer : undefined);
return referencesSyntax && referencesSyntax.elements.length > index ?
createDiagnosticForNodeInSourceFile(
sourceFile,
referencesSyntax.elements[index],
reason.kind === FileIncludeKind.OutputFromProjectReference ?
Diagnostics.File_is_output_from_referenced_project_specified_here :
Diagnostics.File_is_source_from_referenced_project_specified_here,
) :
undefined;
case FileIncludeKind.AutomaticTypeDirectiveFile:
if (!options.types) return undefined;
configFileNode = getOptionsSyntaxByArrayElementValue(getCompilerOptionsObjectLiteralSyntax(), "types", reason.typeReference);
message = Diagnostics.File_is_entry_point_of_type_library_specified_here;
break;
case FileIncludeKind.LibFile:
if (reason.index !== undefined) {
configFileNode = getOptionsSyntaxByArrayElementValue(getCompilerOptionsObjectLiteralSyntax(), "lib", options.lib![reason.index]);
message = Diagnostics.File_is_library_specified_here;
break;
}
const target = getNameOfScriptTarget(getEmitScriptTarget(options));
configFileNode = target ? getOptionsSyntaxByValue(getCompilerOptionsObjectLiteralSyntax(), "target", target) : undefined;
message = Diagnostics.File_is_default_library_for_target_specified_here;
break;
default:
Debug.assertNever(reason);
}
return configFileNode && createDiagnosticForNodeInSourceFile(
options.configFile,
configFileNode,
message,
);
}
}

View File

@ -13,6 +13,7 @@ import {
PackageJsonInfo,
PackageJsonInfoCache,
Pattern,
ProgramDiagnostics,
SymlinkCache,
ThisContainer,
} from "./_namespaces/ts.js";
@ -4892,6 +4893,7 @@ export interface Program extends ScriptReferenceHost {
* @internal
*/
resolvedLibReferences: Map<string, LibResolution> | undefined;
/** @internal */ getProgramDiagnosticsContainer: () => ProgramDiagnostics;
/** @internal */ getCurrentPackagesMap(): Map<string, boolean> | undefined;
/**
* Is the file emitted file

View File

@ -138,6 +138,7 @@ import {
FileExtensionInfo,
fileExtensionIs,
fileExtensionIsOneOf,
FileReference,
FileWatcher,
filter,
find,
@ -408,6 +409,7 @@ import {
lastOrUndefined,
LateVisibilityPaintedStatement,
length,
libMap,
LiteralImportTypeNode,
LiteralLikeElementAccessExpression,
LiteralLikeNode,
@ -497,6 +499,7 @@ import {
ResolutionMode,
ResolvedModuleFull,
ResolvedModuleWithFailedLookupLocations,
ResolvedProjectReference,
ResolvedTypeReferenceDirective,
ResolvedTypeReferenceDirectiveWithFailedLookupLocations,
resolvePath,
@ -549,6 +552,7 @@ import {
TextRange,
TextSpan,
ThisTypePredicate,
toFileNameLowerCase,
Token,
TokenFlags,
tokenToString,
@ -3016,14 +3020,6 @@ export function forEachPropertyAssignment<T>(objectLiteral: ObjectLiteralExpress
});
}
/** @internal */
export function getPropertyArrayElementValue(objectLiteral: ObjectLiteralExpression, propKey: string, elementValue: string): StringLiteral | undefined {
return forEachPropertyAssignment(objectLiteral, propKey, property =>
isArrayLiteralExpression(property.initializer) ?
find(property.initializer.elements, (element): element is StringLiteral => isStringLiteral(element) && element.text === elementValue) :
undefined);
}
/** @internal */
export function getTsConfigObjectLiteralExpression(tsConfigSourceFile: TsConfigSourceFile | undefined): ObjectLiteralExpression | undefined {
if (tsConfigSourceFile && tsConfigSourceFile.statements.length) {
@ -12113,3 +12109,91 @@ export function isNewScopeNode(node: Node): node is IntroducesNewScopeNode {
|| isJSDocSignature(node)
|| isMappedTypeNode(node);
}
/** @internal */
export function getLibNameFromLibReference(libReference: FileReference): string {
return toFileNameLowerCase(libReference.fileName);
}
/** @internal */
export function getLibFileNameFromLibReference(libReference: FileReference): string | undefined {
const libName = getLibNameFromLibReference(libReference);
return libMap.get(libName);
}
/** @internal */
export function forEachResolvedProjectReference<T>(
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
cb: (resolvedProjectReference: ResolvedProjectReference, parent: ResolvedProjectReference | undefined) => T | undefined,
): T | undefined {
return forEachProjectReference(
/*projectReferences*/ undefined,
resolvedProjectReferences,
(resolvedRef, parent) => resolvedRef && cb(resolvedRef, parent),
);
}
/** @internal */
export function forEachProjectReference<T>(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
cbResolvedRef: (resolvedRef: ResolvedProjectReference | undefined, parent: ResolvedProjectReference | undefined, index: number) => T | undefined,
cbRef?: (projectReferences: readonly ProjectReference[] | undefined, parent: ResolvedProjectReference | undefined) => T | undefined,
): T | undefined {
let seenResolvedRefs: Set<Path> | undefined;
return worker(projectReferences, resolvedProjectReferences, /*parent*/ undefined);
function worker(
projectReferences: readonly ProjectReference[] | undefined,
resolvedProjectReferences: readonly (ResolvedProjectReference | undefined)[] | undefined,
parent: ResolvedProjectReference | undefined,
): T | undefined {
// Visit project references first
if (cbRef) {
const result = cbRef(projectReferences, parent);
if (result) return result;
}
let skipChildren: Set<ResolvedProjectReference> | undefined;
return forEach(
resolvedProjectReferences,
(resolvedRef, index) => {
if (resolvedRef && seenResolvedRefs?.has(resolvedRef.sourceFile.path)) {
(skipChildren ??= new Set()).add(resolvedRef);
// ignore recursives
return undefined;
}
const result = cbResolvedRef(resolvedRef, parent, index);
if (result || !resolvedRef) return result;
(seenResolvedRefs ||= new Set()).add(resolvedRef.sourceFile.path);
},
) || forEach(
resolvedProjectReferences,
resolvedRef =>
resolvedRef && !skipChildren?.has(resolvedRef) ?
worker(resolvedRef.commandLine.projectReferences, resolvedRef.references, resolvedRef) :
undefined,
);
}
}
/** @internal */
export function getOptionsSyntaxByArrayElementValue(optionsObject: ObjectLiteralExpression | undefined, name: string, value: string): StringLiteral | undefined {
return optionsObject && getPropertyArrayElementValue(optionsObject, name, value);
}
function getPropertyArrayElementValue(objectLiteral: ObjectLiteralExpression, propKey: string, elementValue: string): StringLiteral | undefined {
return forEachPropertyAssignment(objectLiteral, propKey, property =>
isArrayLiteralExpression(property.initializer) ?
find(property.initializer.elements, (element): element is StringLiteral => isStringLiteral(element) && element.text === elementValue) :
undefined);
}
/** @internal */
export function getOptionsSyntaxByValue(optionsObject: ObjectLiteralExpression | undefined, name: string, value: string): StringLiteral | undefined {
return forEachOptionsSyntaxByName(optionsObject, name, property => isStringLiteral(property.initializer) && property.initializer.text === value ? property.initializer : undefined);
}
/** @internal */
export function forEachOptionsSyntaxByName<T>(optionsObject: ObjectLiteralExpression | undefined, name: string, callback: (prop: PropertyAssignment) => T | undefined): T | undefined {
return forEachPropertyAssignment(optionsObject, name, callback);
}