• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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