1/*@internal*/ 2namespace ts { 3 const sysFormatDiagnosticsHost: FormatDiagnosticsHost | undefined = sys ? { 4 getCurrentDirectory: () => sys.getCurrentDirectory(), 5 getNewLine: () => sys.newLine, 6 getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames) 7 } : undefined; 8 9 /** 10 * Create a function that reports error by writing to the system and handles the formatting of the diagnostic 11 */ 12 export function createDiagnosticReporter(system: System, pretty?: boolean): DiagnosticReporter { 13 const host: FormatDiagnosticsHost = system === sys && sysFormatDiagnosticsHost ? sysFormatDiagnosticsHost : { 14 getCurrentDirectory: () => system.getCurrentDirectory(), 15 getNewLine: () => system.newLine, 16 getCanonicalFileName: createGetCanonicalFileName(system.useCaseSensitiveFileNames), 17 }; 18 if (!pretty) { 19 return diagnostic => system.write(formatDiagnostic(diagnostic, host)); 20 } 21 22 const diagnostics: Diagnostic[] = new Array(1); 23 return diagnostic => { 24 diagnostics[0] = diagnostic; 25 system.write(formatDiagnosticsWithColorAndContext(diagnostics, host) + host.getNewLine()); 26 diagnostics[0] = undefined!; // TODO: GH#18217 27 }; 28 } 29 30 /** 31 * @returns Whether the screen was cleared. 32 */ 33 function clearScreenIfNotWatchingForFileChanges(system: System, diagnostic: Diagnostic, options: CompilerOptions): boolean { 34 if (system.clearScreen && 35 !options.preserveWatchOutput && 36 !options.extendedDiagnostics && 37 !options.diagnostics && 38 contains(screenStartingMessageCodes, diagnostic.code)) { 39 system.clearScreen(); 40 return true; 41 } 42 43 return false; 44 } 45 46 export const screenStartingMessageCodes: number[] = [ 47 Diagnostics.Starting_compilation_in_watch_mode.code, 48 Diagnostics.File_change_detected_Starting_incremental_compilation.code, 49 ]; 50 51 function getPlainDiagnosticFollowingNewLines(diagnostic: Diagnostic, newLine: string): string { 52 return contains(screenStartingMessageCodes, diagnostic.code) 53 ? newLine + newLine 54 : newLine; 55 } 56 57 /** 58 * Get locale specific time based on whether we are in test mode 59 */ 60 export function getLocaleTimeString(system: System) { 61 return !system.now ? 62 new Date().toLocaleTimeString() : 63 // On some systems / builds of Node, there's a non-breaking space between the time and AM/PM. 64 // This branch is solely for testing, so just switch it to a normal space for baseline stability. 65 // See: 66 // - https://github.com/nodejs/node/issues/45171 67 // - https://github.com/nodejs/node/issues/45753 68 system.now().toLocaleTimeString("en-US", { timeZone: "UTC" }).replace("\u202f", " "); 69 } 70 71 /** 72 * Create a function that reports watch status by writing to the system and handles the formatting of the diagnostic 73 */ 74 export function createWatchStatusReporter(system: System, pretty?: boolean): WatchStatusReporter { 75 return pretty ? 76 (diagnostic, newLine, options) => { 77 clearScreenIfNotWatchingForFileChanges(system, diagnostic, options); 78 let output = `[${formatColorAndReset(getLocaleTimeString(system), ForegroundColorEscapeSequences.Grey)}] `; 79 output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${newLine + newLine}`; 80 system.write(output); 81 } : 82 (diagnostic, newLine, options) => { 83 let output = ""; 84 85 if (!clearScreenIfNotWatchingForFileChanges(system, diagnostic, options)) { 86 output += newLine; 87 } 88 89 output += `${getLocaleTimeString(system)} - `; 90 output += `${flattenDiagnosticMessageText(diagnostic.messageText, system.newLine)}${getPlainDiagnosticFollowingNewLines(diagnostic, newLine)}`; 91 92 system.write(output); 93 }; 94 } 95 96 /** Parses config file using System interface */ 97 export function parseConfigFileWithSystem(configFileName: string, optionsToExtend: CompilerOptions, extendedConfigCache: Map<ExtendedConfigCacheEntry> | undefined, watchOptionsToExtend: WatchOptions | undefined, system: System, reportDiagnostic: DiagnosticReporter) { 98 const host: ParseConfigFileHost = system as any; 99 host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, reportDiagnostic, diagnostic); 100 const result = getParsedCommandLineOfConfigFile(configFileName, optionsToExtend, host, extendedConfigCache, watchOptionsToExtend); 101 host.onUnRecoverableConfigFileDiagnostic = undefined!; // TODO: GH#18217 102 return result; 103 } 104 105 export function getErrorCountForSummary(diagnostics: readonly Diagnostic[]) { 106 return countWhere(diagnostics, diagnostic => diagnostic.category === DiagnosticCategory.Error); 107 } 108 109 export function getFilesInErrorForSummary(diagnostics: readonly Diagnostic[]): (ReportFileInError | undefined)[] { 110 const filesInError = 111 filter(diagnostics, diagnostic => diagnostic.category === DiagnosticCategory.Error) 112 .map( 113 errorDiagnostic => { 114 if(errorDiagnostic.file === undefined) return; 115 return `${errorDiagnostic.file.fileName}`; 116 }); 117 return filesInError.map((fileName: string) => { 118 const diagnosticForFileName = find(diagnostics, diagnostic => 119 diagnostic.file !== undefined && diagnostic.file.fileName === fileName 120 ); 121 122 if(diagnosticForFileName !== undefined) { 123 const { line } = getLineAndCharacterOfPosition(diagnosticForFileName.file!, diagnosticForFileName.start!); 124 return { 125 fileName, 126 line: line + 1, 127 }; 128 } 129 }); 130 } 131 132 export function getWatchErrorSummaryDiagnosticMessage(errorCount: number) { 133 return errorCount === 1 ? 134 Diagnostics.Found_1_error_Watching_for_file_changes : 135 Diagnostics.Found_0_errors_Watching_for_file_changes; 136 } 137 138 function prettyPathForFileError(error: ReportFileInError, cwd: string) { 139 const line = formatColorAndReset(":" + error.line, ForegroundColorEscapeSequences.Grey); 140 if (pathIsAbsolute(error.fileName) && pathIsAbsolute(cwd)) { 141 return getRelativePathFromDirectory(cwd, error.fileName, /* ignoreCase */ false) + line; 142 } 143 144 return error.fileName + line; 145 } 146 147 export function getErrorSummaryText( 148 errorCount: number, 149 filesInError: readonly (ReportFileInError | undefined)[], 150 newLine: string, 151 host: HasCurrentDirectory 152 ) { 153 if (errorCount === 0) return ""; 154 const nonNilFiles = filesInError.filter(fileInError => fileInError !== undefined); 155 const distinctFileNamesWithLines = nonNilFiles.map(fileInError => `${fileInError!.fileName}:${fileInError!.line}`) 156 .filter((value, index, self) => self.indexOf(value) === index); 157 158 const firstFileReference = nonNilFiles[0] && prettyPathForFileError(nonNilFiles[0], host.getCurrentDirectory()); 159 160 const d = errorCount === 1 ? 161 createCompilerDiagnostic( 162 filesInError[0] !== undefined ? 163 Diagnostics.Found_1_error_in_1 : 164 Diagnostics.Found_1_error, 165 errorCount, 166 firstFileReference) : 167 createCompilerDiagnostic( 168 distinctFileNamesWithLines.length === 0 ? 169 Diagnostics.Found_0_errors : 170 distinctFileNamesWithLines.length === 1 ? 171 Diagnostics.Found_0_errors_in_the_same_file_starting_at_Colon_1 : 172 Diagnostics.Found_0_errors_in_1_files, 173 errorCount, 174 distinctFileNamesWithLines.length === 1 ? firstFileReference : distinctFileNamesWithLines.length); 175 176 const suffix = distinctFileNamesWithLines.length > 1 ? createTabularErrorsDisplay(nonNilFiles, host) : ""; 177 return `${newLine}${flattenDiagnosticMessageText(d.messageText, newLine)}${newLine}${newLine}${suffix}`; 178 } 179 180 function createTabularErrorsDisplay(filesInError: (ReportFileInError | undefined)[], host: HasCurrentDirectory) { 181 const distinctFiles = filesInError.filter((value, index, self) => index === self.findIndex(file => file?.fileName === value?.fileName)); 182 if (distinctFiles.length === 0) return ""; 183 184 const numberLength = (num: number) => Math.log(num) * Math.LOG10E + 1; 185 const fileToErrorCount = distinctFiles.map(file => ([file, countWhere(filesInError, fileInError => fileInError!.fileName === file!.fileName)] as const)); 186 const maxErrors = fileToErrorCount.reduce((acc, value) => Math.max(acc, value[1] || 0), 0); 187 188 const headerRow = Diagnostics.Errors_Files.message; 189 const leftColumnHeadingLength = headerRow.split(" ")[0].length; 190 const leftPaddingGoal = Math.max(leftColumnHeadingLength, numberLength(maxErrors)); 191 const headerPadding = Math.max(numberLength(maxErrors) - leftColumnHeadingLength, 0); 192 193 let tabularData = ""; 194 tabularData += " ".repeat(headerPadding) + headerRow + "\n"; 195 fileToErrorCount.forEach((row) => { 196 const [file, errorCount] = row; 197 const errorCountDigitsLength = Math.log(errorCount) * Math.LOG10E + 1 | 0; 198 const leftPadding = errorCountDigitsLength < leftPaddingGoal ? 199 " ".repeat(leftPaddingGoal - errorCountDigitsLength) 200 : ""; 201 202 const fileRef = prettyPathForFileError(file!, host.getCurrentDirectory()); 203 tabularData += `${leftPadding}${errorCount} ${fileRef}\n`; 204 }); 205 206 return tabularData; 207 } 208 209 export function isBuilderProgram(program: Program | BuilderProgram): program is BuilderProgram { 210 return !!(program as BuilderProgram).getState; 211 } 212 213 export function listFiles<T extends BuilderProgram>(program: Program | T, write: (s: string) => void) { 214 const options = program.getCompilerOptions(); 215 if (options.explainFiles) { 216 explainFiles(isBuilderProgram(program) ? program.getProgram() : program, write); 217 } 218 else if (options.listFiles || options.listFilesOnly) { 219 forEach(program.getSourceFiles(), file => { 220 write(file.fileName); 221 }); 222 } 223 } 224 225 export function explainFiles(program: Program, write: (s: string) => void) { 226 const reasons = program.getFileIncludeReasons(); 227 const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames()); 228 const relativeFileName = (fileName: string) => convertToRelativePath(fileName, program.getCurrentDirectory(), getCanonicalFileName); 229 for (const file of program.getSourceFiles()) { 230 write(`${toFileName(file, relativeFileName)}`); 231 reasons.get(file.path)?.forEach(reason => write(` ${fileIncludeReasonToDiagnostics(program, reason, relativeFileName).messageText}`)); 232 explainIfFileIsRedirectAndImpliedFormat(file, relativeFileName)?.forEach(d => write(` ${d.messageText}`)); 233 } 234 } 235 236 export function explainIfFileIsRedirectAndImpliedFormat( 237 file: SourceFile, 238 fileNameConvertor?: (fileName: string) => string, 239 ): DiagnosticMessageChain[] | undefined { 240 let result: DiagnosticMessageChain[] | undefined; 241 if (file.path !== file.resolvedPath) { 242 (result ??= []).push(chainDiagnosticMessages( 243 /*details*/ undefined, 244 Diagnostics.File_is_output_of_project_reference_source_0, 245 toFileName(file.originalFileName, fileNameConvertor) 246 )); 247 } 248 if (file.redirectInfo) { 249 (result ??= []).push(chainDiagnosticMessages( 250 /*details*/ undefined, 251 Diagnostics.File_redirects_to_file_0, 252 toFileName(file.redirectInfo.redirectTarget, fileNameConvertor) 253 )); 254 } 255 if (isExternalOrCommonJsModule(file)) { 256 switch (file.impliedNodeFormat) { 257 case ModuleKind.ESNext: 258 if (file.packageJsonScope) { 259 (result ??= []).push(chainDiagnosticMessages( 260 /*details*/ undefined, 261 Diagnostics.File_is_ECMAScript_module_because_0_has_field_type_with_value_module, 262 toFileName(last(file.packageJsonLocations!), fileNameConvertor) 263 )); 264 } 265 break; 266 case ModuleKind.CommonJS: 267 if (file.packageJsonScope) { 268 (result ??= []).push(chainDiagnosticMessages( 269 /*details*/ undefined, 270 file.packageJsonScope.contents.packageJsonContent.type ? 271 Diagnostics.File_is_CommonJS_module_because_0_has_field_type_whose_value_is_not_module : 272 Diagnostics.File_is_CommonJS_module_because_0_does_not_have_field_type, 273 toFileName(last(file.packageJsonLocations!), fileNameConvertor) 274 )); 275 } 276 else if (file.packageJsonLocations?.length) { 277 (result ??= []).push(chainDiagnosticMessages( 278 /*details*/ undefined, 279 Diagnostics.File_is_CommonJS_module_because_package_json_was_not_found, 280 )); 281 } 282 break; 283 } 284 } 285 return result; 286 } 287 288 export function getMatchedFileSpec(program: Program, fileName: string) { 289 const configFile = program.getCompilerOptions().configFile; 290 if (!configFile?.configFileSpecs?.validatedFilesSpec) return undefined; 291 292 const getCanonicalFileName = createGetCanonicalFileName(program.useCaseSensitiveFileNames()); 293 const filePath = getCanonicalFileName(fileName); 294 const basePath = getDirectoryPath(getNormalizedAbsolutePath(configFile.fileName, program.getCurrentDirectory())); 295 return find(configFile.configFileSpecs.validatedFilesSpec, fileSpec => getCanonicalFileName(getNormalizedAbsolutePath(fileSpec, basePath)) === filePath); 296 } 297 298 export function getMatchedIncludeSpec(program: Program, fileName: string) { 299 const configFile = program.getCompilerOptions().configFile; 300 if (!configFile?.configFileSpecs?.validatedIncludeSpecs) return undefined; 301 302 // Return true if its default include spec 303 if (configFile.configFileSpecs.isDefaultIncludeSpec) return true; 304 305 const isJsonFile = fileExtensionIs(fileName, Extension.Json); 306 const basePath = getDirectoryPath(getNormalizedAbsolutePath(configFile.fileName, program.getCurrentDirectory())); 307 const useCaseSensitiveFileNames = program.useCaseSensitiveFileNames(); 308 return find(configFile?.configFileSpecs?.validatedIncludeSpecs, includeSpec => { 309 if (isJsonFile && !endsWith(includeSpec, Extension.Json)) return false; 310 const pattern = getPatternFromSpec(includeSpec, basePath, "files"); 311 return !!pattern && getRegexFromPattern(`(${pattern})$`, useCaseSensitiveFileNames).test(fileName); 312 }); 313 } 314 315 export function fileIncludeReasonToDiagnostics(program: Program, reason: FileIncludeReason, fileNameConvertor?: (fileName: string) => string,): DiagnosticMessageChain { 316 const options = program.getCompilerOptions(); 317 if (isReferencedFile(reason)) { 318 const referenceLocation = getReferencedFileLocation(path => program.getSourceFileByPath(path), reason); 319 const referenceText = isReferenceFileLocation(referenceLocation) ? referenceLocation.file.text.substring(referenceLocation.pos, referenceLocation.end) : `"${referenceLocation.text}"`; 320 let message: DiagnosticMessage; 321 Debug.assert(isReferenceFileLocation(referenceLocation) || reason.kind === FileIncludeKind.Import, "Only synthetic references are imports"); 322 switch (reason.kind) { 323 case FileIncludeKind.Import: 324 if (isReferenceFileLocation(referenceLocation)) { 325 message = referenceLocation.packageId ? 326 Diagnostics.Imported_via_0_from_file_1_with_packageId_2 : 327 Diagnostics.Imported_via_0_from_file_1; 328 } 329 else if (referenceLocation.text === externalHelpersModuleNameText) { 330 message = referenceLocation.packageId ? 331 Diagnostics.Imported_via_0_from_file_1_with_packageId_2_to_import_importHelpers_as_specified_in_compilerOptions : 332 Diagnostics.Imported_via_0_from_file_1_to_import_importHelpers_as_specified_in_compilerOptions; 333 } 334 else { 335 message = referenceLocation.packageId ? 336 Diagnostics.Imported_via_0_from_file_1_with_packageId_2_to_import_jsx_and_jsxs_factory_functions : 337 Diagnostics.Imported_via_0_from_file_1_to_import_jsx_and_jsxs_factory_functions; 338 } 339 break; 340 case FileIncludeKind.ReferenceFile: 341 Debug.assert(!referenceLocation.packageId); 342 message = Diagnostics.Referenced_via_0_from_file_1; 343 break; 344 case FileIncludeKind.TypeReferenceDirective: 345 message = referenceLocation.packageId ? 346 Diagnostics.Type_library_referenced_via_0_from_file_1_with_packageId_2 : 347 Diagnostics.Type_library_referenced_via_0_from_file_1; 348 break; 349 case FileIncludeKind.LibReferenceDirective: 350 Debug.assert(!referenceLocation.packageId); 351 message = Diagnostics.Library_referenced_via_0_from_file_1; 352 break; 353 default: 354 Debug.assertNever(reason); 355 } 356 return chainDiagnosticMessages( 357 /*details*/ undefined, 358 message, 359 referenceText, 360 toFileName(referenceLocation.file, fileNameConvertor), 361 referenceLocation.packageId && packageIdToString(referenceLocation.packageId) 362 ); 363 } 364 switch (reason.kind) { 365 case FileIncludeKind.RootFile: 366 if (!options.configFile?.configFileSpecs) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Root_file_specified_for_compilation); 367 const fileName = getNormalizedAbsolutePath(program.getRootFileNames()[reason.index], program.getCurrentDirectory()); 368 const matchedByFiles = getMatchedFileSpec(program, fileName); 369 if (matchedByFiles) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Part_of_files_list_in_tsconfig_json); 370 const matchedByInclude = getMatchedIncludeSpec(program, fileName); 371 return isString(matchedByInclude) ? 372 chainDiagnosticMessages( 373 /*details*/ undefined, 374 Diagnostics.Matched_by_include_pattern_0_in_1, 375 matchedByInclude, 376 toFileName(options.configFile, fileNameConvertor) 377 ) : 378 // Could be additional files specified as roots or matched by default include 379 chainDiagnosticMessages(/*details*/ undefined, matchedByInclude ? 380 Diagnostics.Matched_by_default_include_pattern_Asterisk_Asterisk_Slash_Asterisk : 381 Diagnostics.Root_file_specified_for_compilation 382 ); 383 case FileIncludeKind.SourceFromProjectReference: 384 case FileIncludeKind.OutputFromProjectReference: 385 const isOutput = reason.kind === FileIncludeKind.OutputFromProjectReference; 386 const referencedResolvedRef = Debug.checkDefined(program.getResolvedProjectReferences()?.[reason.index]); 387 return chainDiagnosticMessages( 388 /*details*/ undefined, 389 outFile(options) ? 390 isOutput ? 391 Diagnostics.Output_from_referenced_project_0_included_because_1_specified : 392 Diagnostics.Source_from_referenced_project_0_included_because_1_specified : 393 isOutput ? 394 Diagnostics.Output_from_referenced_project_0_included_because_module_is_specified_as_none : 395 Diagnostics.Source_from_referenced_project_0_included_because_module_is_specified_as_none, 396 toFileName(referencedResolvedRef.sourceFile.fileName, fileNameConvertor), 397 options.outFile ? "--outFile" : "--out", 398 ); 399 case FileIncludeKind.AutomaticTypeDirectiveFile: 400 return chainDiagnosticMessages( 401 /*details*/ undefined, 402 options.types ? 403 reason.packageId ? 404 Diagnostics.Entry_point_of_type_library_0_specified_in_compilerOptions_with_packageId_1 : 405 Diagnostics.Entry_point_of_type_library_0_specified_in_compilerOptions : 406 reason.packageId ? 407 Diagnostics.Entry_point_for_implicit_type_library_0_with_packageId_1 : 408 Diagnostics.Entry_point_for_implicit_type_library_0, 409 reason.typeReference, 410 reason.packageId && packageIdToString(reason.packageId), 411 ); 412 case FileIncludeKind.LibFile: 413 if (reason.index !== undefined) return chainDiagnosticMessages(/*details*/ undefined, Diagnostics.Library_0_specified_in_compilerOptions, options.lib![reason.index]); 414 const target = forEachEntry(targetOptionDeclaration.type, (value, key) => value === getEmitScriptTarget(options) ? key : undefined); 415 return chainDiagnosticMessages( 416 /*details*/ undefined, 417 target ? 418 Diagnostics.Default_library_for_target_0 : 419 Diagnostics.Default_library, 420 target, 421 ); 422 default: 423 Debug.assertNever(reason); 424 } 425 } 426 427 function toFileName(file: SourceFile | string, fileNameConvertor?: (fileName: string) => string) { 428 const fileName = isString(file) ? file : file.fileName; 429 return fileNameConvertor ? fileNameConvertor(fileName) : fileName; 430 } 431 432 /** 433 * Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options 434 */ 435 export function emitFilesAndReportErrors<T extends BuilderProgram>( 436 program: Program | T, 437 reportDiagnostic: DiagnosticReporter, 438 write?: (s: string) => void, 439 reportSummary?: ReportEmitErrorSummary, 440 writeFile?: WriteFileCallback, 441 cancellationToken?: CancellationToken, 442 emitOnlyDtsFiles?: boolean, 443 customTransformers?: CustomTransformers 444 ) { 445 const isListFilesOnly = !!program.getCompilerOptions().listFilesOnly; 446 447 // First get and report any syntactic errors. 448 const allDiagnostics = program.getConfigFileParsingDiagnostics().slice(); 449 const configFileParsingDiagnosticsLength = allDiagnostics.length; 450 addRange(allDiagnostics, program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken)); 451 452 // If we didn't have any syntactic errors, then also try getting the global and 453 // semantic errors. 454 if (allDiagnostics.length === configFileParsingDiagnosticsLength) { 455 addRange(allDiagnostics, program.getOptionsDiagnostics(cancellationToken)); 456 457 if (!isListFilesOnly) { 458 addRange(allDiagnostics, program.getGlobalDiagnostics(cancellationToken)); 459 460 if (allDiagnostics.length === configFileParsingDiagnosticsLength) { 461 addRange(allDiagnostics, program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken)); 462 } 463 } 464 } 465 466 // Emit and report any errors we ran into. 467 const emitResult = isListFilesOnly 468 ? { emitSkipped: true, diagnostics: emptyArray } 469 : program.emit(/*targetSourceFile*/ undefined, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers); 470 const { emittedFiles, diagnostics: emitDiagnostics } = emitResult; 471 addRange(allDiagnostics, emitDiagnostics); 472 473 const diagnostics = sortAndDeduplicateDiagnostics(allDiagnostics); 474 diagnostics.forEach(reportDiagnostic); 475 if (write) { 476 const currentDir = program.getCurrentDirectory(); 477 forEach(emittedFiles, file => { 478 const filepath = getNormalizedAbsolutePath(file, currentDir); 479 write(`TSFILE: ${filepath}`); 480 }); 481 listFiles(program, write); 482 } 483 484 if (reportSummary) { 485 reportSummary(getErrorCountForSummary(diagnostics), getFilesInErrorForSummary(diagnostics)); 486 } 487 488 return { 489 emitResult, 490 diagnostics, 491 }; 492 } 493 494 export function emitFilesAndReportErrorsAndGetExitStatus<T extends BuilderProgram>( 495 program: Program | T, 496 reportDiagnostic: DiagnosticReporter, 497 write?: (s: string) => void, 498 reportSummary?: ReportEmitErrorSummary, 499 writeFile?: WriteFileCallback, 500 cancellationToken?: CancellationToken, 501 emitOnlyDtsFiles?: boolean, 502 customTransformers?: CustomTransformers 503 ) { 504 const { emitResult, diagnostics } = emitFilesAndReportErrors( 505 program, 506 reportDiagnostic, 507 write, 508 reportSummary, 509 writeFile, 510 cancellationToken, 511 emitOnlyDtsFiles, 512 customTransformers 513 ); 514 515 if (emitResult.emitSkipped && diagnostics.length > 0) { 516 // If the emitter didn't emit anything, then pass that value along. 517 return ExitStatus.DiagnosticsPresent_OutputsSkipped; 518 } 519 else if (diagnostics.length > 0) { 520 // The emitter emitted something, inform the caller if that happened in the presence 521 // of diagnostics or not. 522 return ExitStatus.DiagnosticsPresent_OutputsGenerated; 523 } 524 return ExitStatus.Success; 525 } 526 527 export const noopFileWatcher: FileWatcher = { close: noop }; 528 export const returnNoopFileWatcher = () => noopFileWatcher; 529 530 export function createWatchHost(system = sys, reportWatchStatus?: WatchStatusReporter): WatchHost { 531 const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system); 532 return { 533 onWatchStatusChange, 534 watchFile: maybeBind(system, system.watchFile) || returnNoopFileWatcher, 535 watchDirectory: maybeBind(system, system.watchDirectory) || returnNoopFileWatcher, 536 setTimeout: maybeBind(system, system.setTimeout) || noop, 537 clearTimeout: maybeBind(system, system.clearTimeout) || noop 538 }; 539 } 540 541 export type WatchType = WatchTypeRegistry[keyof WatchTypeRegistry]; 542 export const WatchType: WatchTypeRegistry = { 543 ConfigFile: "Config file", 544 ExtendedConfigFile: "Extended config file", 545 SourceFile: "Source file", 546 MissingFile: "Missing file", 547 WildcardDirectory: "Wild card directory", 548 FailedLookupLocations: "Failed Lookup Locations", 549 AffectingFileLocation: "File location affecting resolution", 550 TypeRoots: "Type roots", 551 ConfigFileOfReferencedProject: "Config file of referened project", 552 ExtendedConfigOfReferencedProject: "Extended config file of referenced project", 553 WildcardDirectoryOfReferencedProject: "Wild card directory of referenced project", 554 PackageJson: "package.json file", 555 ClosedScriptInfo: "Closed Script info", 556 ConfigFileForInferredRoot: "Config file for the inferred project root", 557 NodeModules: "node_modules for closed script infos and package.jsons affecting module specifier cache", 558 MissingSourceMapFile: "Missing source map file", 559 NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root", 560 MissingGeneratedFile: "Missing generated file", 561 NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation", 562 }; 563 564 export interface WatchTypeRegistry { 565 ConfigFile: "Config file", 566 ExtendedConfigFile: "Extended config file", 567 SourceFile: "Source file", 568 MissingFile: "Missing file", 569 WildcardDirectory: "Wild card directory", 570 FailedLookupLocations: "Failed Lookup Locations", 571 AffectingFileLocation: "File location affecting resolution", 572 TypeRoots: "Type roots", 573 ConfigFileOfReferencedProject: "Config file of referened project", 574 ExtendedConfigOfReferencedProject: "Extended config file of referenced project", 575 WildcardDirectoryOfReferencedProject: "Wild card directory of referenced project", 576 PackageJson: "package.json file", 577 578 // Additional tsserver specific watch information 579 ClosedScriptInfo: "Closed Script info", 580 ConfigFileForInferredRoot: "Config file for the inferred project root", 581 NodeModules: "node_modules for closed script infos and package.jsons affecting module specifier cache", 582 MissingSourceMapFile: "Missing source map file", 583 NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root", 584 MissingGeneratedFile: "Missing generated file", 585 NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation", 586 } 587 588 interface WatchFactory<X, Y = undefined> extends ts.WatchFactory<X, Y> { 589 writeLog: (s: string) => void; 590 } 591 592 export function createWatchFactory<Y = undefined>(host: WatchFactoryHost & { trace?(s: string): void; }, options: { extendedDiagnostics?: boolean; diagnostics?: boolean; }) { 593 const watchLogLevel = host.trace ? options.extendedDiagnostics ? WatchLogLevel.Verbose : options.diagnostics ? WatchLogLevel.TriggerOnly : WatchLogLevel.None : WatchLogLevel.None; 594 const writeLog: (s: string) => void = watchLogLevel !== WatchLogLevel.None ? (s => host.trace!(s)) : noop; 595 const result = getWatchFactory<WatchType, Y>(host, watchLogLevel, writeLog) as WatchFactory<WatchType, Y>; 596 result.writeLog = writeLog; 597 return result; 598 } 599 600 export function createCompilerHostFromProgramHost(host: ProgramHost<any>, getCompilerOptions: () => CompilerOptions, directoryStructureHost: DirectoryStructureHost = host): CompilerHost { 601 const useCaseSensitiveFileNames = host.useCaseSensitiveFileNames(); 602 const hostGetNewLine = memoize(() => host.getNewLine()); 603 const compilerHost: CompilerHost = { 604 getSourceFile: (fileName, languageVersionOrOptions, onError) => { 605 const options = getCompilerOptions(); 606 let text: string | undefined; 607 try { 608 performance.mark("beforeIORead"); 609 const encoding = getCompilerOptions().charset; 610 text = !encoding ? compilerHost.readFile(fileName) : host.readFile(fileName, encoding); 611 performance.mark("afterIORead"); 612 performance.measure("I/O Read", "beforeIORead", "afterIORead"); 613 } 614 catch (e) { 615 if (onError) { 616 onError(e.message); 617 } 618 text = ""; 619 } 620 621 return text !== undefined ? createSourceFile(fileName, text, languageVersionOrOptions, /*setParentNodes*/ undefined, /*scriptKind*/ undefined, options) : undefined; 622 }, 623 getDefaultLibLocation: maybeBind(host, host.getDefaultLibLocation), 624 getDefaultLibFileName: options => host.getDefaultLibFileName(options), 625 writeFile, 626 getCurrentDirectory: memoize(() => host.getCurrentDirectory()), 627 useCaseSensitiveFileNames: () => useCaseSensitiveFileNames, 628 getCanonicalFileName: createGetCanonicalFileName(useCaseSensitiveFileNames), 629 getNewLine: () => getNewLineCharacter(getCompilerOptions(), hostGetNewLine), 630 fileExists: f => host.fileExists(f), 631 readFile: f => host.readFile(f), 632 trace: maybeBind(host, host.trace), 633 directoryExists: maybeBind(directoryStructureHost, directoryStructureHost.directoryExists), 634 getDirectories: maybeBind(directoryStructureHost, directoryStructureHost.getDirectories), 635 realpath: maybeBind(host, host.realpath), 636 getEnvironmentVariable: maybeBind(host, host.getEnvironmentVariable) || (() => ""), 637 createHash: maybeBind(host, host.createHash), 638 readDirectory: maybeBind(host, host.readDirectory), 639 disableUseFileVersionAsSignature: host.disableUseFileVersionAsSignature, 640 storeFilesChangingSignatureDuringEmit: host.storeFilesChangingSignatureDuringEmit, 641 }; 642 return compilerHost; 643 644 function writeFile(fileName: string, text: string, writeByteOrderMark: boolean, onError: (message: string) => void) { 645 try { 646 performance.mark("beforeIOWrite"); 647 648 // NOTE: If patchWriteFileEnsuringDirectory has been called, 649 // the host.writeFile will do its own directory creation and 650 // the ensureDirectoriesExist call will always be redundant. 651 writeFileEnsuringDirectories( 652 fileName, 653 text, 654 writeByteOrderMark, 655 (path, data, writeByteOrderMark) => host.writeFile!(path, data, writeByteOrderMark), 656 path => host.createDirectory!(path), 657 path => host.directoryExists!(path)); 658 659 performance.mark("afterIOWrite"); 660 performance.measure("I/O Write", "beforeIOWrite", "afterIOWrite"); 661 } 662 catch (e) { 663 if (onError) { 664 onError(e.message); 665 } 666 } 667 } 668 } 669 670 export function setGetSourceFileAsHashVersioned(compilerHost: CompilerHost) { 671 const originalGetSourceFile = compilerHost.getSourceFile; 672 const computeHash = maybeBind(compilerHost, compilerHost.createHash) || generateDjb2Hash; 673 compilerHost.getSourceFile = (...args) => { 674 const result = originalGetSourceFile.call(compilerHost, ...args); 675 if (result) { 676 result.version = computeHash(result.text); 677 } 678 return result; 679 }; 680 } 681 682 /** 683 * Creates the watch compiler host that can be extended with config file or root file names and options host 684 */ 685 export function createProgramHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system: System, createProgram: CreateProgram<T> | undefined): ProgramHost<T> { 686 const getDefaultLibLocation = memoize(() => getDirectoryPath(normalizePath(system.getExecutingFilePath()))); 687 return { 688 useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames, 689 getNewLine: () => system.newLine, 690 getCurrentDirectory: memoize(() => system.getCurrentDirectory()), 691 getDefaultLibLocation, 692 getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)), 693 fileExists: path => system.fileExists(path), 694 readFile: (path, encoding) => system.readFile(path, encoding), 695 directoryExists: path => system.directoryExists(path), 696 getDirectories: path => system.getDirectories(path), 697 readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth), 698 realpath: maybeBind(system, system.realpath), 699 getEnvironmentVariable: maybeBind(system, system.getEnvironmentVariable), 700 trace: s => system.write(s + system.newLine), 701 createDirectory: path => system.createDirectory(path), 702 writeFile: (path, data, writeByteOrderMark) => system.writeFile(path, data, writeByteOrderMark), 703 createHash: maybeBind(system, system.createHash), 704 createProgram: createProgram || createEmitAndSemanticDiagnosticsBuilderProgram as any as CreateProgram<T>, 705 disableUseFileVersionAsSignature: system.disableUseFileVersionAsSignature, 706 storeFilesChangingSignatureDuringEmit: system.storeFilesChangingSignatureDuringEmit, 707 now: maybeBind(system, system.now), 708 }; 709 } 710 711 /** 712 * Creates the watch compiler host that can be extended with config file or root file names and options host 713 */ 714 function createWatchCompilerHost<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>(system = sys, createProgram: CreateProgram<T> | undefined, reportDiagnostic: DiagnosticReporter, reportWatchStatus?: WatchStatusReporter): WatchCompilerHost<T> { 715 const write = (s: string) => system.write(s + system.newLine); 716 const result = createProgramHost(system, createProgram) as WatchCompilerHost<T>; 717 copyProperties(result, createWatchHost(system, reportWatchStatus)); 718 result.afterProgramCreate = builderProgram => { 719 const compilerOptions = builderProgram.getCompilerOptions(); 720 const newLine = getNewLineCharacter(compilerOptions, () => system.newLine); 721 722 emitFilesAndReportErrors( 723 builderProgram, 724 reportDiagnostic, 725 write, 726 errorCount => result.onWatchStatusChange!( 727 createCompilerDiagnostic(getWatchErrorSummaryDiagnosticMessage(errorCount), errorCount), 728 newLine, 729 compilerOptions, 730 errorCount 731 ) 732 ); 733 }; 734 return result; 735 } 736 737 /** 738 * Report error and exit 739 */ 740 function reportUnrecoverableDiagnostic(system: System, reportDiagnostic: DiagnosticReporter, diagnostic: Diagnostic) { 741 reportDiagnostic(diagnostic); 742 system.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped); 743 } 744 745 export interface CreateWatchCompilerHostInput<T extends BuilderProgram> { 746 system: System; 747 createProgram?: CreateProgram<T>; 748 reportDiagnostic?: DiagnosticReporter; 749 reportWatchStatus?: WatchStatusReporter; 750 } 751 752 export interface CreateWatchCompilerHostOfConfigFileInput<T extends BuilderProgram> extends CreateWatchCompilerHostInput<T> { 753 configFileName: string; 754 optionsToExtend?: CompilerOptions; 755 watchOptionsToExtend?: WatchOptions; 756 extraFileExtensions?: readonly FileExtensionInfo[]; 757 } 758 /** 759 * Creates the watch compiler host from system for config file in watch mode 760 */ 761 export function createWatchCompilerHostOfConfigFile<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({ 762 configFileName, optionsToExtend, watchOptionsToExtend, extraFileExtensions, 763 system, createProgram, reportDiagnostic, reportWatchStatus 764 }: CreateWatchCompilerHostOfConfigFileInput<T>): WatchCompilerHostOfConfigFile<T> { 765 const diagnosticReporter = reportDiagnostic || createDiagnosticReporter(system); 766 const host = createWatchCompilerHost(system, createProgram, diagnosticReporter, reportWatchStatus) as WatchCompilerHostOfConfigFile<T>; 767 host.onUnRecoverableConfigFileDiagnostic = diagnostic => reportUnrecoverableDiagnostic(system, diagnosticReporter, diagnostic); 768 host.configFileName = configFileName; 769 host.optionsToExtend = optionsToExtend; 770 host.watchOptionsToExtend = watchOptionsToExtend; 771 host.extraFileExtensions = extraFileExtensions; 772 return host; 773 } 774 775 export interface CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T extends BuilderProgram> extends CreateWatchCompilerHostInput<T> { 776 rootFiles: string[]; 777 options: CompilerOptions; 778 watchOptions: WatchOptions | undefined; 779 projectReferences?: readonly ProjectReference[]; 780 } 781 /** 782 * Creates the watch compiler host from system for compiling root files and options in watch mode 783 */ 784 export function createWatchCompilerHostOfFilesAndCompilerOptions<T extends BuilderProgram = EmitAndSemanticDiagnosticsBuilderProgram>({ 785 rootFiles, options, watchOptions, projectReferences, 786 system, createProgram, reportDiagnostic, reportWatchStatus 787 }: CreateWatchCompilerHostOfFilesAndCompilerOptionsInput<T>): WatchCompilerHostOfFilesAndCompilerOptions<T> { 788 const host = createWatchCompilerHost(system, createProgram, reportDiagnostic || createDiagnosticReporter(system), reportWatchStatus) as WatchCompilerHostOfFilesAndCompilerOptions<T>; 789 host.rootFiles = rootFiles; 790 host.options = options; 791 host.watchOptions = watchOptions; 792 host.projectReferences = projectReferences; 793 return host; 794 } 795 796 export interface IncrementalCompilationOptions { 797 rootNames: readonly string[]; 798 options: CompilerOptions; 799 configFileParsingDiagnostics?: readonly Diagnostic[]; 800 projectReferences?: readonly ProjectReference[]; 801 host?: CompilerHost; 802 reportDiagnostic?: DiagnosticReporter; 803 reportErrorSummary?: ReportEmitErrorSummary; 804 afterProgramEmitAndDiagnostics?(program: EmitAndSemanticDiagnosticsBuilderProgram): void; 805 system?: System; 806 } 807 export function performIncrementalCompilation(input: IncrementalCompilationOptions) { 808 const system = input.system || sys; 809 const host = input.host || (input.host = createIncrementalCompilerHost(input.options, system)); 810 const builderProgram = createIncrementalProgram(input); 811 const exitStatus = emitFilesAndReportErrorsAndGetExitStatus( 812 builderProgram, 813 input.reportDiagnostic || createDiagnosticReporter(system), 814 s => host.trace && host.trace(s), 815 input.reportErrorSummary || input.options.pretty ? (errorCount, filesInError) => system.write(getErrorSummaryText(errorCount, filesInError, system.newLine, host)) : undefined 816 ); 817 if (input.afterProgramEmitAndDiagnostics) input.afterProgramEmitAndDiagnostics(builderProgram); 818 return exitStatus; 819 } 820} 821