Skip to content

Preserve single-line emit hints for nodes returned by the API - #65062

Open
Harsh Vardhan Chauhan (1-DARK) wants to merge 2 commits into
microsoft:mainfrom
1-DARK:fix-printnode-single-line-type-nodes
Open

Harsh Vardhan Chauhan (1-DARK) wants to merge 2 commits into
microsoft:mainfrom
1-DARK:fix-printnode-single-line-type-nodes

Conversation

@1-DARK

Copy link
Copy Markdown
Contributor

Fixes #64991

Problem

With typescript/sync/async, checker.typeToTypeNode(...) results printed via api.printer.printNode(...) come out across several lines:

[
    Date,
    number
]

whereas checker.typeToString and the 6.0 API give [Date, number] / { a: string; }.

Cause

The node builder marks tuple types, type literals (and a few other nodes: mapped types, empty type literals, string literals in type position, pseudo-type-builder results) with EFSingleLine / EFNoAsciiEscaping. Those flags live in the node builder's EmitContext, not on the node. The API encoder only serialized the node, and printNode decoded it into a fresh printer with no emit flags, so the default multi-line layout was used.

Fix

Carry the two printer hints across the API boundary:

  • ast/nodeflags.go: add NodeFlagsEmitSingleLine (bit 29) and NodeFlagsEmitNoAsciiEscaping (bit 30). They are transport-only and never set by the parser/binder.
  • api/encoder: new EncodeNodeWithEmitContext; when given an emit context it sets those bits in each node's flags (without mutating the nodes).
  • checker/printer.go: TypeToTypeNodeWithEmitContext and SignatureToSignatureDeclarationWithEmitContext return the builder's emit context alongside the node.
  • api/session.go: typeToTypeNode and signatureToSignatureDeclaration encode with that context; printNode converts the bits back into emit flags before printing.
  • Regenerated enums/nodeFlags.* and enum_values_generated.go.

No client-facing API changes. Because the hint travels in node.flags, it survives the client re-encoding the node for printNode. FlagsMultilineObjectLiterals is respected (the builder doesn't set the flag in that case, so nothing is sent), and hand-written multi-line type literals are unaffected.

Alternatives considered

  • A PrintNodeOptions flag (e.g. singleLine): works, but would flatten every type literal/tuple in the tree, including user-written ones, and requires callers to opt in to what 6.0 did by default.
  • Per-kind commonData bits in the encoder: more explicit, but needs generator and TS node class changes. Happy to switch to that if preferred.

Tests

  • Go: TestPrintNodeRestoresEmitFlags (encode with emit context → decode → print; with and without the hint).
  • JS: new sync/async API test printing typeToTypeNode results for { tuple: [Date, number]; obj: { a: string } } and asserting they equal typeToString.
  • npx hereby test: 168642 tests passed. hereby lint and hereby check:format are clean. Sync/async API tests: 727/727.

Notes

  • Note: node.flags for builder-produced nodes seen from JS now includes bit 29/30 when set.
  • AI disclosure: this patch was authored with an AI coding agent (pi). I have reviewed it, understand it, and will respond to review feedback myself.

Nodes built by the node builder (e.g. via typeToTypeNode) mark tuple and
type literal nodes as single-line using emit flags kept in an emit
context. The API encoder never sent those, so printNode on the client
side printed such nodes across several lines, unlike typeToString and the
6.0 API.

Carry EFSingleLine and EFNoAsciiEscaping across the API in two reserved
NodeFlags bits when encoding the results of typeToTypeNode and
signatureToSignatureDeclaration, and restore them as emit flags in
printNode.
Copilot AI balanced review requested due to automatic review settings October 30, 2026 18:01
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Oct 30, 2026
@typescript-automation typescript-automation Bot added the For Uncommitted Bug PR for untriaged, rejected, closed or missing bug label Oct 30, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new tests lack non-ASCII round-trip coverage for EFNoAsciiEscaping, so regressions in transporting or restoring that hint would go undetected.

1 open issue
1 file not reviewed
  • tsc/internal/api/enum_values_generated.go: Generated file
What changed in this PR

Preserves node-builder printer hints across the TypeScript API boundary, addressing #64991 so checker-generated tuples and type literals retain their single-line formatting.

Changes:

  • Transports single-line and no-ASCII-escaping hints through node flags.
  • Returns emit contexts from checker builders and restores hints before printing.
  • Adds Go round-trip coverage and sync/async API formatting tests.
File Description
tsc/​internal/​checker/​printer.go Adds builder methods that return emit contexts.
tsc/​internal/​ast/​nodeflags.go Defines transport flags for printer hints.
tsc/​internal/​api/​session.go Encodes builder hints and restores them for printing.
tsc/​internal/​api/​printnode_emitflags_test.go Tests single-line hints through encoding and decoding.
tsc/​internal/​api/​enum_values_generated.go Registers the new generated enum values.
tsc/​internal/​api/​encoder/​encoder.go Serializes emit hints without mutating nodes.
packages/​typescript/​test/​sync/​api.test.ts Tests single-line formatting through the sync API.
packages/​typescript/​test/​async/​api.test.ts Tests single-line formatting through the async API.
packages/​typescript/​enums/​nodeFlags.js Adds runtime enum values for transported hints.
packages/​typescript/​enums/​nodeFlags.d.ts Adds corresponding enum declarations.

Review effort: Balanced

Comment on lines +468 to +469
if emitFlags&printer.EFNoAsciiEscaping != 0 {
flags |= ast.NodeFlagsEmitNoAsciiEscaping

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks. Added TestPrintNodeRestoresNoAsciiEscaping: a synthesized "é" literal round-trips verbatim with EFNoAsciiEscaping and as "\u00E9" without it. I also confirmed it fails if the encoder stops setting NodeFlagsEmitNoAsciiEscaping.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

No active review issues were identified for the change preserving printer hints across the TypeScript API boundary.

1 open issue
1 file not reviewed
  • tsc/internal/api/enum_values_generated.go: Generated file

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

typescript/sync: tuples and type literals from checker.typeToTypeNode print multi-line (SingleLine flag lost)

2 participants