From c01f73a0c77d9f818ae5430714f582ff90e0208b Mon Sep 17 00:00:00 2001 From: Yoshito Ohata <79030436+oha-4@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:51:35 +0900 Subject: [PATCH 1/3] Keep `is`/`extends` inline for multi-argument template heritage A model/scalar heritage clause (`is X` / `extends X`) wrapped the keyword and the template reference in one `group(indent(...))`. Prettier measures that group against the full flat width of the template argument list, so it broke and pushed the keyword onto its own indented line even when the declaration line was short: model Picked is PickProperties< Sample, ... >; `printHeritageClause` now keeps the keyword inline when the base is a template reference with multiple (breakable) arguments, letting the argument list control the line break: model Picked is PickProperties< Sample, ... >; Single-argument and non-template bases keep breaking the keyword when the declaration is too long. Fixes the `is`-breaking part of #11009. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...matter-heritage-keyword-break-2026-6-18.md | 7 +++ .../compiler/src/formatter/print/printer.ts | 40 +++++++++++++---- .../compiler/test/formatter/formatter.test.ts | 45 +++++++++++++++++++ 3 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 .chronus/changes/fix-formatter-heritage-keyword-break-2026-6-18.md diff --git a/.chronus/changes/fix-formatter-heritage-keyword-break-2026-6-18.md b/.chronus/changes/fix-formatter-heritage-keyword-break-2026-6-18.md new file mode 100644 index 00000000000..d9e5429e605 --- /dev/null +++ b/.chronus/changes/fix-formatter-heritage-keyword-break-2026-6-18.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Keep the `is`/`extends` keyword on the declaration line when the base is a template reference with multiple arguments. The template argument list now controls the line breaking instead of the keyword being pushed onto its own indented line. diff --git a/packages/compiler/src/formatter/print/printer.ts b/packages/compiler/src/formatter/print/printer.ts index adf307c6c6a..497155a71ce 100644 --- a/packages/compiler/src/formatter/print/printer.ts +++ b/packages/compiler/src/formatter/print/printer.ts @@ -1043,6 +1043,31 @@ export function printArrayLiteral( ]); } +// When the base is a multi-argument template, its argument list breaks on its +// own, so keep `is`/`extends` inline instead of breaking the keyword too (#11009). +function printHeritageClause( + path: AstPath, + print: PrettierChildPrint, + keyword: string, + propertyName: keyof T, +): Doc { + const ref = path.node[propertyName] as Node | undefined; + if (!ref) { + return ""; + } + const printed = [`${keyword} `, path.call(print, propertyName as any)]; + if (isMultiArgTemplateReference(ref)) { + return [" ", printed]; + } + return group(indent([ifBreak(line, " "), printed])); +} + +function isMultiArgTemplateReference(node: Node): boolean { + return ( + node.kind === SyntaxKind.TypeReference && (node as TypeReferenceNode).arguments.length >= 2 + ); +} + export function printModelStatement( path: AstPath, options: TypeSpecPrettierOptions, @@ -1050,10 +1075,8 @@ export function printModelStatement( ) { const node = path.node; const id = path.call(print, "id"); - const heritage = node.extends - ? [ifBreak(line, " "), "extends ", path.call(print, "extends")] - : ""; - const isBase = node.is ? [ifBreak(line, " "), "is ", path.call(print, "is")] : ""; + const heritage = printHeritageClause(path, print, "extends", "extends"); + const isBase = printHeritageClause(path, print, "is", "is"); const generic = printTemplateParameters(path, options, print, "templateParameters"); const nodeHasComments = hasComments(node, CommentCheckFlags.Dangling); const shouldPrintBody = nodeHasComments || !(node.properties.length === 0 && node.is); @@ -1064,7 +1087,8 @@ export function printModelStatement( "model ", id, generic, - group(indent(["", heritage, isBase])), + heritage, + isBase, body, ]; } @@ -1240,9 +1264,7 @@ function printScalarStatement( const id = path.call(print, "id"); const template = printTemplateParameters(path, options, print, "templateParameters"); - const heritage = node.extends - ? [ifBreak(line, " "), "extends ", path.call(print, "extends")] - : ""; + const heritage = printHeritageClause(path, print, "extends", "extends"); const nodeHasComments = hasComments(node, CommentCheckFlags.Dangling); const shouldPrintBody = nodeHasComments || !(node.members.length === 0); @@ -1253,7 +1275,7 @@ function printScalarStatement( "scalar ", id, template, - group(indent(["", heritage])), + heritage, members, ]; } diff --git a/packages/compiler/test/formatter/formatter.test.ts b/packages/compiler/test/formatter/formatter.test.ts index c4ac8e5900e..7595e57b6d2 100644 --- a/packages/compiler/test/formatter/formatter.test.ts +++ b/packages/compiler/test/formatter/formatter.test.ts @@ -207,6 +207,21 @@ model Foo extends SuperExtremeAndVeryVeryVeryVeryVeryVeryLongModelThatWillBeTo expected: ` model Foo extends SuperExtremeAndVeryVeryVeryVeryVeryVeryLongModelThatWillBeTooLong {} +`, + }); + }); + + it("keeps extends inline and breaks the template arguments when too long", async () => { + await assertFormat({ + code: ` +model Foo extends Base {} +`, + expected: ` +model Foo extends Base< + FirstArgumentTypeName, + SecondArgumentTypeName, + ThirdArgumentTypeName +> {} `, }); }); @@ -255,6 +270,21 @@ model Foo is SuperExtremeAndVeryVeryVeryVeryVeryVeryLongLongLongModelThatWillB expected: ` model Foo is SuperExtremeAndVeryVeryVeryVeryVeryVeryLongLongLongModelThatWillBeTooLong; +`, + }); + }); + + it("keeps is inline and breaks the template arguments when too long", async () => { + await assertFormat({ + code: ` +model Foo is Base; +`, + expected: ` +model Foo is Base< + FirstArgumentTypeName, + SecondArgumentTypeName, + ThirdArgumentTypeName +>; `, }); }); @@ -809,6 +839,21 @@ scalar Foo extends string; }); }); + it("keeps extends inline and breaks the template arguments when too long", async () => { + await assertFormat({ + code: ` +scalar Foo extends Base; +`, + expected: ` +scalar Foo extends Base< + FirstArgumentTypeName, + SecondArgumentTypeName, + ThirdArgumentTypeName +>; +`, + }); + }); + it("format with template parameters", async () => { await assertFormat({ code: ` From 95e7383e845f74505a3b563c85b9bcc5a9e2f8d6 Mon Sep 17 00:00:00 2001 From: Yoshito Ohata <79030436+oha-4@users.noreply.github.com> Date: Thu, 18 Jun 2026 00:51:36 +0900 Subject: [PATCH 2/3] Reformat specs and docs affected by heritage formatter change Apply the new `is`/`extends` inline formatting (#11009) to committed artifacts that the formatter now reformats: the http-client-java test spec arm-customization.tsp and the 1.11 release-notes embedded example. Whitespace only, no semantic change. Also update the regenerated `crossLanguageVersion` hash in azure-resourcemanager-armcustomization-generated_metadata.json, which is whitespace-sensitive to the reformatted spec. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...formatter-heritage-keyword-break-java-2026-6-18.md | 7 +++++++ ...cemanager-armcustomization-generated_metadata.json | 2 +- .../tsp/arm-customization.tsp | 6 ++++-- .../content/docs/release-notes/typespec-1-11-0.mdx | 11 +++++------ 4 files changed, 17 insertions(+), 9 deletions(-) create mode 100644 .chronus/changes/fix-formatter-heritage-keyword-break-java-2026-6-18.md diff --git a/.chronus/changes/fix-formatter-heritage-keyword-break-java-2026-6-18.md b/.chronus/changes/fix-formatter-heritage-keyword-break-java-2026-6-18.md new file mode 100644 index 00000000000..378311e042e --- /dev/null +++ b/.chronus/changes/fix-formatter-heritage-keyword-break-java-2026-6-18.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@typespec/http-client-java" +--- + +Reformat a test spec affected by the compiler formatter change keeping `is`/`extends` inline for multi-argument template references (whitespace only, no semantic change). diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/azure-resourcemanager-armcustomization-generated_metadata.json b/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/azure-resourcemanager-armcustomization-generated_metadata.json index 422aed2868c..13041748030 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/azure-resourcemanager-armcustomization-generated_metadata.json +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/azure-resourcemanager-armcustomization-generated_metadata.json @@ -1 +1 @@ -{"flavor":"Azure","apiVersions":{"TspTest.ArmCustomization":"2023-12-01-preview"},"crossLanguagePackageId":"TspTest.ArmCustomization","crossLanguageVersion":"60dbe4eea940","crossLanguageDefinitions":{"tsptest.armcustomization.fluent.ArmCustomizationClient":"TspTest.ArmCustomization","tsptest.armcustomization.fluent.VaultsClient":"TspTest.ArmCustomization.Vaults","tsptest.armcustomization.fluent.VaultsClient.getByResourceGroup":"TspTest.ArmCustomization.Vaults.get","tsptest.armcustomization.fluent.VaultsClient.getByResourceGroupWithResponse":"TspTest.ArmCustomization.Vaults.get","tsptest.armcustomization.fluent.models.VaultInner":"TspTest.ArmCustomization.Vault","tsptest.armcustomization.implementation.ArmCustomizationClientBuilder":"TspTest.ArmCustomization","tsptest.armcustomization.models.VaultProperties":"TspTest.ArmCustomization.VaultProperties"},"generatedFiles":["src/main/java/module-info.java","src/main/java/tsptest/armcustomization/ArmCustomizationManager.java","src/main/java/tsptest/armcustomization/fluent/ArmCustomizationClient.java","src/main/java/tsptest/armcustomization/fluent/VaultsClient.java","src/main/java/tsptest/armcustomization/fluent/models/VaultInner.java","src/main/java/tsptest/armcustomization/fluent/models/package-info.java","src/main/java/tsptest/armcustomization/fluent/package-info.java","src/main/java/tsptest/armcustomization/implementation/ArmCustomizationClientBuilder.java","src/main/java/tsptest/armcustomization/implementation/ArmCustomizationClientImpl.java","src/main/java/tsptest/armcustomization/implementation/ResourceManagerUtils.java","src/main/java/tsptest/armcustomization/implementation/VaultImpl.java","src/main/java/tsptest/armcustomization/implementation/VaultsClientImpl.java","src/main/java/tsptest/armcustomization/implementation/VaultsImpl.java","src/main/java/tsptest/armcustomization/implementation/package-info.java","src/main/java/tsptest/armcustomization/models/Vault.java","src/main/java/tsptest/armcustomization/models/VaultProperties.java","src/main/java/tsptest/armcustomization/models/Vaults.java","src/main/java/tsptest/armcustomization/models/package-info.java","src/main/java/tsptest/armcustomization/package-info.java"]} \ No newline at end of file +{"flavor":"Azure","apiVersions":{"TspTest.ArmCustomization":"2023-12-01-preview"},"crossLanguagePackageId":"TspTest.ArmCustomization","crossLanguageVersion":"ff5f72365aee","crossLanguageDefinitions":{"tsptest.armcustomization.fluent.ArmCustomizationClient":"TspTest.ArmCustomization","tsptest.armcustomization.fluent.VaultsClient":"TspTest.ArmCustomization.Vaults","tsptest.armcustomization.fluent.VaultsClient.getByResourceGroup":"TspTest.ArmCustomization.Vaults.get","tsptest.armcustomization.fluent.VaultsClient.getByResourceGroupWithResponse":"TspTest.ArmCustomization.Vaults.get","tsptest.armcustomization.fluent.models.VaultInner":"TspTest.ArmCustomization.Vault","tsptest.armcustomization.implementation.ArmCustomizationClientBuilder":"TspTest.ArmCustomization","tsptest.armcustomization.models.VaultProperties":"TspTest.ArmCustomization.VaultProperties"},"generatedFiles":["src/main/java/module-info.java","src/main/java/tsptest/armcustomization/ArmCustomizationManager.java","src/main/java/tsptest/armcustomization/fluent/ArmCustomizationClient.java","src/main/java/tsptest/armcustomization/fluent/VaultsClient.java","src/main/java/tsptest/armcustomization/fluent/models/VaultInner.java","src/main/java/tsptest/armcustomization/fluent/models/package-info.java","src/main/java/tsptest/armcustomization/fluent/package-info.java","src/main/java/tsptest/armcustomization/implementation/ArmCustomizationClientBuilder.java","src/main/java/tsptest/armcustomization/implementation/ArmCustomizationClientImpl.java","src/main/java/tsptest/armcustomization/implementation/ResourceManagerUtils.java","src/main/java/tsptest/armcustomization/implementation/VaultImpl.java","src/main/java/tsptest/armcustomization/implementation/VaultsClientImpl.java","src/main/java/tsptest/armcustomization/implementation/VaultsImpl.java","src/main/java/tsptest/armcustomization/implementation/package-info.java","src/main/java/tsptest/armcustomization/models/Vault.java","src/main/java/tsptest/armcustomization/models/VaultProperties.java","src/main/java/tsptest/armcustomization/models/Vaults.java","src/main/java/tsptest/armcustomization/models/package-info.java","src/main/java/tsptest/armcustomization/package-info.java"]} \ No newline at end of file diff --git a/packages/http-client-java/generator/http-client-generator-test/tsp/arm-customization.tsp b/packages/http-client-java/generator/http-client-generator-test/tsp/arm-customization.tsp index b964ba2e376..fe67b54a891 100644 --- a/packages/http-client-java/generator/http-client-generator-test/tsp/arm-customization.tsp +++ b/packages/http-client-java/generator/http-client-generator-test/tsp/arm-customization.tsp @@ -23,8 +23,10 @@ enum Versions { v2023_12_01_preview: "2023-12-01-preview", } -model Vault - is Azure.ResourceManager.Legacy.TrackedResourceWithOptionalLocation { +model Vault is Azure.ResourceManager.Legacy.TrackedResourceWithOptionalLocation< + VaultProperties, + false +> { ...ResourceNameParameter< Resource = Vault, KeyName = "vaultName", diff --git a/website/src/content/docs/release-notes/typespec-1-11-0.mdx b/website/src/content/docs/release-notes/typespec-1-11-0.mdx index 23ed7633339..a28beb27fda 100644 --- a/website/src/content/docs/release-notes/typespec-1-11-0.mdx +++ b/website/src/content/docs/release-notes/typespec-1-11-0.mdx @@ -31,12 +31,11 @@ model Example { description: string; } -model CreateAndReadExample - is FilterVisibility< - Example, - #{ all: #[Lifecycle.Create, Lifecycle.Read] }, - "CreateAndRead{name}" - >; +model CreateAndReadExample is FilterVisibility< + Example, + #{ all: #[Lifecycle.Create, Lifecycle.Read] }, + "CreateAndRead{name}" +>; ``` :::caution From 051c04c2fba92d768003df19c3df5ab9299534ec Mon Sep 17 00:00:00 2001 From: Yoshito Ohata <79030436+oha-4@users.noreply.github.com> Date: Fri, 19 Jun 2026 10:17:13 +0900 Subject: [PATCH 3/3] Revert metadata hash change per review feedback Keep azure-resourcemanager-armcustomization-generated_metadata.json out of this formatter PR as requested. The arm-customization.tsp reformat stays (required by format:check); leaving the regenerated crossLanguageVersion hash reverted lets CI surface the RegenCheck behavior for the java team. Co-Authored-By: Claude Opus 4.8 (1M context) --- ...ure-resourcemanager-armcustomization-generated_metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/azure-resourcemanager-armcustomization-generated_metadata.json b/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/azure-resourcemanager-armcustomization-generated_metadata.json index 13041748030..422aed2868c 100644 --- a/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/azure-resourcemanager-armcustomization-generated_metadata.json +++ b/packages/http-client-java/generator/http-client-generator-test/src/main/resources/META-INF/azure-resourcemanager-armcustomization-generated_metadata.json @@ -1 +1 @@ -{"flavor":"Azure","apiVersions":{"TspTest.ArmCustomization":"2023-12-01-preview"},"crossLanguagePackageId":"TspTest.ArmCustomization","crossLanguageVersion":"ff5f72365aee","crossLanguageDefinitions":{"tsptest.armcustomization.fluent.ArmCustomizationClient":"TspTest.ArmCustomization","tsptest.armcustomization.fluent.VaultsClient":"TspTest.ArmCustomization.Vaults","tsptest.armcustomization.fluent.VaultsClient.getByResourceGroup":"TspTest.ArmCustomization.Vaults.get","tsptest.armcustomization.fluent.VaultsClient.getByResourceGroupWithResponse":"TspTest.ArmCustomization.Vaults.get","tsptest.armcustomization.fluent.models.VaultInner":"TspTest.ArmCustomization.Vault","tsptest.armcustomization.implementation.ArmCustomizationClientBuilder":"TspTest.ArmCustomization","tsptest.armcustomization.models.VaultProperties":"TspTest.ArmCustomization.VaultProperties"},"generatedFiles":["src/main/java/module-info.java","src/main/java/tsptest/armcustomization/ArmCustomizationManager.java","src/main/java/tsptest/armcustomization/fluent/ArmCustomizationClient.java","src/main/java/tsptest/armcustomization/fluent/VaultsClient.java","src/main/java/tsptest/armcustomization/fluent/models/VaultInner.java","src/main/java/tsptest/armcustomization/fluent/models/package-info.java","src/main/java/tsptest/armcustomization/fluent/package-info.java","src/main/java/tsptest/armcustomization/implementation/ArmCustomizationClientBuilder.java","src/main/java/tsptest/armcustomization/implementation/ArmCustomizationClientImpl.java","src/main/java/tsptest/armcustomization/implementation/ResourceManagerUtils.java","src/main/java/tsptest/armcustomization/implementation/VaultImpl.java","src/main/java/tsptest/armcustomization/implementation/VaultsClientImpl.java","src/main/java/tsptest/armcustomization/implementation/VaultsImpl.java","src/main/java/tsptest/armcustomization/implementation/package-info.java","src/main/java/tsptest/armcustomization/models/Vault.java","src/main/java/tsptest/armcustomization/models/VaultProperties.java","src/main/java/tsptest/armcustomization/models/Vaults.java","src/main/java/tsptest/armcustomization/models/package-info.java","src/main/java/tsptest/armcustomization/package-info.java"]} \ No newline at end of file +{"flavor":"Azure","apiVersions":{"TspTest.ArmCustomization":"2023-12-01-preview"},"crossLanguagePackageId":"TspTest.ArmCustomization","crossLanguageVersion":"60dbe4eea940","crossLanguageDefinitions":{"tsptest.armcustomization.fluent.ArmCustomizationClient":"TspTest.ArmCustomization","tsptest.armcustomization.fluent.VaultsClient":"TspTest.ArmCustomization.Vaults","tsptest.armcustomization.fluent.VaultsClient.getByResourceGroup":"TspTest.ArmCustomization.Vaults.get","tsptest.armcustomization.fluent.VaultsClient.getByResourceGroupWithResponse":"TspTest.ArmCustomization.Vaults.get","tsptest.armcustomization.fluent.models.VaultInner":"TspTest.ArmCustomization.Vault","tsptest.armcustomization.implementation.ArmCustomizationClientBuilder":"TspTest.ArmCustomization","tsptest.armcustomization.models.VaultProperties":"TspTest.ArmCustomization.VaultProperties"},"generatedFiles":["src/main/java/module-info.java","src/main/java/tsptest/armcustomization/ArmCustomizationManager.java","src/main/java/tsptest/armcustomization/fluent/ArmCustomizationClient.java","src/main/java/tsptest/armcustomization/fluent/VaultsClient.java","src/main/java/tsptest/armcustomization/fluent/models/VaultInner.java","src/main/java/tsptest/armcustomization/fluent/models/package-info.java","src/main/java/tsptest/armcustomization/fluent/package-info.java","src/main/java/tsptest/armcustomization/implementation/ArmCustomizationClientBuilder.java","src/main/java/tsptest/armcustomization/implementation/ArmCustomizationClientImpl.java","src/main/java/tsptest/armcustomization/implementation/ResourceManagerUtils.java","src/main/java/tsptest/armcustomization/implementation/VaultImpl.java","src/main/java/tsptest/armcustomization/implementation/VaultsClientImpl.java","src/main/java/tsptest/armcustomization/implementation/VaultsImpl.java","src/main/java/tsptest/armcustomization/implementation/package-info.java","src/main/java/tsptest/armcustomization/models/Vault.java","src/main/java/tsptest/armcustomization/models/VaultProperties.java","src/main/java/tsptest/armcustomization/models/Vaults.java","src/main/java/tsptest/armcustomization/models/package-info.java","src/main/java/tsptest/armcustomization/package-info.java"]} \ No newline at end of file