Skip to content

Fix formatter blank line and over-indent for union template argument#11010

Merged
timotheeguerin merged 9 commits into
microsoft:mainfrom
oha-4:fix/formatter-union-template-arg
Jun 24, 2026
Merged

Fix formatter blank line and over-indent for union template argument#11010
timotheeguerin merged 9 commits into
microsoft:mainfrom
oha-4:fix/formatter-union-template-arg

Conversation

@oha-4

@oha-4 oha-4 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #11009

Problem

When a union expression is used directly as one of multiple template arguments and the argument list is long enough to wrap, tsp format inserts a blank line before the union and indents its leading-| variants one level deeper than the sibling arguments:

model Picked
  is PickProperties<
    Sample,

      | "alpha"
      | "bravo"
      ...
  >;

Fix

printTemplateParameters already emits a softline + an indent level before each argument in a multi-argument list. printUnion was also emitting its own leading line + indent (+ align(2)), so the two stacked, producing the blank line and the extra indentation level.

printUnion now detects when the union is a direct argument of a multi-argument template reference (isInMultiTemplateArgumentList) and, in that case, lets the surrounding argument list control the line break and indentation. Result:

model Picked
  is PickProperties<
    Sample,
    | "alpha"
    | "bravo"
    ...
  >;

The standalone/value-position cases (e.g. alias X = "a" | "b" | ...) and the single-template-argument (shouldHug) case are unchanged, since there the surrounding context does not supply the line break/indent.

Tests

When a `union` expression was used directly as one of multiple template
arguments and the argument list wrapped, the formatter inserted a blank
line before the union and indented its `|`-prefixed variants one level
deeper than the sibling arguments.

The argument list already supplies a line break and an indentation level
before each argument, so `printUnion` no longer adds its own leading line
and indent when the union is a direct argument of a multi-argument
template reference.

Fixes microsoft#11009

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@microsoft-github-policy-service microsoft-github-policy-service Bot added compiler:core Issues for @typespec/compiler emitter:client:java Issue for the Java client emitter: @typespec/http-client-java labels Jun 17, 2026
@oha-4

oha-4 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

@microsoft-github-policy-service agree

@pkg-pr-new

pkg-pr-new Bot commented Jun 17, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/compiler@11010

commit: bd04975

@github-actions

github-actions Bot commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/compiler
  • @typespec/http-client-java
Show changes

@typespec/compiler - fix ✏️

Fix formatter inserting a blank line and over-indenting a union expression used directly as one of multiple template arguments (e.g. PickProperties<Source, "a" | "b">)

@typespec/http-client-java - internal ✏️

Reformat union template arguments in test files to match updated formatter style.

@timotheeguerin timotheeguerin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the contribution, I think there is also a regression this introduce in the formatting, i'll also need to see the effect this has on all of azure specs though it does seem like a good thing to fix

Comment thread packages/http-client-java/generator/http-client-generator-test/tsp/arm.tsp Outdated
Address review feedback: the previous fix dropped align(2) on union
variants when the union is a multi-argument template argument, which
de-indented a nested template inside a variant. Keep align(2)
unconditionally (matching prettier's union printer) so a breaking
variant stays aligned under its "| " prefix, and add a regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oha-4

oha-4 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review — you're right, fixed in 4daeb1b6b. The previous change dropped align(2) from the variants, which de-indented the nested template.

And to your question: this does follow prettier's union printer — indent-or-not depends on the parent (shouldIndent, union-type.js#L23-L38), but the per-variant align(2) is kept regardless (#L49-L56). I'd wrongly tied them together; now align(2) is unconditional, so a breaking variant stays aligned under its | , matching your screenshot. Added a regression test for it too.

Reformatting arm.tsp/response.tsp (union template arguments) changed the
tcgc crossLanguageVersion hash of the generated metadata. Update the two
*_metadata.json files to the regenerated hashes so the Java RegenCheck
passes, and add an internal changeset for @typespec/http-client-java so
the changelog check passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Comment thread packages/compiler/src/formatter/print/printer.ts Outdated
A TemplateArgument node only ever exists as an element of
TypeReference.arguments (the sole TemplateArgumentNode[] in the AST),
so when the parent is a TemplateArgument the owning TypeReference is
always the next node ancestor. Replace the variable-depth loop with a
direct getParentNode(1) check; behavior is unchanged and all formatter
tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@weidongxu-microsoft weidongxu-microsoft 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.

approve on java lib to unblock.

but unsure why metadata file changes, they should not be affected by typespec source formatting. though I think Alan merged another unblocker.

@oha-4 oha-4 requested a review from alzimmermsft as a code owner June 22, 2026 05:24
@timotheeguerin

Copy link
Copy Markdown
Member

approve on java lib to unblock.

but unsure why metadata file changes, they should not be affected by typespec source formatting. though I think Alan merged another unblocker.

yeah we investigated with Allen, I believe the issue is that the crossVersionId hashes the file content which the formatting affect. Should be good now

timotheeguerin pushed a commit to timotheeguerin/typespec that referenced this pull request Jun 22, 2026
…osoft#11011)

Fixes the `is`-breaking part of microsoft#11009 (the blank-line/over-indent part
is addressed separately in microsoft#11010).

## Problem

When a model/scalar heritage clause (`is X` / `extends X`) references a
template with multiple arguments, the formatter wrapped the keyword and
the template reference in a single `group(indent(...))`. Prettier
measures that group against the full flat width of the template argument
list, so the group broke and pushed the keyword onto its own indented
line — even when the declaration line itself was short:

```tsp
model Picked
  is PickProperties<
    Sample,
    ...
  >;
```

## Fix

`printHeritageClause` now keeps the keyword on the declaration line when
the base is a template reference with multiple (breakable) arguments,
and lets the template argument list control the line break:

```tsp
model Picked is PickProperties<
  Sample,
  ...
>;
```

Single-argument (hugged) and non-template bases keep the previous
behavior of breaking the keyword onto its own line when the declaration
is too long.

## Tests

- Added regression tests for the multi-argument case on `model is`,
`model extends`, and `scalar extends` in `formatter.test.ts`.
- Reformatted one committed `http-client-java` test spec
(`arm-customization.tsp`) that contained the old output (whitespace
only, no semantic change).
- Formatter test suite, eslint, tsc, and `prettier --check "**/*.tsp"`
all pass locally.

> Note: the `crossLanguageVersion` hash in the corresponding
`*_metadata.json` is whitespace-sensitive; it will be updated from the
RegenCheck output once CI runs.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@timotheeguerin timotheeguerin added this pull request to the merge queue Jun 23, 2026
@timotheeguerin

Copy link
Copy Markdown
Member

Thanks for the formatter fixes @oha-4

@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jun 23, 2026
@timotheeguerin

Copy link
Copy Markdown
Member

@oha-4 it seems like tests need to be updated as seems to conflict with your other pr I merged

@timotheeguerin timotheeguerin enabled auto-merge June 24, 2026 15:13
@timotheeguerin timotheeguerin added this pull request to the merge queue Jun 24, 2026
Merged via the queue into microsoft:main with commit 482035c Jun 24, 2026
39 checks passed
@oha-4 oha-4 deleted the fix/formatter-union-template-arg branch June 24, 2026 17:49
iscai-msft pushed a commit to iscai-msft/typespec that referenced this pull request Jun 25, 2026
…oft#11093)

Fixes microsoft#11092

## Problem

PR microsoft#11010 fixed a blank-line / over-indent issue when a `union`
expression is used as one of several **positional** template arguments.
That fix made `printUnion` suppress its own leading line + indent when
the union is a direct argument of a multi-argument template reference
(the surrounding argument list already supplies them).

This regressed the **named** template argument case (`Name = <union>`).
For a named argument the union is printed *after* `Name = `, so the
argument list's line break / indent does **not** apply to the union
variants. Suppressing the union's own break + indent collapsed the
variants:

```tsp
alias Alias = Test<
  A = "Some long value",
  TakesALongUnion = | string
  | int32
  | int64
  | "Some very long string that split line"
>;
```

## Fix

`isInMultiTemplateArgumentList` now returns `false` when the parent
`TemplateArgument` has a `name`. Named-argument unions fall back to the
standalone behavior (their own leading `line` + `| ` + indent):

```tsp
alias Alias = Test<
  A = "Some long value",
  TakesALongUnion =
    | string
    | int32
    | int64
    | "Some very long string that split line"
>;
```

The single-argument case and the positional multi-argument case (microsoft#11009)
are unchanged.

## Tests

- Added a regression test for the named-union multi-argument case
(microsoft#11092).
- Existing microsoft#11009 positional tests still pass; full formatter suite (216
tests) passes.
- `prettier --check "**/*.tsp"` clean; compiler builds.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compiler:core Issues for @typespec/compiler emitter:client:java Issue for the Java client emitter: @typespec/http-client-java

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Formatter adds a blank line and over-indents a union used as a template argument

3 participants