diff --git a/common/changes/@typespec/compiler/fix-format-union_2023-09-19-19-07.json b/common/changes/@typespec/compiler/fix-format-union_2023-09-19-19-07.json new file mode 100644 index 00000000000..bb467b4c760 --- /dev/null +++ b/common/changes/@typespec/compiler/fix-format-union_2023-09-19-19-07.json @@ -0,0 +1,15 @@ +{ + "changes": [ + { + "packageName": "@typespec/compiler", + "comment": "Fix: Anonymous union variants were formatted with an extra leading `:`", + "type": "none" + }, + { + "packageName": "@typespec/compiler", + "comment": "Formatter: Unions and Enums members are now formatted following the same rules as model properties. An extra line will be added between members if the member is annotated with a decorator, directive or doc comment.", + "type": "none" + } + ], + "packageName": "@typespec/compiler" +} diff --git a/common/changes/@typespec/protobuf/fix-format-union_2023-09-20-20-26.json b/common/changes/@typespec/protobuf/fix-format-union_2023-09-20-20-26.json new file mode 100644 index 00000000000..8f48e4e9521 --- /dev/null +++ b/common/changes/@typespec/protobuf/fix-format-union_2023-09-20-20-26.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@typespec/protobuf", + "comment": "", + "type": "none" + } + ], + "packageName": "@typespec/protobuf" +} \ No newline at end of file diff --git a/packages/compiler/lib/decorators.tsp b/packages/compiler/lib/decorators.tsp index f8f637efc61..9d8a9845e1b 100644 --- a/packages/compiler/lib/decorators.tsp +++ b/packages/compiler/lib/decorators.tsp @@ -367,11 +367,13 @@ enum DateTimeKnownEncoding { * Encode to string. */ rfc3339: "rfc3339", + /** * RFC 7231 standard. https://www.ietf.org/rfc/rfc7231.txt * Encode to string. */ rfc7231: "rfc7231", + /** * Encode to integer */ @@ -386,6 +388,7 @@ enum DurationKnownEncoding { * ISO8601 duration */ ISO8601: "ISO8601", + /** * Encode to integer or float */ @@ -400,6 +403,7 @@ enum BytesKnownEncoding { * Encode to Base64 */ base64: "base64", + /** * Encode to Base64 Url */ diff --git a/packages/compiler/src/formatter/print/printer.ts b/packages/compiler/src/formatter/print/printer.ts index 9781a6f430e..0f5926d7175 100644 --- a/packages/compiler/src/formatter/print/printer.ts +++ b/packages/compiler/src/formatter/print/printer.ts @@ -688,18 +688,8 @@ function printEnumBlock( return "{}"; } - return group([ - "{", - indent([ - hardline, - join( - hardline, - path.map((x) => [print(x as any), ","], "members") - ), - ]), - hardline, - "}", - ]); + const body = joinMembersInBlock(path, "members", options, print, ",", hardline); + return group(["{", indent(body), hardline, "}"]); } export function printEnumMember( @@ -710,12 +700,10 @@ export function printEnumMember( const node = path.getValue(); const id = path.call(print, "id"); const value = node.value ? [": ", path.call(print, "value")] : ""; - const { decorators, multiline } = printDecorators(path, options, print, { + const { decorators } = printDecorators(path, options, print, { tryInline: DecoratorsTryInline.enumMember, }); - const propertyIndex = path.stack[path.stack.length - 2]; - const isNotFirst = typeof propertyIndex === "number" && propertyIndex > 0; - return [multiline && isNotFirst ? hardline : "", decorators, id, value]; + return [decorators, id, value]; } function printEnumSpreadMember( @@ -747,18 +735,8 @@ export function printUnionVariantsBlock( return "{}"; } - return group([ - "{", - indent([ - hardline, - join( - hardline, - path.map((x) => [print(x as any), ","], "options") - ), - ]), - hardline, - "}", - ]); + const body = joinMembersInBlock(path, "options", options, print, ",", hardline); + return group(["{", indent(body), hardline, "}"]); } export function printUnionVariant( @@ -766,12 +744,11 @@ export function printUnionVariant( options: TypeSpecPrettierOptions, print: PrettierChildPrint ) { - const id = path.call(print, "id"); - const value = [": ", path.call(print, "value")]; + const id = path.node.id === undefined ? "" : [path.call(print, "id"), ": "]; const { decorators } = printDecorators(path, options, print, { tryInline: DecoratorsTryInline.unionVariant, }); - return [decorators, id, value]; + return [decorators, id, path.call(print, "value")]; } export function printInterfaceStatement( @@ -965,7 +942,9 @@ export function printModelExpression( const properties = node.properties.length === 0 ? "" - : indent(joinPropertiesInBlock(path as any, options, print, ifBreak(",", ", "), softline)); + : indent( + joinMembersInBlock(path, "properties", options, print, ifBreak(",", ", "), softline) + ); return group([properties, softline]); } } @@ -1019,28 +998,29 @@ function printModelPropertiesBlock( const lineDoc = tryInline ? softline : hardline; const seperator = isModelAValue(path) ? "," : ";"; - const body = [joinPropertiesInBlock(path as any, options, print, seperator, lineDoc)]; + const body = [joinMembersInBlock(path, "properties", options, print, seperator, lineDoc)]; if (nodeHasComments) { body.push(printDanglingComments(path, options, { sameIndent: true })); } return group(["{", indent(body), lineDoc, "}"]); } -function joinPropertiesInBlock( - path: AstPath< - Node & { - properties: readonly ( - | ModelPropertyNode - | ModelSpreadPropertyNode - | ProjectionModelPropertyNode - | ProjectionModelSpreadPropertyNode - )[]; - } - >, +/** + * Join members nodes that are in a block by adding extra new lines when needed.(e.g. when there are decorators or doc comments ) + * @param path Prettier AST Path. + * @param options Prettier options + * @param print Prettier print callback + * @param separator Separator + * @param regularLine What line to use when we should split lines + * @returns + */ +function joinMembersInBlock( + path: AstPath, + member: keyof T, options: TypeSpecPrettierOptions, print: PrettierChildPrint, separator: Doc, - regularLine: Doc + regularLine: Doc = hardline ): Doc { const doc: Doc[] = [regularLine]; const propertyContainerNode = path.getValue(); @@ -1048,8 +1028,8 @@ function joinPropertiesInBlock( let newLineBeforeNextProp = false; path.each((item, propertyIndex) => { const isFirst = propertyIndex === 0; - const isLast = propertyIndex === propertyContainerNode.properties.length - 1; - const shouldWrapInNewLines = shouldWrapPropertyInNewLines(item as any, options); + const isLast = propertyIndex === (propertyContainerNode[member] as any).length - 1; + const shouldWrapInNewLines = shouldWrapMemberInNewLines(item as any, options); if ((newLineBeforeNextProp || shouldWrapInNewLines) && !isFirst) { doc.push(hardline); @@ -1065,7 +1045,7 @@ function joinPropertiesInBlock( newLineBeforeNextProp = true; } } - }, "properties"); + }, member as any); return doc; } @@ -1075,10 +1055,13 @@ function joinPropertiesInBlock( * - has decorators on lines above * - has leading comments */ -function shouldWrapPropertyInNewLines( +function shouldWrapMemberInNewLines( path: AstPath< | ModelPropertyNode | ModelSpreadPropertyNode + | EnumMemberNode + | EnumSpreadMemberNode + | UnionVariantNode | ProjectionModelPropertyNode | ProjectionModelSpreadPropertyNode >, @@ -1086,7 +1069,9 @@ function shouldWrapPropertyInNewLines( ): boolean { const node = path.getValue(); return ( - ((node.kind === SyntaxKind.ModelProperty || node.kind === SyntaxKind.ProjectionModelProperty) && + (node.kind !== SyntaxKind.ModelSpreadProperty && + node.kind !== SyntaxKind.ProjectionModelSpreadProperty && + node.kind !== SyntaxKind.EnumSpreadMember && shouldDecoratorBreakLine(path as any, options, { tryInline: DecoratorsTryInline.modelProperty, })) || @@ -1309,7 +1294,7 @@ function getLastStatement(statements: Statement[]): Statement | undefined { export function printUnion( path: AstPath, - options: object, + options: TypeSpecPrettierOptions, print: PrettierChildPrint ) { const node = path.getValue(); diff --git a/packages/compiler/test/formatter/formatter.test.ts b/packages/compiler/test/formatter/formatter.test.ts index b5435a2423d..5c7f9faf0bc 100644 --- a/packages/compiler/test/formatter/formatter.test.ts +++ b/packages/compiler/test/formatter/formatter.test.ts @@ -1572,6 +1572,73 @@ enum Foo { }); }); + describe("union", () => { + it("format simple union", async () => { + await assertFormat({ + code: ` +union Foo { A, B} +union Bar + { A, + B} +`, + expected: ` +union Foo { + A, + B, +} +union Bar { + A, + B, +} +`, + }); + }); + + it("format named union", async () => { + await assertFormat({ + code: ` + union Foo { a: A, b: B} +`, + expected: ` +union Foo { + a: A, + b: B, +} + +`, + }); + }); + + it("separate members if there is decorators", async () => { + await assertFormat({ + code: ` +union Foo { + @doc("foo") + a: A, @doc("bar") + b : B, + + + + @doc("third") + c : C} + +`, + expected: ` +union Foo { + @doc("foo") + a: A, + + @doc("bar") + b: B, + + @doc("third") + c: C, +} +`, + }); + }); + }); + describe("namespaces", () => { it("format global namespace", async () => { await assertFormat({ diff --git a/packages/protobuf/lib/proto.tsp b/packages/protobuf/lib/proto.tsp index fa892bfbd7e..1d1e7a53607 100644 --- a/packages/protobuf/lib/proto.tsp +++ b/packages/protobuf/lib/proto.tsp @@ -265,16 +265,19 @@ enum StreamMode { * other until the connections are closed. */ Duplex, + /** * The input of the operation is streaming. The client will send a stream of events; and, once the stream is closed, * the service will respond with a message. */ In, + /** * The output of the operation is streaming. The client will send a message to the service, and the service will send * a stream of events back to the client. */ Out, + /** * Neither the input nor the output are streaming. This is the default mode of an operation without the `@stream` * decorator.