Skip to content

Commit c01f73a

Browse files
oha-4claude
andcommitted
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) <noreply@anthropic.com>
1 parent ef4eacb commit c01f73a

3 files changed

Lines changed: 83 additions & 9 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
changeKind: fix
3+
packages:
4+
- "@typespec/compiler"
5+
---
6+
7+
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.

packages/compiler/src/formatter/print/printer.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,17 +1043,40 @@ export function printArrayLiteral(
10431043
]);
10441044
}
10451045

1046+
// When the base is a multi-argument template, its argument list breaks on its
1047+
// own, so keep `is`/`extends` inline instead of breaking the keyword too (#11009).
1048+
function printHeritageClause<T extends Node>(
1049+
path: AstPath<T>,
1050+
print: PrettierChildPrint,
1051+
keyword: string,
1052+
propertyName: keyof T,
1053+
): Doc {
1054+
const ref = path.node[propertyName] as Node | undefined;
1055+
if (!ref) {
1056+
return "";
1057+
}
1058+
const printed = [`${keyword} `, path.call(print, propertyName as any)];
1059+
if (isMultiArgTemplateReference(ref)) {
1060+
return [" ", printed];
1061+
}
1062+
return group(indent([ifBreak(line, " "), printed]));
1063+
}
1064+
1065+
function isMultiArgTemplateReference(node: Node): boolean {
1066+
return (
1067+
node.kind === SyntaxKind.TypeReference && (node as TypeReferenceNode).arguments.length >= 2
1068+
);
1069+
}
1070+
10461071
export function printModelStatement(
10471072
path: AstPath<ModelStatementNode>,
10481073
options: TypeSpecPrettierOptions,
10491074
print: PrettierChildPrint,
10501075
) {
10511076
const node = path.node;
10521077
const id = path.call(print, "id");
1053-
const heritage = node.extends
1054-
? [ifBreak(line, " "), "extends ", path.call(print, "extends")]
1055-
: "";
1056-
const isBase = node.is ? [ifBreak(line, " "), "is ", path.call(print, "is")] : "";
1078+
const heritage = printHeritageClause(path, print, "extends", "extends");
1079+
const isBase = printHeritageClause(path, print, "is", "is");
10571080
const generic = printTemplateParameters(path, options, print, "templateParameters");
10581081
const nodeHasComments = hasComments(node, CommentCheckFlags.Dangling);
10591082
const shouldPrintBody = nodeHasComments || !(node.properties.length === 0 && node.is);
@@ -1064,7 +1087,8 @@ export function printModelStatement(
10641087
"model ",
10651088
id,
10661089
generic,
1067-
group(indent(["", heritage, isBase])),
1090+
heritage,
1091+
isBase,
10681092
body,
10691093
];
10701094
}
@@ -1240,9 +1264,7 @@ function printScalarStatement(
12401264
const id = path.call(print, "id");
12411265
const template = printTemplateParameters(path, options, print, "templateParameters");
12421266

1243-
const heritage = node.extends
1244-
? [ifBreak(line, " "), "extends ", path.call(print, "extends")]
1245-
: "";
1267+
const heritage = printHeritageClause(path, print, "extends", "extends");
12461268
const nodeHasComments = hasComments(node, CommentCheckFlags.Dangling);
12471269
const shouldPrintBody = nodeHasComments || !(node.members.length === 0);
12481270

@@ -1253,7 +1275,7 @@ function printScalarStatement(
12531275
"scalar ",
12541276
id,
12551277
template,
1256-
group(indent(["", heritage])),
1278+
heritage,
12571279
members,
12581280
];
12591281
}

packages/compiler/test/formatter/formatter.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,21 @@ model Foo extends SuperExtremeAndVeryVeryVeryVeryVeryVeryLongModelThatWillBeTo
207207
expected: `
208208
model Foo
209209
extends SuperExtremeAndVeryVeryVeryVeryVeryVeryLongModelThatWillBeTooLong {}
210+
`,
211+
});
212+
});
213+
214+
it("keeps extends inline and breaks the template arguments when too long", async () => {
215+
await assertFormat({
216+
code: `
217+
model Foo extends Base<FirstArgumentTypeName, SecondArgumentTypeName, ThirdArgumentTypeName> {}
218+
`,
219+
expected: `
220+
model Foo extends Base<
221+
FirstArgumentTypeName,
222+
SecondArgumentTypeName,
223+
ThirdArgumentTypeName
224+
> {}
210225
`,
211226
});
212227
});
@@ -255,6 +270,21 @@ model Foo is SuperExtremeAndVeryVeryVeryVeryVeryVeryLongLongLongModelThatWillB
255270
expected: `
256271
model Foo
257272
is SuperExtremeAndVeryVeryVeryVeryVeryVeryLongLongLongModelThatWillBeTooLong;
273+
`,
274+
});
275+
});
276+
277+
it("keeps is inline and breaks the template arguments when too long", async () => {
278+
await assertFormat({
279+
code: `
280+
model Foo is Base<FirstArgumentTypeName, SecondArgumentTypeName, ThirdArgumentTypeName>;
281+
`,
282+
expected: `
283+
model Foo is Base<
284+
FirstArgumentTypeName,
285+
SecondArgumentTypeName,
286+
ThirdArgumentTypeName
287+
>;
258288
`,
259289
});
260290
});
@@ -809,6 +839,21 @@ scalar Foo extends string;
809839
});
810840
});
811841

842+
it("keeps extends inline and breaks the template arguments when too long", async () => {
843+
await assertFormat({
844+
code: `
845+
scalar Foo extends Base<FirstArgumentTypeName, SecondArgumentTypeName, ThirdArgumentTypeName>;
846+
`,
847+
expected: `
848+
scalar Foo extends Base<
849+
FirstArgumentTypeName,
850+
SecondArgumentTypeName,
851+
ThirdArgumentTypeName
852+
>;
853+
`,
854+
});
855+
});
856+
812857
it("format with template parameters", async () => {
813858
await assertFormat({
814859
code: `

0 commit comments

Comments
 (0)