copilot-swe-agent[bot] 7f169c18f2 Fix compiler logic to check for wildcard correctly
- checker.ts: Check for wildcard before choosing error message
- programDiagnostics.ts: Check for wildcard before returning early
- watch.ts: Check for wildcard to choose correct message
- jsTyping.ts: Use optional chaining for wildcard check
- tscWatch tests: Use wildcard for tests that dynamically install types

These functions should only show "add to types" messages when wildcard is NOT present.

Co-authored-by: RyanCavanaugh <6685088+RyanCavanaugh@users.noreply.github.com>
2026-01-27 19:04:03 +00:00

999 lines
45 KiB
TypeScript

import {
addRange,
BuilderProgram,
CancellationToken,
chainDiagnosticMessages,
CharacterCodes,
combinePaths,
CompilerHost,
CompilerOptions,
contains,
convertToRelativePath,
copyProperties,
countWhere,
createCompilerDiagnostic,
createEmitAndSemanticDiagnosticsBuilderProgram,
createGetCanonicalFileName,
createGetSourceFile,
createIncrementalCompilerHost,
createIncrementalProgram,
CreateProgram,
createWriteFileMeasuringIO,
CustomTransformers,
Debug,
Diagnostic,
DiagnosticAndArguments,
DiagnosticCategory,
DiagnosticMessage,
DiagnosticMessageChain,
DiagnosticReporter,
Diagnostics,
DirectoryStructureHost,
EmitAndSemanticDiagnosticsBuilderProgram,
EmitResult,
emptyArray,
endsWith,
ExitStatus,
ExtendedConfigCacheEntry,
Extension,
externalHelpersModuleNameText,
FileExtensionInfo,
fileExtensionIs,
FileIncludeKind,
FileIncludeReason,
FileWatcher,
filter,
find,
findIndex,
flattenDiagnosticMessageText,
forEach,
ForegroundColorEscapeSequences,
formatColorAndReset,
formatDiagnostic,
FormatDiagnosticsHost,
formatDiagnosticsWithColorAndContext,
generateDjb2Hash,
getDefaultLibFileName,
getDirectoryPath,
getEmitDeclarations,
getEmitScriptTarget,
getImpliedNodeFormatForEmitWorker,
getLineAndCharacterOfPosition,
getNameOfScriptTarget,
getNewLineCharacter,
getNormalizedAbsolutePath,
getParsedCommandLineOfConfigFile,
getPatternFromSpec,
getReferencedFileLocation,
getRegexFromPattern,
getRelativePathFromDirectory,
getWatchFactory,
HasCurrentDirectory,
isExternalOrCommonJsModule,
isLineBreak,
isReferencedFile,
isReferenceFileLocation,
isString,
last,
maxBy,
maybeBind,
memoize,
ModuleKind,
noop,
normalizePath,
packageIdToString,
ParseConfigFileHost,
ParsedCommandLine,
pathIsAbsolute,
Program,
ProgramHost,
ProjectReference,
ReportEmitErrorSummary,
ReportFileInError,
sortAndDeduplicateDiagnostics,
SortedReadonlyArray,
SourceFile,
sourceMapCommentRegExp,
sourceMapCommentRegExpDontCareLineStart,
sys,
System,
WatchCompilerHost,
WatchCompilerHostOfConfigFile,
WatchCompilerHostOfFilesAndCompilerOptions,
WatchFactory,
WatchFactoryHost,
WatchHost,
WatchLogLevel,
WatchOptions,
WatchStatusReporter,
whitespaceOrMapCommentRegExp,
WriteFileCallback,
} from "./_namespaces/ts.js";
const sysFormatDiagnosticsHost: FormatDiagnosticsHost | undefined = sys ? {
getCurrentDirectory: () => sys.getCurrentDirectory(),
getNewLine: () => sys.newLine,
getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames),
} : undefined;
/**
* Create a function that reports error by writing to the system and handles the formatting of the diagnostic
*
* @internal
*/
export function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter {
const host: FormatDiagnosticsHost = system === sys && sysFormatDiagnosticsHost ? sysFormatDiagnosticsHost : {
getCurrentDirectory: () => system.getCurrentDirectory(),
getNewLine: () => system.newLine,
getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames),
};
if (!pretty) {
return diagnostic => system.write(formatDiagnostic(diagnostic, host));
}
const diagnostics: Diagnostic[] = new Array(1);
return diagnostic => {
diagnostics[0] = diagnostic;
system.write(formatDiagnosticsWithColorAndContext(diagnostics, host) + host.getNewLine());
diagnostics[0] = undefined!; // TODO: GH#18217
};
}
/**
* @returns Whether the screen was cleared.
*/
function clearScreenIfNotWatchingForFileChanges(system: System, diagnostic: Diagnostic, options: CompilerOptions): boolean {
if (
system.clearScreen &&
!options.preserveWatchOutput &&
!options.extendedDiagnostics &&
!options.diagnostics &&
contains(screenStartingMessageCodes, diagnostic.code)
) {
system.clearScreen();
return true;
}
return false;
}
const screenStartingMessageCodes: number[] = [
Diagnostics.Starting_compilation_in_watch_mode.code,
Diagnostics.File_change_detected_Starting_incremental_compilation.code,
];
function getPlainDiagnosticFollowingNewLines(diagnostic: Diagnostic, newLine: string): string {
return contains(screenStartingMessageCodes, diagnostic.code)
? newLine + newLine
: newLine;
}
/**
* Get locale specific time based on whether we are in test mode
*
* @internal
*/
export function getLocaleTimeString(system: System): string {
return !system.now ?
new Date().toLocaleTimeString() :
// On some systems / builds of Node, there's a non-breaking space between the time and AM/PM.
// This branch is solely for testing, so just switch it to a normal space for baseline stability.
// See:
// - https://github.com/nodejs/node/issues/45171
// - https://github.com/nodejs/node/issues/45753
system.now().toLocaleTimeString("en-US", { timeZone: "UTC" }).replace("\u202f", " ");
}
/**
* Create a function that reports watch status by writing to the system and handles the formatting of the diagnostic
*
* @internal
*/
export function createWatchStatusReporter(system: System, pretty?: boolean): WatchStatusReporter {
return pretty ?
(diagnostic, newLine, options) => {
clearScreenIfNotWatchingForFileChanges(system, diagnostic, options);
let output = `[${formatColorAndReset(getLocaleTimeString(system), ForegroundColorEscapeSequences.Grey)}] `;
output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${newLine + newLine}`;
system.write(output);
} :
(diagnostic, newLine, options) => {
let output = "";
if (!clearScreenIfNotWatchingForFileChanges(system, diagnostic, options)) {
output += newLine;
}
output += `${getLocaleTimeString(system)} - `;
output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${getPlainDiagnosticFollowingNewLines(diagnostic, newLine)}`;
system.write(output);
};
}
/**
* Parses config file using System interface
*
* @internal
*/
export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, extendedConfigCache: Map<string, ExtendedConfigCacheEntry> | undefined, watchOptionsToExtend: WatchOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter): ParsedCommandLine | undefined {
const host: ParseConfigFileHost = system as any;
host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic);
const result = getParsedCommandLineOfConfigFile(configFileName, optionsToExtend, host, extendedConfigCache, watchOptionsToExtend);
host.onUnRecoverableConfigFileDiagnostic = undefined!; // TODO: GH#18217
return result;
}
/** @internal */
export function getErrorCountForSummary(diagnostics: readonly Diagnostic[]): number {
return countWhere(diagnostics, diagnostic => diagnostic.category === DiagnosticCategory.Error);
}
/** @internal */
export function getFilesInErrorForSummary(diagnostics: readonly Diagnostic[]): (ReportFileInError | undefined)[] {
const filesInError = filter(diagnostics, diagnostic => diagnostic.category === DiagnosticCategory.Error)
.map(
errorDiagnostic => {
if (errorDiagnostic.file === undefined) return;
return `${errorDiagnostic.file.fileName}`;
},
);
return filesInError.map(fileName => {
if (fileName === undefined) {
return undefined;
}
const diagnosticForFileName = find(diagnostics, diagnostic => diagnostic.file !== undefined && diagnostic.file.fileName === fileName);
if (diagnosticForFileName !== undefined) {
const { line } = getLineAndCharacterOfPosition(diagnosticForFileName.file!, diagnosticForFileName.start!);
return {
fileName,
line: line + 1,
};
}
});
}
/** @internal */
export function getWatchErrorSummaryDiagnosticMessage(errorCount: number): DiagnosticMessage {
return errorCount === 1 ?
Diagnostics.Found_1_error_Watching_for_file_changes :
Diagnostics.Found_0_errors_Watching_for_file_changes;
}
function prettyPathForFileError(error: ReportFileInError, cwd: string) {
const line = formatColorAndReset(":" + error.line, ForegroundColorEscapeSequences.Grey);
if (pathIsAbsolute(error.fileName) && pathIsAbsolute(cwd)) {
return getRelativePathFromDirectory(cwd, error.fileName, /*ignoreCase*/ false) + line;
}
return error.fileName + line;
}
/** @internal */
export function getErrorSummaryText(
errorCount: number,
filesInError: readonly (ReportFileInError | undefined)[],
newLine: string,
host: HasCurrentDirectory,
): string {
if (errorCount === 0) return "";
const nonNilFiles = filesInError.filter(fileInError => fileInError !== undefined);
const distinctFileNamesWithLines = nonNilFiles.map(fileInError => `${fileInError.fileName}:${fileInError.line}`)
.filter((value, index, self) => self.indexOf(value) === index);
const firstFileReference = nonNilFiles[0] && prettyPathForFileError(nonNilFiles[0], host.getCurrentDirectory());
let messageAndArgs: DiagnosticAndArguments;
if (errorCount === 1) {
messageAndArgs = filesInError[0] !== undefined ? [Diagnostics.Found_1_error_in_0, firstFileReference] : [Diagnostics.Found_1_error];
}
else {
messageAndArgs = distinctFileNamesWithLines.length === 0 ? [Diagnostics.Found_0_errors, errorCount] :
distinctFileNamesWithLines.length === 1 ? [Diagnostics.Found_0_errors_in_the_same_file_starting_at_Colon_1, errorCount, firstFileReference] :
[Diagnostics.Found_0_errors_in_1_files, errorCount, distinctFileNamesWithLines.length];
}
const d = createCompilerDiagnostic(...messageAndArgs);
const suffix = distinctFileNamesWithLines.length > 1 ? createTabularErrorsDisplay(nonNilFiles, host) : "";
return `${newLine}${flattenDiagnosticMessageText(d.messageText, newLine)}${newLine}${newLine}${suffix}`;
}
function createTabularErrorsDisplay(filesInError: (ReportFileInError | undefined)[], host: HasCurrentDirectory) {
const distinctFiles = filesInError.filter((value, index, self) => index === self.findIndex(file => file?.fileName === value?.fileName));
if (distinctFiles.length === 0) return "";
const numberLength = (num: number) => Math.log(num) * Math.LOG10E + 1;
const fileToErrorCount = distinctFiles.map(file => ([file, countWhere(filesInError, fileInError => fileInError!.fileName === file!.fileName)] as const));
const maxErrors = maxBy(fileToErrorCount, 0, value => value[1]);
const headerRow = Diagnostics.Errors_Files.message;
const leftColumnHeadingLength = headerRow.split(" ")[0].length;
const leftPaddingGoal = Math.max(leftColumnHeadingLength, numberLength(maxErrors));
const headerPadding = Math.max(numberLength(maxErrors) - leftColumnHeadingLength, 0);
let tabularData = "";
tabularData += " ".repeat(headerPadding) + headerRow + "\n";
fileToErrorCount.forEach(row => {
const [file, errorCount] = row;
const errorCountDigitsLength = Math.log(errorCount) * Math.LOG10E + 1 | 0;
const leftPadding = errorCountDigitsLength < leftPaddingGoal ?
" ".repeat(leftPaddingGoal - errorCountDigitsLength)
: "";
const fileRef = prettyPathForFileError(file!, host.getCurrentDirectory());
tabularData += `${leftPadding}${errorCount} ${fileRef}\n`;
});
return tabularData;
}
/** @internal */
export function isBuilderProgram<T extends BuilderProgram>(program: Program | BuilderProgram): program is T {
return !!(program as T).state;
}
function listFiles<T extends BuilderProgram>(program: Program | T, write: (s: string) => void) {
const options = program.getCompilerOptions();
if (options.explainFiles) {
explainFiles(isBuilderProgram(program) ? program.getProgram() : program, write);
}
else if (options.listFiles || options.listFilesOnly) {
forEach(program.getSourceFiles(), file => {
write(file.fileName);
});
}
}
/** @internal */
export function explainFiles(program: Program, write: (s: string) => void): void {
const reasons = program.getFileIncludeReasons();
const relativeFileName = (fileName: string) => convertToRelativePath(fileName, program.getCurrentDirectory(), program.getCanonicalFileName);
for (const file of program.getSourceFiles()) {
write(`${toFileName(file, relativeFileName)}`);
reasons.get(file.path)?.forEach(reason => write(` ${fileIncludeReasonToDiagnostics(program, reason, relativeFileName).messageText}`));
explainIfFileIsRedirectAndImpliedFormat(file, program.getCompilerOptionsForFile(file), relativeFileName)?.forEach(d => write(` ${d.messageText}`));
}
}
/** @internal */
export function explainIfFileIsRedirectAndImpliedFormat(
file: SourceFile,
options: CompilerOptions,
fileNameConvertor?: (fileName: string) => string,
): DiagnosticMessageChain[] | undefined {
let result: DiagnosticMessageChain[] | undefined;
if (file.path !== file.resolvedPath) {
(result ??= []).push(chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.File_is_output_of_project_reference_source_0,
toFileName(file.originalFileName, fileNameConvertor),
));
}
if (file.redirectInfo) {
(result ??= []).push(chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.File_redirects_to_file_0,
toFileName(file.redirectInfo.redirectTarget, fileNameConvertor),
));
}
if (isExternalOrCommonJsModule(file)) {
switch (getImpliedNodeFormatForEmitWorker(file, options)) {
case ModuleKind.ESNext:
if (file.packageJsonScope) {
(result ??= []).push(chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.File_is_ECMAScript_module_because_0_has_field_type_with_value_module,
toFileName(last(file.packageJsonLocations!), fileNameConvertor),
));
}
break;
case ModuleKind.CommonJS:
if (file.packageJsonScope) {
(result ??= []).push(chainDiagnosticMessages(
/*details*/ undefined,
file.packageJsonScope.contents.packageJsonContent.type ?
Diagnostics.File_is_CommonJS_module_because_0_has_field_type_whose_value_is_not_module :
Diagnostics.File_is_CommonJS_module_because_0_does_not_have_field_type,
toFileName(last(file.packageJsonLocations!), fileNameConvertor),
));
}
else if (file.packageJsonLocations?.length) {
(result ??= []).push(chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.File_is_CommonJS_module_because_package_json_was_not_found,
));
}
break;
}
}
return result;
}
/** @internal */
export function getMatchedFileSpec(program: Program, fileName: string): string | undefined {
const configFile = program.getCompilerOptions().configFile;
if (!configFile?.configFileSpecs?.validatedFilesSpec) return undefined;
const filePath = program.getCanonicalFileName(fileName);
const basePath = getDirectoryPath(getNormalizedAbsolutePath(configFile.fileName, program.getCurrentDirectory()));
const index = findIndex(configFile.configFileSpecs.validatedFilesSpec, fileSpec => program.getCanonicalFileName(getNormalizedAbsolutePath(fileSpec, basePath)) === filePath);
return index !== -1 ? configFile.configFileSpecs.validatedFilesSpecBeforeSubstitution![index] : undefined;
}
/** @internal */
export function getMatchedIncludeSpec(program: Program, fileName: string): string | true | undefined {
const configFile = program.getCompilerOptions().configFile;
if (!configFile?.configFileSpecs?.validatedIncludeSpecs) return undefined;
// Return true if its default include spec
if (configFile.configFileSpecs.isDefaultIncludeSpec) return true;
const isJsonFile = fileExtensionIs(fileName, Extension.Json);
const basePath = getDirectoryPath(getNormalizedAbsolutePath(configFile.fileName, program.getCurrentDirectory()));
const useCaseSensitiveFileNames = program.useCaseSensitiveFileNames();
const index = findIndex(configFile?.configFileSpecs?.validatedIncludeSpecs, includeSpec => {
if (isJsonFile && !endsWith(includeSpec, Extension.Json)) return false;
const pattern = getPatternFromSpec(includeSpec, basePath, "files");
return !!pattern && getRegexFromPattern(`(?:${pattern})$`, useCaseSensitiveFileNames).test(fileName);
});
return index !== -1 ? configFile.configFileSpecs.validatedIncludeSpecsBeforeSubstitution![index] : undefined;
}
/** @internal */
export function fileIncludeReasonToDiagnostics(program: Program, reason: FileIncludeReason, fileNameConvertor?: (fileName: string) => string): DiagnosticMessageChain {
const options = program.getCompilerOptions();
if (isReferencedFile(reason)) {
const referenceLocation = getReferencedFileLocation(program, reason);
const referenceText = isReferenceFileLocation(referenceLocation) ? referenceLocation.file.text.substring(referenceLocation.pos, referenceLocation.end) : `"${referenceLocation.text}"`;
let message: DiagnosticMessage;
Debug.assert(isReferenceFileLocation(referenceLocation) || reason.kind === FileIncludeKind.Import, "Only synthetic references are imports");
switch (reason.kind) {
case FileIncludeKind.Import:
if (isReferenceFileLocation(referenceLocation)) {
message = referenceLocation.packageId ?
Diagnostics.Imported_via_0_from_file_1_with_packageId_2 :
Diagnostics.Imported_via_0_from_file_1;
}
else if (referenceLocation.text === externalHelpersModuleNameText) {
message = referenceLocation.packageId ?
Diagnostics.Imported_via_0_from_file_1_with_packageId_2_to_import_importHelpers_as_specified_in_compilerOptions :
Diagnostics.Imported_via_0_from_file_1_to_import_importHelpers_as_specified_in_compilerOptions;
}
else {
message = referenceLocation.packageId ?
Diagnostics.Imported_via_0_from_file_1_with_packageId_2_to_import_jsx_and_jsxs_factory_functions :
Diagnostics.Imported_via_0_from_file_1_to_import_jsx_and_jsxs_factory_functions;
}
break;
case FileIncludeKind.ReferenceFile:
Debug.assert(!referenceLocation.packageId);
message = Diagnostics.Referenced_via_0_from_file_1;
break;
case FileIncludeKind.TypeReferenceDirective:
message = referenceLocation.packageId ?
Diagnostics.Type_library_referenced_via_0_from_file_1_with_packageId_2 :
Diagnostics.Type_library_referenced_via_0_from_file_1;
break;
case FileIncludeKind.LibReferenceDirective:
Debug.assert(!referenceLocation.packageId);
message = Diagnostics.Library_referenced_via_0_from_file_1;
break;
default:
Debug.assertNever(reason);
}
return chainDiagnosticMessages(
/*details*/ undefined,
message,
referenceText,
toFileName(referenceLocation.file, fileNameConvertor),
(referenceLocation.packageId && packageIdToString(referenceLocation.packageId))!,
);
}
switch (reason.kind) {
case FileIncludeKind.RootFile:
if (!options.configFile?.configFileSpecs) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Root_file_specified_for_compilation);
const fileName = getNormalizedAbsolutePath(program.getRootFileNames()[reason.index], program.getCurrentDirectory());
const matchedByFiles = getMatchedFileSpec(program, fileName);
if (matchedByFiles) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Part_of_files_list_in_tsconfig_json);
const matchedByInclude = getMatchedIncludeSpec(program, fileName);
return isString(matchedByInclude) ?
chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Matched_by_include_pattern_0_in_1,
matchedByInclude,
toFileName(options.configFile, fileNameConvertor),
) :
// Could be additional files specified as roots or matched by default include
chainDiagnosticMessages(
/*details*/ undefined,
matchedByInclude ?
Diagnostics.Matched_by_default_include_pattern_Asterisk_Asterisk_Slash_Asterisk :
Diagnostics.Root_file_specified_for_compilation,
);
case FileIncludeKind.SourceFromProjectReference:
case FileIncludeKind.OutputFromProjectReference:
const isOutput = reason.kind === FileIncludeKind.OutputFromProjectReference;
const referencedResolvedRef = Debug.checkDefined(program.getResolvedProjectReferences()?.[reason.index]);
return chainDiagnosticMessages(
/*details*/ undefined,
options.outFile ?
isOutput ?
Diagnostics.Output_from_referenced_project_0_included_because_1_specified :
Diagnostics.Source_from_referenced_project_0_included_because_1_specified :
isOutput ?
Diagnostics.Output_from_referenced_project_0_included_because_module_is_specified_as_none :
Diagnostics.Source_from_referenced_project_0_included_because_module_is_specified_as_none,
toFileName(referencedResolvedRef.sourceFile.fileName, fileNameConvertor),
options.outFile ? "--outFile" : "--out",
);
case FileIncludeKind.AutomaticTypeDirectiveFile: {
const messageAndArgs: DiagnosticAndArguments = options.types?.includes("*") ?
reason.packageId ?
[Diagnostics.Entry_point_for_implicit_type_library_0_with_packageId_1, reason.typeReference, packageIdToString(reason.packageId)] :
[Diagnostics.Entry_point_for_implicit_type_library_0, reason.typeReference] :
reason.packageId ?
[Diagnostics.Entry_point_of_type_library_0_specified_in_compilerOptions_with_packageId_1, reason.typeReference, packageIdToString(reason.packageId)] :
[Diagnostics.Entry_point_of_type_library_0_specified_in_compilerOptions, reason.typeReference];
return chainDiagnosticMessages(/*details*/ undefined, ...messageAndArgs);
}
case FileIncludeKind.LibFile: {
if (reason.index !== undefined) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Library_0_specified_in_compilerOptions, options.lib![reason.index]);
const target = getNameOfScriptTarget(getEmitScriptTarget(options));
const messageAndArgs: DiagnosticAndArguments = target ? [Diagnostics.Default_library_for_target_0, target] : [Diagnostics.Default_library];
return chainDiagnosticMessages(/*details*/ undefined, ...messageAndArgs);
}
default:
Debug.assertNever(reason);
}
}
function toFileName(file: SourceFile | string, fileNameConvertor?: (fileName: string) => string) {
const fileName = isString(file) ? file : file.fileName;
return fileNameConvertor ? fileNameConvertor(fileName) : fileName;
}
/**
* Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options
*
* @internal
*/
export function emitFilesAndReportErrors<T extends BuilderProgram>(
program: Program | T,
reportDiagnostic: DiagnosticReporter,
write?: (s: string) => void,
reportSummary?: ReportEmitErrorSummary,
writeFile?: WriteFileCallback,
cancellationToken?: CancellationToken,
emitOnlyDtsFiles?: boolean,
customTransformers?: CustomTransformers,
): {
emitResult: EmitResult;
diagnostics: SortedReadonlyArray<Diagnostic>;
} {
const options = program.getCompilerOptions();
// First get and report any syntactic errors.
const allDiagnostics = program.getConfigFileParsingDiagnostics().slice();
const configFileParsingDiagnosticsLength = allDiagnostics.length;
addRange(allDiagnostics, program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken));
// If we didn't have any syntactic errors, then also try getting the global and
// semantic errors.
if (allDiagnostics.length === configFileParsingDiagnosticsLength) {
addRange(allDiagnostics, program.getOptionsDiagnostics(cancellationToken));
if (!options.listFilesOnly) {
addRange(allDiagnostics, program.getGlobalDiagnostics(cancellationToken));
if (allDiagnostics.length === configFileParsingDiagnosticsLength) {
addRange(allDiagnostics, program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken));
}
if (
options.noEmit &&
getEmitDeclarations(options) &&
allDiagnostics.length === configFileParsingDiagnosticsLength
) {
addRange(allDiagnostics, program.getDeclarationDiagnostics(/*sourceFile*/ undefined, cancellationToken));
}
}
}
// Emit and report any errors we ran into.
const emitResult = options.listFilesOnly
? { emitSkipped: true, diagnostics: emptyArray }
: program.emit(/*targetSourceFile*/ undefined, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
addRange(allDiagnostics, emitResult.diagnostics);
const diagnostics = sortAndDeduplicateDiagnostics(allDiagnostics);
diagnostics.forEach(reportDiagnostic);
if (write) {
const currentDir = program.getCurrentDirectory();
forEach(emitResult.emittedFiles, file => {
const filepath = getNormalizedAbsolutePath(file, currentDir);
write(`TSFILE: ${filepath}`);
});
listFiles(program, write);
}
if (reportSummary) {
reportSummary(getErrorCountForSummary(diagnostics), getFilesInErrorForSummary(diagnostics));
}
return {
emitResult,
diagnostics,
};
}
/** @internal */
export function emitFilesAndReportErrorsAndGetExitStatus<T extends BuilderProgram>(
program: Program | T,
reportDiagnostic: DiagnosticReporter,
write?: (s: string) => void,
reportSummary?: ReportEmitErrorSummary,
writeFile?: WriteFileCallback,
cancellationToken?: CancellationToken,
emitOnlyDtsFiles?: boolean,
customTransformers?: CustomTransformers,
): ExitStatus {
const { emitResult, diagnostics } = emitFilesAndReportErrors(
program,
reportDiagnostic,
write,
reportSummary,
writeFile,
cancellationToken,
emitOnlyDtsFiles,
customTransformers,
);
if (emitResult.emitSkipped && diagnostics.length > 0) {
// If the emitter didn't emit anything, then pass that value along.
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
}
else if (diagnostics.length > 0) {
// The emitter emitted something, inform the caller if that happened in the presence
// of diagnostics or not.
return ExitStatus.DiagnosticsPresent_OutputsGenerated;
}
return ExitStatus.Success;
}
/** @internal */
export const noopFileWatcher: FileWatcher = { close: noop };
/** @internal */
export const returnNoopFileWatcher = (): FileWatcher => noopFileWatcher;
/** @internal */
export function createWatchHost(system: System = sys, reportWatchStatus?: WatchStatusReporter): WatchHost {
const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system);
return {
onWatchStatusChange,
watchFile: maybeBind(system, system.watchFile) || returnNoopFileWatcher,
watchDirectory: maybeBind(system, system.watchDirectory) || returnNoopFileWatcher,
setTimeout: maybeBind(system, system.setTimeout) || noop,
clearTimeout: maybeBind(system, system.clearTimeout) || noop,
preferNonRecursiveWatch: system.preferNonRecursiveWatch,
};
}
/** @internal */
export type WatchType = WatchTypeRegistry[keyof WatchTypeRegistry];
/** @internal */
export const WatchType: WatchTypeRegistry = {
ConfigFile: "Config file",
ExtendedConfigFile: "Extended config file",
SourceFile: "Source file",
MissingFile: "Missing file",
WildcardDirectory: "Wild card directory",
FailedLookupLocations: "Failed Lookup Locations",
AffectingFileLocation: "File location affecting resolution",
TypeRoots: "Type roots",
ConfigFileOfReferencedProject: "Config file of referened project",
ExtendedConfigOfReferencedProject: "Extended config file of referenced project",
WildcardDirectoryOfReferencedProject: "Wild card directory of referenced project",
PackageJson: "package.json file",
ClosedScriptInfo: "Closed Script info",
ConfigFileForInferredRoot: "Config file for the inferred project root",
NodeModules: "node_modules for closed script infos and package.jsons affecting module specifier cache",
MissingSourceMapFile: "Missing source map file",
NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root",
MissingGeneratedFile: "Missing generated file",
NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation",
TypingInstallerLocationFile: "File location for typing installer",
TypingInstallerLocationDirectory: "Directory location for typing installer",
};
/** @internal */
export interface WatchTypeRegistry {
ConfigFile: "Config file";
ExtendedConfigFile: "Extended config file";
SourceFile: "Source file";
MissingFile: "Missing file";
WildcardDirectory: "Wild card directory";
FailedLookupLocations: "Failed Lookup Locations";
AffectingFileLocation: "File location affecting resolution";
TypeRoots: "Type roots";
ConfigFileOfReferencedProject: "Config file of referened project";
ExtendedConfigOfReferencedProject: "Extended config file of referenced project";
WildcardDirectoryOfReferencedProject: "Wild card directory of referenced project";
PackageJson: "package.json file";
// Additional tsserver specific watch information
ClosedScriptInfo: "Closed Script info";
ConfigFileForInferredRoot: "Config file for the inferred project root";
NodeModules: "node_modules for closed script infos and package.jsons affecting module specifier cache";
MissingSourceMapFile: "Missing source map file";
NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root";
MissingGeneratedFile: "Missing generated file";
NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation";
TypingInstallerLocationFile: "File location for typing installer";
TypingInstallerLocationDirectory: "Directory location for typing installer";
}
/** @internal */
export interface WatchFactoryWithLog<X, Y = undefined> extends WatchFactory<X, Y> {
writeLog: (s: string) => void;
}
/** @internal */
export function createWatchFactory<Y = undefined>(host: WatchFactoryHost & { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }): WatchFactoryWithLog<WatchType, Y> {
const watchLogLevel = host.trace ? options.extendedDiagnostics ? WatchLogLevel.Verbose : options.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None;
const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => host.trace!(s)) : noop;
const result = getWatchFactory<WatchType, Y>(host, watchLogLevel, writeLog) as WatchFactoryWithLog<WatchType, Y>;
result.writeLog = writeLog;
return result;
}
/** @internal */
export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCompilerOptions: () => CompilerOptions, directoryStructureHost: DirectoryStructureHost = host): CompilerHost {
const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames();
const compilerHost: CompilerHost = {
getSourceFile: createGetSourceFile(
(fileName, encoding) => !encoding ? compilerHost.readFile(fileName) : host.readFile(fileName, encoding),
/*setParentNodes*/ undefined,
),
getDefaultLibLocation: maybeBind(host, host.getDefaultLibLocation),
getDefaultLibFileName: options => host.getDefaultLibFileName(options),
writeFile: createWriteFileMeasuringIO(
(path, data, writeByteOrderMark) => host.writeFile!(path, data, writeByteOrderMark),
path => host.createDirectory!(path),
path => host.directoryExists!(path),
),
getCurrentDirectory: memoize(() => host.getCurrentDirectory()),
useCaseSensitiveFileNames: () => useCaseSensitiveFileNames,
getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames),
getNewLine: () => getNewLineCharacter(getCompilerOptions()),
fileExists: f => host.fileExists(f),
readFile: f => host.readFile(f),
trace: maybeBind(host, host.trace),
directoryExists: maybeBind(directoryStructureHost, directoryStructureHost.directoryExists),
getDirectories: maybeBind(directoryStructureHost, directoryStructureHost.getDirectories),
realpath: maybeBind(host, host.realpath),
getEnvironmentVariable: maybeBind(host, host.getEnvironmentVariable) || (() => ""),
createHash: maybeBind(host, host.createHash),
readDirectory: maybeBind(host, host.readDirectory),
storeSignatureInfo: host.storeSignatureInfo,
jsDocParsingMode: host.jsDocParsingMode,
};
return compilerHost;
}
/** @internal */
export function getSourceFileVersionAsHashFromText(host: Pick<CompilerHost, "createHash">, text: string): string {
// If text can contain the sourceMapUrl ignore sourceMapUrl for calcualting hash
if (text.match(sourceMapCommentRegExpDontCareLineStart)) {
let lineEnd = text.length;
let lineStart = lineEnd;
for (let pos = lineEnd - 1; pos >= 0; pos--) {
const ch = text.charCodeAt(pos);
switch (ch) {
case CharacterCodes.lineFeed:
if (pos && text.charCodeAt(pos - 1) === CharacterCodes.carriageReturn) {
pos--;
}
// falls through
case CharacterCodes.carriageReturn:
break;
default:
if (ch < CharacterCodes.maxAsciiCharacter || !isLineBreak(ch)) {
lineStart = pos;
continue;
}
break;
}
// This is start of the line
const line = text.substring(lineStart, lineEnd);
if (line.match(sourceMapCommentRegExp)) {
text = text.substring(0, lineStart);
break;
}
// If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file
else if (!line.match(whitespaceOrMapCommentRegExp)) {
break;
}
lineEnd = lineStart;
}
}
return (host.createHash || generateDjb2Hash)(text);
}
/** @internal */
export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost): void {
const originalGetSourceFile = compilerHost.getSourceFile;
compilerHost.getSourceFile = (...args) => {
const result = originalGetSourceFile.call(compilerHost, ...args);
if (result) {
result.version = getSourceFileVersionAsHashFromText(compilerHost, result.text);
}
return result;
};
}
/**
* Creates the watch compiler host that can be extended with config file or root file names and options host
*
* @internal
*/
export function createProgramHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined): ProgramHost<T> {
const getDefaultLibLocation = memoize(() => getDirectoryPath(normalizePath(system.getExecutingFilePath())));
return {
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
getNewLine: () => system.newLine,
getCurrentDirectory: memoize(() => system.getCurrentDirectory()),
getDefaultLibLocation,
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
fileExists: path => system.fileExists(path),
readFile: (path, encoding) => system.readFile(path, encoding),
directoryExists: path => system.directoryExists(path),
getDirectories: path => system.getDirectories(path),
readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth),
realpath: maybeBind(system, system.realpath),
getEnvironmentVariable: maybeBind(system, system.getEnvironmentVariable),
trace: s => system.write(s + system.newLine),
createDirectory: path => system.createDirectory(path),
writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark),
createHash: maybeBind(system, system.createHash),
createProgram: createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>,
storeSignatureInfo: system.storeSignatureInfo,
now: maybeBind(system, system.now),
};
}
/**
* Creates the watch compiler host that can be extended with config file or root file names and options host
*/
function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> {
const write = (s: string) => system.write(s + system.newLine);
const result = createProgramHost(system, createProgram) as WatchCompilerHost<T>;
copyProperties(result, createWatchHost(system, reportWatchStatus));
result.afterProgramCreate = builderProgram => {
const compilerOptions = builderProgram.getCompilerOptions();
const newLine = getNewLineCharacter(compilerOptions);
emitFilesAndReportErrors(
builderProgram,
reportDiagnostic,
write,
errorCount =>
result.onWatchStatusChange!(
createCompilerDiagnostic(getWatchErrorSummaryDiagnosticMessage(errorCount), errorCount),
newLine,
compilerOptions,
errorCount,
),
);
};
return result;
}
/**
* Report error and exit
*/
function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) {
reportDiagnostic(diagnostic);
system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
/** @internal */
export interface CreateWatchCompilerHostInput<T extends BuilderProgram> {
system: System;
createProgram?: CreateProgram<T>;
reportDiagnostic?: DiagnosticReporter;
reportWatchStatus?: WatchStatusReporter;
}
/** @internal */
export interface CreateWatchCompilerHostOfConfigFileInput<T extends BuilderProgram> extends CreateWatchCompilerHostInput<T> {
configFileName: string;
optionsToExtend?: CompilerOptions;
watchOptionsToExtend?: WatchOptions;
extraFileExtensions?: readonly FileExtensionInfo[];
}
/**
* Creates the watch compiler host from system for config file in watch mode
*
* @internal
*/
export function createWatchCompilerHostOfConfigFile<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
configFileName,
optionsToExtend,
watchOptionsToExtend,
extraFileExtensions,
system,
createProgram,
reportDiagnostic,
reportWatchStatus,
}: CreateWatchCompilerHostOfConfigFileInput<T>): WatchCompilerHostOfConfigFile<T> {
const diagnosticReporter = reportDiagnostic || createDiagnosticReporter(system);
const host = createWatchCompilerHost(system, createProgram, diagnosticReporter, reportWatchStatus) as WatchCompilerHostOfConfigFile<T>;
host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, diagnosticReporter, diagnostic);
host.configFileName = configFileName;
host.optionsToExtend = optionsToExtend;
host.watchOptionsToExtend = watchOptionsToExtend;
host.extraFileExtensions = extraFileExtensions;
return host;
}
/** @internal */
export interface CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T extends BuilderProgram> extends CreateWatchCompilerHostInput<T> {
rootFiles: string[];
options: CompilerOptions;
watchOptions: WatchOptions | undefined;
projectReferences?: readonly ProjectReference[];
}
/**
* Creates the watch compiler host from system for compiling root files and options in watch mode
*
* @internal
*/
export function createWatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({
rootFiles,
options,
watchOptions,
projectReferences,
system,
createProgram,
reportDiagnostic,
reportWatchStatus,
}: CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T>): WatchCompilerHostOfFilesAndCompilerOptions<T> {
const host = createWatchCompilerHost(system, createProgram, reportDiagnostic || createDiagnosticReporter(system), reportWatchStatus) as WatchCompilerHostOfFilesAndCompilerOptions<T>;
host.rootFiles = rootFiles;
host.options = options;
host.watchOptions = watchOptions;
host.projectReferences = projectReferences;
return host;
}
/** @internal */
export interface IncrementalCompilationOptions {
rootNames: readonly string[];
options: CompilerOptions;
configFileParsingDiagnostics?: readonly Diagnostic[];
projectReferences?: readonly ProjectReference[];
host?: CompilerHost;
reportDiagnostic?: DiagnosticReporter;
reportErrorSummary?: ReportEmitErrorSummary;
afterProgramEmitAndDiagnostics?(program: EmitAndSemanticDiagnosticsBuilderProgram): void;
system?: System;
}
/** @internal */
export function performIncrementalCompilation(input: IncrementalCompilationOptions): ExitStatus {
const system = input.system || sys;
const host = input.host || (input.host = createIncrementalCompilerHost(input.options, system));
const builderProgram = createIncrementalProgram(input);
const exitStatus = emitFilesAndReportErrorsAndGetExitStatus(
builderProgram,
input.reportDiagnostic || createDiagnosticReporter(system),
s => host.trace && host.trace(s),
input.reportErrorSummary || input.options.pretty ? (errorCount, filesInError) => system.write(getErrorSummaryText(errorCount, filesInError, system.newLine, host)) : undefined,
);
if (input.afterProgramEmitAndDiagnostics) input.afterProgramEmitAndDiagnostics(builderProgram);
return exitStatus;
}