diff --git a/.chronus/changes/feat-fix-indentation-in-doc-comments-2024-4-20-14-31-22.md b/.chronus/changes/feat-fix-indentation-in-doc-comments-2024-4-20-14-31-22.md new file mode 100644 index 0000000000..f6d9cd81b0 --- /dev/null +++ b/.chronus/changes/feat-fix-indentation-in-doc-comments-2024-4-20-14-31-22.md @@ -0,0 +1,12 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: internal +packages: + - "@typespec/http" + - "@typespec/protobuf" + - "@typespec/rest" + - "@typespec/versioning" + - "@typespec/xml" +--- + +Regen docs diff --git a/.chronus/changes/feat-fix-indentation-in-doc-comments-2024-4-20-14-54-41.md b/.chronus/changes/feat-fix-indentation-in-doc-comments-2024-4-20-14-54-41.md new file mode 100644 index 0000000000..28b7152d03 --- /dev/null +++ b/.chronus/changes/feat-fix-indentation-in-doc-comments-2024-4-20-14-54-41.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Preserve leading whitespace in fenced blocks in doc comments diff --git a/docs/libraries/http/reference/decorators.md b/docs/libraries/http/reference/decorators.md index 24c42c8d45..a9dfefd0dc 100644 --- a/docs/libraries/http/reference/decorators.md +++ b/docs/libraries/http/reference/decorators.md @@ -411,8 +411,8 @@ namespace PetStore; ```typespec @server("https://{region}.foo.com", "Regional endpoint", { -@doc("Region name") -region?: string = "westus", + @doc("Region name") + region?: string = "westus", }) ``` diff --git a/docs/libraries/xml/reference/decorators.md b/docs/libraries/xml/reference/decorators.md index 0f384c07cb..d1917fce4d 100644 --- a/docs/libraries/xml/reference/decorators.md +++ b/docs/libraries/xml/reference/decorators.md @@ -36,7 +36,7 @@ model Blob { ```xml -abcdef + abcdef ``` @@ -85,9 +85,9 @@ model Book { ```xml -string -string -string + string + string + string ``` @@ -192,11 +192,11 @@ model Pet { ```xml - - -string - - + + + string + + ``` @@ -210,9 +210,9 @@ model Pet { ```xml - -string - + + string + ``` @@ -226,9 +226,9 @@ model BlobName { ```xml - -abcdef - + + abcdef + ``` @@ -242,6 +242,6 @@ model BlobName { ```xml -abcdef + abcdef ``` diff --git a/docs/standard-library/built-in-decorators.md b/docs/standard-library/built-in-decorators.md index 35d318e512..b705b4b502 100644 --- a/docs/standard-library/built-in-decorators.md +++ b/docs/standard-library/built-in-decorators.md @@ -149,9 +149,9 @@ Provide an alternative name for this type when serialized to the given mime type ```typespec model Certificate { -@encodedName("application/json", "exp") -@encodedName("application/xml", "expiry") -expireAt: int32; + @encodedName("application/json", "exp") + @encodedName("application/xml", "expiry") + expireAt: int32; } ``` @@ -160,7 +160,7 @@ expireAt: int32; ```typespec @encodedName("application/merge-patch+json", "exp") -^ error cannot use subtype + ^ error cannot use subtype ``` @@ -183,8 +183,8 @@ None ```typespec @error model PetStoreError { -code: string; -message: string; + code: string; + message: string; } ``` @@ -262,8 +262,8 @@ Specifies how a templated type should name their instances. ```typespec @friendlyName("{name}List", T) model List { -value: Item[]; -nextLink: string; + value: Item[]; + nextLink: string; } ``` @@ -324,7 +324,7 @@ Mark a model property as the key to identify instances of that type ```typespec model Pet { -@key id: string; + @key id: string; } ``` @@ -352,8 +352,8 @@ Provide a set of known values to a string type. scalar ErrorCode extends string; enum KnownErrorCode { -NotFound, -Invalid, + NotFound, + Invalid, } ``` @@ -670,8 +670,8 @@ Provide an alternative name for this type. ```typespec model Certificate { -@projectedName("json", "exp") -expireAt: int32; + @projectedName("json", "exp") + expireAt: int32; } ``` @@ -855,12 +855,12 @@ See also: [Automatic visibility](https://typespec.io/docs/libraries/http/operati ```typespec model Dog { -// the service will generate an ID, so you don't need to send it. -@visibility("read") id: int32; -// the service will store this secret name, but won't ever return it -@visibility("create", "update") secretName: string; -// the regular name is always present -name: string; + // the service will generate an ID, so you don't need to send it. + @visibility("read") id: int32; + // the service will store this secret name, but won't ever return it + @visibility("create", "update") secretName: string; + // the regular name is always present + name: string; } ``` @@ -977,9 +977,9 @@ not necessary to use this decorator. ```typespec model Dog { -@visibility("read") id: int32; -@visibility("create", "update") secretName: string; -name: string; + @visibility("read") id: int32; + @visibility("create", "update") secretName: string; + name: string; } // The spread operator will copy all the properties of Dog into DogRead, @@ -990,14 +990,14 @@ name: string; // properties are kept. @withVisibility("create", "update") model DogCreateOrUpdate { -...Dog; + ...Dog; } // In this case the id and name properties are kept and the secretName property // is removed. @withVisibility("read") model DogRead { -...Dog; + ...Dog; } ``` diff --git a/packages/compiler/generated-defs/TypeSpec.ts b/packages/compiler/generated-defs/TypeSpec.ts index eb92bd45eb..489bc02cad 100644 --- a/packages/compiler/generated-defs/TypeSpec.ts +++ b/packages/compiler/generated-defs/TypeSpec.ts @@ -192,8 +192,8 @@ export type ServiceDecorator = ( * ```typespec * @error * model PetStoreError { - * code: string; - * message: string; + * code: string; + * message: string; * } * ``` */ @@ -415,8 +415,8 @@ export type TagDecorator = ( * ```typespec * @friendlyName("{name}List", T) * model List { - * value: Item[]; - * nextLink: string; + * value: Item[]; + * nextLink: string; * } * ``` */ @@ -437,8 +437,8 @@ export type FriendlyNameDecorator = ( * scalar ErrorCode extends string; * * enum KnownErrorCode { - * NotFound, - * Invalid, + * NotFound, + * Invalid, * } * ``` */ @@ -455,7 +455,7 @@ export type KnownValuesDecorator = ( * @example * ```typespec * model Pet { - * @key id: string; + * @key id: string; * } * ``` */ @@ -494,8 +494,8 @@ export type OverloadDecorator = ( * @example * ```typespec * model Certificate { - * @projectedName("json", "exp") - * expireAt: int32; + * @projectedName("json", "exp") + * expireAt: int32; * } * ``` */ @@ -514,16 +514,16 @@ export type ProjectedNameDecorator = ( * @example * ```typespec * model Certificate { - * @encodedName("application/json", "exp") - * @encodedName("application/xml", "expiry") - * expireAt: int32; + * @encodedName("application/json", "exp") + * @encodedName("application/xml", "expiry") + * expireAt: int32; * } * ``` * @example Invalid values * * ```typespec * @encodedName("application/merge-patch+json", "exp") - * ^ error cannot use subtype + * ^ error cannot use subtype * ``` */ export type EncodedNameDecorator = ( @@ -581,12 +581,12 @@ export type DiscriminatorDecorator = ( * @example * ```typespec * model Dog { - * // the service will generate an ID, so you don't need to send it. - * @visibility("read") id: int32; - * // the service will store this secret name, but won't ever return it - * @visibility("create", "update") secretName: string; - * // the regular name is always present - * name: string; + * // the service will generate an ID, so you don't need to send it. + * @visibility("read") id: int32; + * // the service will store this secret name, but won't ever return it + * @visibility("create", "update") secretName: string; + * // the regular name is always present + * name: string; * } * ``` */ @@ -611,9 +611,9 @@ export type VisibilityDecorator = ( * @example * ```typespec * model Dog { - * @visibility("read") id: int32; - * @visibility("create", "update") secretName: string; - * name: string; + * @visibility("read") id: int32; + * @visibility("create", "update") secretName: string; + * name: string; * } * * // The spread operator will copy all the properties of Dog into DogRead, @@ -624,14 +624,14 @@ export type VisibilityDecorator = ( * // properties are kept. * @withVisibility("create", "update") * model DogCreateOrUpdate { - * ...Dog; + * ...Dog; * } * * // In this case the id and name properties are kept and the secretName property * // is removed. * @withVisibility("read") * model DogRead { - * ...Dog; + * ...Dog; * } * ``` */ diff --git a/packages/compiler/src/core/parser.ts b/packages/compiler/src/core/parser.ts index 12dcc2cf19..af9c92ffed 100644 --- a/packages/compiler/src/core/parser.ts +++ b/packages/compiler/src/core/parser.ts @@ -2815,11 +2815,32 @@ function createParser(code: string | SourceFile, options: ParseOptions = {}): Pa nextToken(); start = tokenPos(); while (parseOptional(Token.Whitespace)); - if (parseOptional(Token.Star)) { + if (!parseOptional(Token.Star)) { + break; + } + if (!inCodeFence) { parseOptional(Token.Whitespace); start = tokenPos(); break; } + // If we are in a code fence we want to preserve the leading whitespace + // except for the first space after the star which is used as indentation. + const whitespaceStart = tokenPos(); + parseOptional(Token.Whitespace); + + // This `min` handles the case when there is no whitespace after the + // star e.g. a case like this: + // + // /** + // *``` + // *foo-bar + // *``` + // */ + // + // Not having space after the star isn't idiomatic, but we support this. + // `whitespaceStart + 1` strips the first space before `foo-bar` if there + // is a space after the star (the idiomatic case). + start = Math.min(whitespaceStart + 1, tokenPos()); break; case Token.EndOfFile: break loop; diff --git a/packages/compiler/test/parser.test.ts b/packages/compiler/test/parser.test.ts index d65cb17f0d..ec3074d363 100644 --- a/packages/compiler/test/parser.test.ts +++ b/packages/compiler/test/parser.test.ts @@ -1015,8 +1015,14 @@ describe("compiler: parser", () => { * * \`\`\` * This is not a @tag because we're in a code fence. + * This is indented code. * \`\`\` * + *\`\`\` + *This code fence is glued + *to the stars + *\`\`\` + * * \`This is not a @tag either because we're in a code span\`. * * @param x the param @@ -1036,7 +1042,17 @@ describe("compiler: parser", () => { strictEqual( docs[0].content[0].text, - "This one has a `code span` and a code fence and it spreads over\nmore than one line.\n\n```\nThis is not a @tag because we're in a code fence.\n```\n\n`This is not a @tag either because we're in a code span`." + "This one has a `code span` and a code fence and it spreads over\n" + + "more than one line.\n\n" + + "```\n" + + "This is not a @tag because we're in a code fence.\n" + + " This is indented code.\n" + + "```\n\n" + + "```\n" + + "This code fence is glued\n" + + "to the stars\n" + + "```\n\n" + + "`This is not a @tag either because we're in a code span`." ); strictEqual(docs[0].tags.length, 6); const [xParam, yParam, tTemplate, uTemplate, returns, pretend] = docs[0].tags; diff --git a/packages/http/README.md b/packages/http/README.md index 95f35ff1d6..06511ac3d5 100644 --- a/packages/http/README.md +++ b/packages/http/README.md @@ -458,8 +458,8 @@ namespace PetStore; ```typespec @server("https://{region}.foo.com", "Regional endpoint", { -@doc("Region name") -region?: string = "westus", + @doc("Region name") + region?: string = "westus", }) ``` diff --git a/packages/http/generated-defs/TypeSpec.Http.ts b/packages/http/generated-defs/TypeSpec.Http.ts index 449ecf2904..6058763fc1 100644 --- a/packages/http/generated-defs/TypeSpec.Http.ts +++ b/packages/http/generated-defs/TypeSpec.Http.ts @@ -187,8 +187,8 @@ export type HeadDecorator = (context: DecoratorContext, target: Operation) => vo * * ```typespec * @server("https://{region}.foo.com", "Regional endpoint", { - * @doc("Region name") - * region?: string = "westus", + * @doc("Region name") + * region?: string = "westus", * }) * ``` */ diff --git a/packages/http/lib/auth.tsp b/packages/http/lib/auth.tsp index f1ecf4eb93..4d81e43343 100644 --- a/packages/http/lib/auth.tsp +++ b/packages/http/lib/auth.tsp @@ -19,12 +19,12 @@ enum AuthType { } /** - * Basic authentication is a simple authentication scheme built into the HTTP protocol. - * The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password. - * For example, to authorize as demo / `p@55w0rd` the client would send - * ``` - * Authorization: Basic ZGVtbzpwQDU1dzByZA== - * ``` + * Basic authentication is a simple authentication scheme built into the HTTP protocol. + * The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password. + * For example, to authorize as demo / `p@55w0rd` the client would send + * ``` + * Authorization: Basic ZGVtbzpwQDU1dzByZA== + * ``` */ @doc("") model BasicAuth { @@ -40,7 +40,7 @@ model BasicAuth { * The name “Bearer authentication” can be understood as “give access to the bearer of this token.” The bearer token is a cryptic string, usually generated by the server in response to a login request. * The client must send this token in the Authorization header when making requests to protected resources: * ``` - * Authorization: Bearer + * Authorization: Bearer * ``` */ @doc("") diff --git a/packages/http/src/types.ts b/packages/http/src/types.ts index e7ab7deb5d..e69de12398 100644 --- a/packages/http/src/types.ts +++ b/packages/http/src/types.ts @@ -59,7 +59,7 @@ export interface HttpAuthBase { * The client sends HTTP requests with the Authorization header that contains the word Basic word followed by a space and a base64-encoded string username:password. * For example, to authorize as demo / p@55w0rd the client would send * ``` - * Authorization: Basic ZGVtbzpwQDU1dzByZA== + * Authorization: Basic ZGVtbzpwQDU1dzByZA== * ``` */ export interface BasicAuth extends HttpAuthBase { diff --git a/packages/json-schema/test/doc.test.ts b/packages/json-schema/test/doc.test.ts new file mode 100644 index 0000000000..0a2083551f --- /dev/null +++ b/packages/json-schema/test/doc.test.ts @@ -0,0 +1,31 @@ +import assert from "assert"; +import { describe, it } from "vitest"; +import { emitSchema } from "./utils.js"; + +describe("doc", () => { + it("sets description from doc comments", async () => { + const schemas = await emitSchema(` + /** + * \`\`\`toml + * deny = [ + * { name = "simple" }, + * { name = "simple", version = "*" }, + * { name = "simple", wrappers = ["example"] } + * ] + * \`\`\` + */ + scalar PackageSpec extends string; + `); + + assert.deepStrictEqual( + schemas["PackageSpec.json"].description, + `\`\`\`toml +deny = [ + { name = "simple" }, + { name = "simple", version = "*" }, + { name = "simple", wrappers = ["example"] } +] +\`\`\`` + ); + }); +}); diff --git a/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts b/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts index b7d148c44c..65997979dd 100644 --- a/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts +++ b/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts @@ -43,8 +43,8 @@ export type MessageDecorator = (context: DecoratorContext, target: Type) => void * @example * ```typespec * model ExampleMessage { - * @field(1) - * test: string; + * @field(1) + * test: string; * } * ``` */ @@ -82,7 +82,7 @@ export type FieldDecorator = ( * // Reserve the fields 8-15 inclusive, 100, and the field name "test" within a model. * @reserve([8, 15], 100, "test") * model Example { - * // ... + * // ... * } * ``` */ diff --git a/packages/rest/generated-defs/TypeSpec.Rest.ts b/packages/rest/generated-defs/TypeSpec.Rest.ts index 5106d6e089..a4e7f920b3 100644 --- a/packages/rest/generated-defs/TypeSpec.Rest.ts +++ b/packages/rest/generated-defs/TypeSpec.Rest.ts @@ -13,7 +13,7 @@ import type { * ```typespec * @autoRoute * interface Pets { - * get(@segment("pets") @path id: string): void; //-> route: /pets/{id} + * get(@segment("pets") @path id: string): void; //-> route: /pets/{id} * } * ``` */ diff --git a/packages/versioning/generated-defs/TypeSpec.Versioning.ts b/packages/versioning/generated-defs/TypeSpec.Versioning.ts index 1048c63004..e3b18b71e7 100644 --- a/packages/versioning/generated-defs/TypeSpec.Versioning.ts +++ b/packages/versioning/generated-defs/TypeSpec.Versioning.ts @@ -22,9 +22,9 @@ import type { * @versioned(Versions) * namespace MyService; * enum Versions { - * v1, - * v2, - * v3, + * v1, + * v2, + * v3, * } * ``` */ @@ -50,12 +50,12 @@ export type VersionedDecorator = ( * @versioned(Versions) * namespace MyService1; * enum Version { - * @useDependency(MyLib.Versions.v1_1) // V1 use lib v1_1 - * v1, - * @useDependency(MyLib.Versions.v1_1) // V2 use lib v1_1 - * v2, - * @useDependency(MyLib.Versions.v2) // V3 use lib v2 - * v3, + * @useDependency(MyLib.Versions.v1_1) // V1 use lib v1_1 + * v1, + * @useDependency(MyLib.Versions.v1_1) // V2 use lib v1_1 + * v2, + * @useDependency(MyLib.Versions.v2) // V3 use lib v2 + * v3, * } * ``` */ @@ -78,10 +78,10 @@ export type UseDependencyDecorator = ( * model AlsoAddedInV2 {} * * model Foo { - * name: string; + * name: string; * - * @added(Versions.v3) - * addedInV3: string; + * @added(Versions.v3) + * addedInV3: string; * } * ``` */ @@ -113,10 +113,10 @@ export type AddedDecorator = ( * model AlsoRemovedInV2 {} * * model Foo { - * name: string; + * name: string; * - * @removed(Versions.v3) - * removedInV3: string; + * @removed(Versions.v3) + * removedInV3: string; * } * ``` */ @@ -169,9 +169,9 @@ export type RenamedFromDecorator = ( * @example * ```tsp * model Foo { - * name: string; - * @madeOptional(Versions.v2) - * nickname: string; + * name: string; + * @madeOptional(Versions.v2) + * nickname: string; * } * ``` */ @@ -188,9 +188,9 @@ export type MadeOptionalDecorator = ( * @example * ```tsp * model Foo { - * name: string; - * @madeRequired(Versions.v2) - * nickname: string; + * name: string; + * @madeRequired(Versions.v2) + * nickname: string; * } * ``` */ diff --git a/packages/versioning/lib/decorators.tsp b/packages/versioning/lib/decorators.tsp index 042e2cb31f..ab9e8306ad 100644 --- a/packages/versioning/lib/decorators.tsp +++ b/packages/versioning/lib/decorators.tsp @@ -11,13 +11,13 @@ namespace TypeSpec { * @example * * ```tsp - * @versioned(Versions) - * namespace MyService; - * enum Versions { - * v1, - * v2, - * v3, - * } + * @versioned(Versions) + * namespace MyService; + * enum Versions { + * v1, + * v2, + * v3, + * } * ``` */ extern dec versioned(target: Namespace, versions: Enum); diff --git a/packages/xml/README.md b/packages/xml/README.md index ccc52c5a9a..4e255b3ea3 100644 --- a/packages/xml/README.md +++ b/packages/xml/README.md @@ -46,7 +46,7 @@ model Blob { ```xml -abcdef + abcdef ``` @@ -95,9 +95,9 @@ model Book { ```xml -string -string -string + string + string + string ``` @@ -202,11 +202,11 @@ model Pet { ```xml - - -string - - + + + string + + ``` @@ -220,9 +220,9 @@ model Pet { ```xml - -string - + + string + ``` @@ -236,9 +236,9 @@ model BlobName { ```xml - -abcdef - + + abcdef + ``` @@ -252,6 +252,6 @@ model BlobName { ```xml -abcdef + abcdef ``` diff --git a/packages/xml/generated-defs/TypeSpec.Xml.ts b/packages/xml/generated-defs/TypeSpec.Xml.ts index 053f99a305..2944a85814 100644 --- a/packages/xml/generated-defs/TypeSpec.Xml.ts +++ b/packages/xml/generated-defs/TypeSpec.Xml.ts @@ -9,17 +9,17 @@ import type { DecoratorContext, Enum, ModelProperty, Type } from "@typespec/comp * ```tsp * @name("XmlBook") * model Book { - * @name("XmlId") id: string; - * @encodedName("application/xml", "XmlName") name: string; - * content: string; + * @name("XmlId") id: string; + * @encodedName("application/xml", "XmlName") name: string; + * content: string; * } * ``` * * ```xml * - * string - * string - * string + * string + * string + * string * * ``` */ @@ -32,20 +32,20 @@ export type NameDecorator = (context: DecoratorContext, target: Type, name: stri * * ```tsp * model Blob { - * id: string; + * id: string; * } * ``` * * ```xml * - * abcdef + * abcdef * * ``` * @example With `@attribute` * * ```tsp * model Blob { - * @attribute id: string; + * @attribute id: string; * } * ``` * @@ -64,60 +64,60 @@ export type AttributeDecorator = (context: DecoratorContext, target: ModelProper * * ```tsp * model Pet { - * tags: Tag[]; + * tags: Tag[]; * } * ``` * * ```xml * - * - * - * string - * - * + * + * + * string + * + * * * ``` * @example Array property with `@unwrapped` * * ```tsp * model Pet { - * @unwrapped tags: Tag[]; + * @unwrapped tags: Tag[]; * } * ``` * * ```xml * - * - * string - * + * + * string + * * * ``` * @example String property default * * ```tsp * model BlobName { - * content: string; + * content: string; * } * ``` * * ```xml * - * - * abcdef - * + * + * abcdef + * * * ``` * @example Array property with `@unwrapped` * * ```tsp * model BlobName { - * @unwrapped content: string; + * @unwrapped content: string; * } * ``` * * ```xml * - * abcdef + * abcdef * * ``` */ @@ -135,10 +135,10 @@ export type UnwrappedDecorator = (context: DecoratorContext, target: ModelProper * ```tsp * @ns( "https://example.com/ns1", "ns1") * model Foo { - * @ns("https://example.com/ns1", "ns1") - * bar: string - * @ns("https://example.com/ns2", "ns2") - * bar: string + * @ns("https://example.com/ns1", "ns1") + * bar: string + * @ns("https://example.com/ns2", "ns2") + * bar: string * } * ``` * @example With enum @@ -146,16 +146,16 @@ export type UnwrappedDecorator = (context: DecoratorContext, target: ModelProper * ```tsp * @Xml.nsDeclarations * enum Namespaces { - * ns1: "https://example.com/ns1", - * ns2: "https://example.com/ns2" + * ns1: "https://example.com/ns1", + * ns2: "https://example.com/ns2" * } * * @Xml.ns(Namespaces.ns1) * model Foo { - * @Xml.ns(Namespaces.ns1) - * bar: string - * @Xml.ns(Namespaces.ns2) - * bar: string + * @Xml.ns(Namespaces.ns1) + * bar: string + * @Xml.ns(Namespaces.ns2) + * bar: string * } * ``` */