Skip to content
Merged
8 changes: 8 additions & 0 deletions .chronus/changes/tspd-improvements-2026-4-11-16-35-10-2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: feature
packages:
- "@typespec/tspd"
---

Improve render of complex emitter options
8 changes: 8 additions & 0 deletions .chronus/changes/tspd-improvements-2026-4-11-16-35-10.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: feature
packages:
- "@typespec/tspd"
---

Render documentation for sub exports
2 changes: 2 additions & 0 deletions packages/http-client-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo

**Type:** `string`

**Default:** `"test-package"`

Name of the package as it will be in package.json
14 changes: 14 additions & 0 deletions packages/http-server-csharp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,36 +52,48 @@ Skips formatting of generated C# Types. By default, C# files are formatted using

**Type:** `"models" | "all"`

**Default:** `"all"`

Chooses which service artifacts to emit. choices include 'models' or 'all' artifacts.

### `emit-mocks`

**Type:** `"mocks-and-project-files" | "mocks-only" | "none"`

**Default:** `"none"`

Emits mock implementations of business logic, setup code, and project files, enabling the service to respond to requests before a real implementation is provided

### `use-swaggerui`

**Type:** `boolean`

**Default:** `false`

Configure a Swagger UI endpoint in the development configuration

### `openapi-path`

**Type:** `string`

**Default:** `null`

Use openapi at the given path for generating SwaggerUI endpoints. By default, this will be 'openapi/openapi.yaml' if the 'use-swaggerui' option is enabled.

### `overwrite`

**Type:** `boolean`

**Default:** `false`

When generating mock and project files, overwrite any existing files with the same name.

### `project-name`

**Type:** `string`

**Default:** `"ServiceProject"`

The name of the generated project.

### `http-port`
Expand All @@ -100,4 +112,6 @@ The service https port when hosting the project locally.

**Type:** `"array" | "enumerable"`

**Default:** `"array"`

Specifies the collection type to use: 'array' or 'enumerable'. The default is 'array'.
8 changes: 8 additions & 0 deletions packages/http-server-js/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo

**Type:** `boolean`

**Default:** `false`

If set to `true`, the emitter will generate a router that exposes an Express.js middleware function in addition to the ordinary Node.js HTTP server router.

If this option is not set to `true`, the `expressMiddleware` property will not be present on the generated router.
Expand All @@ -56,18 +58,24 @@ If this option is not set to `true`, the `expressMiddleware` property will not b

**Type:** `"temporal-polyfill" | "temporal" | "date-duration"`

**Default:** `"temporal-polyfill"`

The type of datetime models to use for TypeSpecs DateTime and Duration types.

### `omit-unreachable-types`

**Type:** `boolean`

**Default:** `false`

By default, the emitter will create interfaces that represent all models in the service namespace. If this option is set to `true`, the emitter will only emit those types that are reachable from an HTTP operation.

### `no-format`

**Type:** `boolean`

**Default:** `false`

If set to `true`, the emitter will not format the generated code using Prettier.

## Functionality and generated code
Expand Down
4 changes: 4 additions & 0 deletions packages/json-schema/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ When true, emit all references as json schema files, even if the referenced type

**Type:** `boolean`

**Default:** `false`

If true, then for models emitted as object schemas we default `unevaluatedProperties` to `{ not: {} }`,
if not explicitly specified elsewhere.
Default: `false`
Expand All @@ -105,6 +107,8 @@ Default: `false`

**Type:** `"ignore" | "oneOf" | "anyOf"`

**Default:** `"ignore"`

Strategy for emitting models with the @discriminator decorator:

- ignore: Emit as regular object schema (default). Derived models use allOf to reference their base model.
Expand Down
38 changes: 35 additions & 3 deletions packages/openapi3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ See [Configuring output directory for more info](https://typespec.io/docs/handbo

### `file-type`

**Type:** `string,array`
**Type:** `"yaml" | "json" | ("yaml" | "json")[]`

If the content should be serialized as YAML or JSON. Can be a single value or an array to emit multiple formats. Default 'yaml', if not specified infer from the `output-file` extension

**Options:**

- `"yaml" | "json"`
- `("yaml" | "json")[]`

### `output-file`

**Type:** `string`
Expand Down Expand Up @@ -86,12 +91,16 @@ Example Multiple service with versioning

### `openapi-versions`

**Type:** `array`
**Type:** `"3.0.0" | "3.1.0" | "3.2.0"`

**Default:** `["3.0.0"]`

### `new-line`

**Type:** `"crlf" | "lf"`

**Default:** `"lf"`

Set the newline character for emitting files.

### `omit-unreachable-types`
Expand All @@ -105,13 +114,17 @@ By default all types declared under the service namespace will be included. With

**Type:** `"inline-only" | "never"`

**Default:** `"never"`

If the generated openapi types should have the `x-typespec-name` extension set with the name of the TypeSpec type that created it.
This extension is meant for debugging and should not be depended on.

### `safeint-strategy`

**Type:** `"double-int" | "int64"`

**Default:** `"int64"`

How to handle safeint type. Options are:

- `double-int`: Will produce `type: integer, format: double-int`
Expand All @@ -123,6 +136,8 @@ Default: `int64`

**Type:** `boolean`

**Default:** `false`

If true, then for models emitted as object schemas we default `additionalProperties` to false for
OpenAPI 3.0, and `unevaluatedProperties` to false for OpenAPI 3.1, if not explicitly specified elsewhere.
Default: `false`
Expand All @@ -138,7 +153,24 @@ See https://github.com/OAI/OpenAPI-Specification/discussions/4622 for discussion

### `operation-id-strategy`

**Type:** `undefined`
**Type:** `"parent-container" | "fqn" | "explicit-only" | object { kind, separator }`

**Options:**

- `"parent-container" | "fqn" | "explicit-only"` (default: `"parent-container"`)

Determines how to generate operation IDs when `@operationId` is not used.
Avaliable options are:

- `parent-container`: Uses the parent namespace and operation name to generate the ID.
- `fqn`: Uses the fully qualified name of the operation to generate the ID.
- `explicit-only`: Only use explicitly defined operation IDs.
- `object { kind, separator }`

| Name | Type | Default | Description |
| ----------- | ------------------------------------------------ | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind` | `"parent-container" \| "fqn" \| "explicit-only"` | `"parent-container"` | Determines how to generate operation IDs when `@operationId` is not used.<br />Avaliable options are:<br /> - `parent-container`: Uses the parent namespace and operation name to generate the ID.<br /> - `fqn`: Uses the fully qualified name of the operation to generate the ID.<br /> - `explicit-only`: Only use explicitly defined operation IDs. |
| `separator` | `string` | | Separator used to join segment in the operation name. |

## Decorators

Expand Down
109 changes: 102 additions & 7 deletions packages/tspd/src/ref-doc/emitters/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
DecoratorRefDoc,
DeprecationNotice,
EmitterOptionRefDoc,
EmitterOptionVariantRefDoc,
EnumRefDoc,
ExampleRefDoc,
InterfaceRefDoc,
Expand Down Expand Up @@ -268,11 +269,22 @@ export class MarkdownRenderer {
content.push(this.templateParameters(union.templateParameters));
}

if (union.variants.size > 0) {
content.push(this.unionVariants(union));
}

content.push(this.examples(union.examples));

return this.typeSection(union, content);
}

unionVariants(union: UnionRefDoc): MarkdownDoc {
const rows = [...union.variants.values()].map((v) => {
return [v.name, this.ref(v.type.type), v.doc];
});
return section("Variants", [table([["Name", "Type", "Description"], ...rows])]);
}

scalar(scalar: ScalarRefDoc): MarkdownDoc {
const content: MarkdownDoc = ["", scalar.doc, codeblock(scalar.signature, "typespec"), ""];

Expand Down Expand Up @@ -401,17 +413,100 @@ export class MarkdownRenderer {
]),
);
for (const option of options) {
content.push(
section(`${inlinecode(option.name)}`, [
`**Type:** ${inlinecode(option.type)}`,
"",
option.doc,
]),
);
content.push(this.emitterOption(option));
}
return section("Emitter options", content);
}

emitterOptionTypeDisplay(option: { type: string; allowedValues?: readonly string[] }): string {
if (option.allowedValues && option.allowedValues.length > 0) {
return inlinecode(option.allowedValues.join(" | "));
}
return inlinecode(option.type);
}

emitterOption(option: EmitterOptionRefDoc): MarkdownDoc {
const content: MarkdownDoc = [];

// Deprecation notice
if (option.deprecated !== undefined) {
const message = option.deprecated || "This option is deprecated.";
content.push(this.deprecationNotice({ message }), "");
}

// Type line
content.push(`**Type:** ${this.emitterOptionTypeDisplay(option)}`);

// Default value
if (option.default !== undefined) {
content.push("", `**Default:** ${inlinecode(option.default)}`);
}

// Description
if (option.doc) {
content.push("", option.doc);
}

// Variants (oneOf)
if (option.variants && option.variants.length > 0) {
content.push("", "**Options:**", "");
for (const variant of option.variants) {
content.push(this.emitterOptionVariant(variant));
}
}

// Nested options
if (option.nestedOptions && option.nestedOptions.length > 0) {
content.push("", "**Properties:**", "");
const rows: string[][] = [["Name", "Type", "Default", "Description"]];
for (const nested of option.nestedOptions) {
rows.push([
inlinecode(nested.name),
this.emitterOptionTypeDisplay(nested),
nested.default !== undefined ? inlinecode(nested.default) : "",
nested.doc,
]);
}
content.push(table(rows));
}

return section(`${inlinecode(option.name)}`, content);
}

emitterOptionVariant(variant: EmitterOptionVariantRefDoc): MarkdownDoc {
const content: MarkdownDoc = [];

// Variant type/values
const typeDisplay = this.emitterOptionTypeDisplay(variant);

let header = `- ${typeDisplay}`;
if (variant.default !== undefined) {
header += ` (default: ${inlinecode(variant.default)})`;
}
content.push(header);

if (variant.doc) {
content.push("", ` ${variant.doc}`);
}

// Nested options for object variants
if (variant.nestedOptions && variant.nestedOptions.length > 0) {
content.push("");
const rows: string[][] = [["Name", "Type", "Default", "Description"]];
for (const nested of variant.nestedOptions) {
rows.push([
inlinecode(nested.name),
this.emitterOptionTypeDisplay(nested),
nested.default !== undefined ? inlinecode(nested.default) : "",
nested.doc,
]);
}
content.push(" ", table(rows));
}

return content;
}

linterUsage(refDoc: TypeSpecRefDoc) {
if (refDoc.linter === undefined) {
return [];
Expand Down
Loading
Loading