Port formatDiagnostics and formatDiagnosticsWithColorAndContext - #63935
Port formatDiagnostics and formatDiagnosticsWithColorAndContext#63935John Favret (johnfav03) wants to merge 7 commits into
formatDiagnostics and formatDiagnosticsWithColorAndContext#63935Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 5d2afb8f-0680-4d91-9a76-9c08a760ce24
There was a problem hiding this comment.
Pull request overview
Adds Program diagnostic-formatting APIs for roadmap item #63875 (3D), backed by snapshot-aware server formatting.
Changes:
- Adds sync/async formatting APIs, protocol methods, and host path handling.
- Resolves source/config context and tracks diagnostic origins.
- Adds API tests and updates diagnostic baselines.
Reviewed changes
Copilot reviewed 159 out of 159 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
tsc/internal/api/format_diagnostics.go |
Implements server-side formatting and file resolution. |
tsc/internal/api/session.go |
Dispatches formatting requests. |
tsc/internal/api/proto.go |
Defines formatting protocol data. |
tsc/internal/api/session_format_diagnostics_test.go |
Tests server formatting behavior. |
tsc/internal/diagnosticwriter/diagnosticwriter.go |
Refactors shared diagnostic rendering. |
packages/typescript/src/api/sync/api.ts |
Exposes synchronous formatting methods. |
packages/typescript/src/api/async/api.ts |
Exposes asynchronous formatting methods. |
packages/typescript/src/api/sync/types.ts |
Adds the synchronous formatting host type. |
packages/typescript/src/api/async/types.ts |
Adds the asynchronous formatting host type. |
packages/typescript/src/api/path.ts |
Adds host-relative path conversion. |
packages/typescript/src/api/proto.generated.ts |
Adds generated protocol declarations. |
packages/typescript/test/sync/api.test.ts |
Tests the synchronous API. |
packages/typescript/test/async/api.test.ts |
Tests the asynchronous API. |
tsc/testdata/submoduleAccepted.txt |
Updates accepted baseline differences. |
tsc/testdata/baselines/reference/compiler/** |
Updates compiler diagnostic formatting baselines. |
tsc/testdata/baselines/reference/config/** |
Updates configuration diagnostic baselines. |
tsc/testdata/baselines/reference/tsc/** |
Updates command-line compiler baselines. |
tsc/testdata/baselines/reference/tscWatch/** |
Updates watch-mode baselines. |
tsc/testdata/baselines/reference/tsbuild/** |
Updates solution-build baselines. |
tsc/testdata/baselines/reference/tsbuildWatch/** |
Updates watched solution-build baselines. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (1)
tsc/internal/api/proto.go:1446
- These transport-only fields become part of the public
DiagnosticAPI becauseDiagnosticResponseis re-exported asDiagnosticinpackages/typescript/src/api/proto.ts:14. That exposesoriginSnapshot,originProject, anddisplayFileNameeven though origin is intentionally tracked with a private symbol and these values are absent from returned diagnostics; consumers can therefore see and set misleading implementation fields that do not satisfy the formatter's provenance check. Please use a separate formatting-request wire type (or otherwise exclude these fields from the publicDiagnostictype).
// OriginSnapshot and OriginProject identify the program that produced this diagnostic.
OriginSnapshot SnapshotID `json:"originSnapshot,omitzero"`
OriginProject ProjectID `json:"originProject,omitzero"`
// DisplayFileName is the host-formatted file name used only for diagnostic output.
DisplayFileName string `json:"displayFileName,omitempty"`
|
Hm, I think we can have a slightly simpler way of determining whether a diagnostic is valid to be printed in the context of a file from a given snapshot/project. Instead of associating a specific snapshot/project after the fact, what if we just included the source file's hash on diagnostic responses that include a file? Then, the server could validate during printing that the file it looked up matches the hash. The one caveat is that I think the methods that return tsconfig source files don't initialize The other one I'm not sure about is diagnostics that are mapped through declaration maps (i.e. the program loaded a referenced project's .d.ts file, but the diagnostic location was changed to the .ts file via a .d.ts.map). I think that's a thing? Just taking the hash of the declaration file should be fine, we just need to make sure that all works round-trip. Probably a good test to have for this functionality even with the current association approach. |
|
The details of this were only clear to me recently, so apologies for the late feedback, but today you can get |
|
Good point, I think my suggestion to use file hashes would handle that case. |
Andrew Branch (andrewbranch)
left a comment
There was a problem hiding this comment.
api/format_diagnostics.go is surprisingly complex which I think indicates that something is wrong here.
I actually think formatDiagnostic should just be ported to JS, not require a server call at all. It's very few lines of code in total in Strada. And it definitely shouldn't require a full SourceFile to work. Instead of jumping through all these hoops to get back to a SourceFile, we should just be lifting the properties needed for diagnostic formatting onto the API's DiagnosticResponse type at the moment it's created, while we still have a SourceFile on hand in the server. It looks like this is having to do so much work in order to convert pos and end from offsets to line/character pairs. Let's just include the pos/end in both formats on DiagnosticResponse, and then we don't have to make a server round trip just to get the line map.
| if (diagnostic.fileName && diagnostic.code !== fileAppearsToBeBinaryCode) { | ||
| output += host.getNewLine(); | ||
| output += formatCodeSpan(diagnostic, "", getCategoryFormat(diagnostic.category), host); | ||
| } | ||
|
|
||
| if (diagnostic.relatedInformation?.length) { | ||
| output += host.getNewLine(); | ||
| for (const related of diagnostic.relatedInformation) { | ||
| if (related.fileName && related.startPosition) { | ||
| output += host.getNewLine(); | ||
| output += halfIndent + formatLocation(related, host); | ||
| output += formatCodeSpan(related, indent, foregroundColorEscapeCyan, host); | ||
| } | ||
| output += host.getNewLine(); | ||
| output += indent + flattenDiagnosticMessage(related, host.getNewLine()); | ||
| } | ||
| } | ||
| output += host.getNewLine(); |
| resp.Pos = int(core.UTF16Len(file.Text()[:pos])) | ||
| resp.End = int(core.UTF16Len(file.Text()[:end])) |
| const lastCharacter = sourceLine.line === endPosition.line | ||
| ? endPosition.character | ||
| : lineContent.length; | ||
| context += " ".repeat(startPosition.character); | ||
| context += "~".repeat(Math.max(0, lastCharacter - startPosition.character)); |
| host: FormatDiagnosticsHost, | ||
| ): string { | ||
| let output = ""; | ||
| for (const diagnostic of diagnostics) { |
In the TS API, these are exposed on
Programas:formatDiagnostics(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost)formatDiagnosticsWithColorAndContext(diagnostics: readonly Diagnostic[], host: FormatDiagnosticsHost)Part of #63875 (3D)