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