I’ve started making contributions to TypeScript, working my way up from small error reporting nitpicks to (hopefully!) real syntactic and control flow improvements. These blog posts are documentation of the steps I took to figure out what to do for these contributes & then do them. If you’re thinking of contributing to TypeScript or other large open source libraries, I hope seeing how to delve into a giant foreign monolith is of some help!
Previous post: Trailing Type Parameters
Next post: Identifiers after Numeric Literals
Problem Statement
TypeScript Issue 22124: Provide summary of errors when in — pretty mode?
When refactoring large projects, the CLI output from compile runs (both from solo compiles and --watch
) can build up pretty quickly.
Every time I save a file it feels like the terminal is filling up with a giant sea of angry errors.
It would be convenient to have an error around the lines of ”Found 43 errors in 21 files.
” at the bottom of each compile to help track progress.
Here’s what a --pretty --watch
run with errors looks like now:
Time to debug!
Step 1: Where
Where should TypeScript print the list of error messages?
Presumably, there must be something that prints out each of the error messages in order.
The messages only have some of this fancy formatting in --pretty
mode, so I figured it’d be easier to look for whatever prints that stuff and see who calls it.
Two unique, search-friendly features of --pretty
printing are:
~
underlines underneath error messages- Special console colors
I ran a search on ”~
” and found a few places in src/compiler/program.ts
, all within a function named formatDiagnosticsWithColorAndContext
.
Perfect!
I put a console.log("Found", diagnostics.length, "errors");
at the bottom of formatDiagnosticsWithColorAndContext
and ran the compiler on a project with two errors across two files to see the message being printed twice:
So it looks like formatDiagnosticsWithColorAndContext
gets run on every file.
Not on every run.
Not so perfect.
😦
A full-text search showed it only ever being called in createDiagnosticReporter
in src/compiler/watch.ts
.
That is called by createWatchCompilerHostOfConfigFile
, which passes it as a reportDiagnostic parameter to createWatchCompilerHost
.
Within that function it’s only ever used within a new function named emitFilesAndReportErrorUsingBuilder
, which passes it to emitFilesAndReportErrors
.
I put the same console message in the new location and again ran the compiler on the same project. This time there was a single message at the end about two complaints:
Hooray! Found the place.
Step 2: How?
How to print all the errors?
I wanted to print both the count of errors and count of files. The first part seemed to already be solved by the count of diagnostics. Remaining to do was to count the unique file names from the diagnostics, create some kind of diagnostic message about the results, and print that message.
2.1: Counting Unique File Names
The Diagnostic
type has a field file: SourceFile | undefined
and the SourceFile
type has a field fileName: string
.
I figured that was a reasonable source of information on file names.
But counting them?
Counting unique objects is generally optimally efficient in JavaScript with a Set.
I ran full-text searches on new Set
and Set.from
but didn’t find anything… which makes sense, as TypeScript works in environments that don’t natively have Set.
But surely there must be something in TypeScript for counting unique objects? What about the slightly-older Map?
I tried again searching for new Map
and found two generic, useful-looking functions at the top of src/compiler/core.ts
:
createDictionaryObject
createMap
Maps are useful when you want a dictionary-like object but can’t guarantee you won’t have have to check edge case key names such as ”
hasOwnProperty
”. That tends to happen when you’re coding a superset of JavaScript.
createMap
uses createDictionaryObject
internally to satisfy the Map
interface.
Looks like TypeScript polyfills Map
.
I haven’t personally executed reliable performance tests to evaluate which is faster, but if TypeScript has a function to choose between the two options, I figured it would make sense to go along with whatever TypeScript used.
Conclusion: use createMap
to keep track of unique file names with errors.
2.2: Localizing Error and File Counts
The diagnostic messages stored by TypeScript in diagnosticInformationMap.generated.ts
are string templates without any logic for pluralization logic.
That’s a little inconvenient as I wanted to print a different message in each of three singular/plural possibilities:
- Single error, single file
- Multiple errors, single file
- Multiple errors, multiple files
Three new strings it was.
The contribution guide’s section on localization mentions that those strings come from src/compiler/diagnosticMessages.json
and are generated by the jake generate-diagnostics
command.
For example:
"Compilation complete. Watching for file changes.": {
"category": "Message",
"code": 6042
},
The ”code
” field is interesting.
There are codes around that compilation message from 6001
to 6190
, then gaps around. I assumed this was some kind of organization strategy and added the three new messages in 6191
-6193
:
Found_1_error_in_1_file: diag(6191, ...)
Found_0_errors_in_1_file: diag(6192, ...)
Found_0_errors_in_1_files: diag(6193, ...)
Later, I thought to click the Diagnostics guidelines link in CONTRIBUTING.md…
2.3 Diagnostics Printing
Other diagnostics in emitFilesAndReportErrors
are printed via a function named reportDiagnostic
.
I tried using that to print the new summary message but got a different-looking message prefix:
Elsewhere, such as with the nice ”Compilation complete. ...
” message, the reportWatchDiagnostic
function not available in emitFilesAndReportErrors
was used.
It was being created locally within createWatchCompilerHost
but not passed through to (or visible within) emitFilesAndReportErrors
.
I changed emitFilesAndReportErrors
to take it as a parameter (in addition to system.newLine
to pass to the reporter
) and use it: