1.12.0
TypeSpec 1.12.0 adds more flexible configuration, improves everyday workflows in Playground and VS Code, and strengthens correctness across generated HTTP, OpenAPI, Protobuf, and Python outputs.
Thank you to everyone who contributed feedback and fixes for version 1.12.
Highlights
Section titled “Highlights”Configure linter rule options
Section titled “Configure linter rule options”Linter authors can now define typed rule options with defaults, and projects can override those options directly in tspconfig.yaml or rulesets. This makes shared linting policies much more practical for teams that want common standards with room for project-specific tuning.
Define defaultOptions when creating a rule, then enable the rule with either true or an options object in your linter configuration.
Example
Rule authors define defaults once, and projects can either enable the rule as-is or override its options.
const myRule = createRule({ name: "no-model-with-name", severity: "warning", description: "Bans models with a specific name", messages: { default: "This model name is not allowed" }, defaultOptions: { bannedName: "Foo" }, create(context) { return { model: (target) => { if (target.name === context.options.bannedName) { context.reportDiagnostic({ target }); } }, }; },});linter: enable: "@typespec/my-lib/no-model-with-name": truelinter: enable: "@typespec/my-lib/no-model-with-name": bannedName: "Bar"Related PR: microsoft/typespec#10352 - Add rule options
Protobuf output now preserves optional field presence more accurately
Section titled “Protobuf output now preserves optional field presence more accurately”Optional TypeSpec properties now map to protobuf optional for eligible scalar and enum fields, allowing generated .proto files to preserve explicit presence where protobuf supports it. Message fields retain protobuf’s existing presence behavior, and lossy cases such as repeated or map fields now produce warnings instead of implying semantics protobuf cannot represent.
Use ? on protobuf model properties as usual, regenerate your .proto files, and review any warnings for optional arrays or maps.
Example
Scalars and enums now emit optional; message fields keep their normal presence behavior, and optional arrays still cannot preserve explicit presence.
enum Status { active: 0, disabled: 1,}
model Details { @field(1) id: int32;}
model Example { // int32 is a scalar type, so this optional property will emit `optional` in protobuf. @field(1) count?: int32;
// Status is an enum type, so this optional property will emit `optional` in protobuf. @field(2) status?: Status;
// Details is a message type, so this optional property will not emit `optional` in protobuf // (message fields always have explicit presence discipline in protobuf). @field(3) details?: Details;
// Arrays cannot preserve explicit presence in protobuf, so this optional property will not emit `optional` in protobuf // and will produce a warning. @field(4) tags?: string[];}message Example { optional int32 count = 1; optional Status status = 2; Details details = 3; repeated string tags = 4;}Related PR: microsoft/typespec#10598 - [protobuf] Represent TypeSpec optional properties as optional fields
Playground keeps context while you iterate
Section titled “Playground keeps context while you iterate”The Playground now shows compilation progress, preserves the last successful output during transient errors, and highlights changed files and lines after recompilation. This makes it easier to compare generated output and keep working through intermediate failures without losing context.
As you edit and recompile, the output pane keeps prior results visible and clearly marks what changed.
Related PR: microsoft/typespec#10349 - feat(playground): add compilation spinner, diff highlighting, and out…
Plain unions can represent a single HTTP response more cleanly
Section titled “Plain unions can represent a single HTTP response more cleanly”Operations that return a union of types without explicit status codes or content types are now treated as a single HTTP response, and generated OpenAPI examples stay aligned with that model. This better reflects author intent when a union represents alternate payload shapes for the same response rather than multiple distinct HTTP responses.
Model alternate payload shapes as a plain union when they share the same response metadata, then regenerate your HTTP and OpenAPI output.
Example
model Cat { kind: "cat"; name: string;}
model Dog { kind: "dog"; name: string; barkVolume: int32;}
op readPet(): Cat | Dog;responses: "200": content: application/json: schema: oneOf: - $ref: "#/components/schemas/Cat" - $ref: "#/components/schemas/Dog"Related PR: microsoft/typespec#10180 - Examples multi response
Bug fixes
Section titled “Bug fixes”Bundler now resolves packages that use subpath exports
Section titled “Bundler now resolves packages that use subpath exports”Packages that rely on subpath exports now resolve correctly in bundler and Playground workflows.
OpenAPI discriminator mappings stay complete during circular emission
Section titled “OpenAPI discriminator mappings stay complete during circular emission”Generated OpenAPI 3.0 and 3.2 output no longer drops the first discriminator mapping entry during circular emission. This keeps discriminated schemas complete and avoids incorrect or incomplete polymorphic output.
Example of the fix
@discriminator("classification")model Pokemon { classification: string;}
model NormalPokemon extends Pokemon { classification: "normal";}
model LegendaryPokemon extends Pokemon { classification: "legendary";}
model MythicalPokemon extends Pokemon { classification: "mythical";}
union PokemonVariant { normal: NormalPokemon, legendary: LegendaryPokemon, mythical: MythicalPokemon,}discriminator: propertyName: classification mapping: normal: "#/components/schemas/NormalPokemon" legendary: "#/components/schemas/LegendaryPokemon" mythical: "#/components/schemas/MythicalPokemon"Python numeric now maps to float
Section titled “Python numeric now maps to float”The Python emitter no longer maps TypeSpec numeric to int. Generated clients now use float, avoiding unsound typing for values that may include fractional data.
Example of the fix
op example(): numeric;def example(self) -> float: ...Python paging annotations stay correct when an operation is named list
Section titled “Python paging annotations stay correct when an operation is named list”Generated paging methods now use the List alias consistently when the operation is named list and page items are themselves collections. This avoids confusing or incorrect type annotations in generated Python code.
Example of the fix
List = list
def list( self, *, cls: ClsType[List[List[str]]],) -> AsyncItemPaged[List[str]]: ...tsp format no longer crashes on comment-only parameter lists
Section titled “tsp format no longer crashes on comment-only parameter lists”Formatting now succeeds when an operation parameter list contains only a dangling block comment, and that comment is preserved.
Example of the fix
namespace MyApp;
op find(/* conditions */): unknown;namespace MyApp;
op find(/* conditions */): unknown;Playground diff highlighting is visible and typing is smoother
Section titled “Playground diff highlighting is visible and typing is smoother”Line-level diff highlighting in the Playground output editor now renders correctly, and redundant recompilations are coalesced while a compile is already running. This reduces missed visual feedback and helps avoid typing stalls during rapid edits.
VS Code editing is more reliable and less disruptive
Section titled “VS Code editing is more reliable and less disruptive”A number of issues around hover, completion, preview, and symbols have been fixed in the VS Code extension. Quick fixes resolve more reliably, and startup progress no longer blocks the UI as a notification.
Related PRs:
- microsoft/typespec#10567 - fix: handle unhandled exceptions in LSP server and VS Code extension
- microsoft/typespec#10618 - Fix unreliable code fix application by switching to
codeAction/resolve - microsoft/typespec#10523 - Move “Launching TypeSpec language service…” progress to status bar
Additional improvements
Section titled “Additional improvements”tsp init now writes concrete current package versions
Section titled “tsp init now writes concrete current package versions”Newly scaffolded projects now use the current TypeSpec package versions at initialization time, such as ^1.12.0, instead of a generic placeholder. This makes generated package.json files easier to review and more predictable to reproduce.
The compiler now warns about duplicate and self-imports
Section titled “The compiler now warns about duplicate and self-imports”Files that import themselves or repeat the same import now produce warnings. These patterns often indicate mistakes, and surfacing them earlier helps keep specs cleaner and easier to maintain.
Example
import "./main.tsp";import "./other.tsp";import "./other.tsp";warning: A file cannot import itself.warning: Duplicate import of "./other.tsp"Long union expressions format more readably
Section titled “Long union expressions format more readably”When union expressions wrap, the formatter now prefers a clearer leading-pipe layout. This improves readability in larger specs and reduces manual formatting churn during review.
Example
alias Pet = Cat | Dog | Snake | Bird | Fish | Lizard;alias Pet = Cat | Dog | Snake | Bird | Fish | Lizard;Bundler workflows now support alloy-based emitters
Section titled “Bundler workflows now support alloy-based emitters”Bundler and Playground-style workflows can now load alloy-based emitters, expanding the set of emitters supported in browser-oriented and bundled scenarios.
@patch(#{implicitOptionality: true}) is now deprecated
Section titled “@patch(#{implicitOptionality: true}) is now deprecated”The older implicit patch-optional behavior is now deprecated. Move to an explicit patch model with optional properties if you want the previous shape, or switch to MergePatchUpdate or MergePatchCreateOrUpdate when you want explicit merge-patch semantics.
Example
Replace the deprecated form with an explicit patch model, or switch to explicit merge-patch semantics.
model Pet { name: string; age: int32;}
@patch(#{ implicitOptionality: true }) op updatePet(@body patch: Pet): void;model Pet { name: string; age: int32;}
model PetPatch { name?: string; age?: int32;}
@patch op updatePet(@body patch: PetPatch): void;model Pet { name: string; age: int32;}
@patch op updatePet(@body patch: MergePatchUpdate<Pet>): void;If you are upgrading embedded Playground integrations, remove the deprecated diff-highlighting option. For HTTP patch operations, plan a migration away from implicit optionality so future updates remain straightforward.
Full Changelog
Section titled “Full Changelog”Show all changes
Deprecations
Section titled “Deprecations”@typespec/http
Section titled “@typespec/http”-
#9884 Deprecate use of
@patch(#{implicitOptionality: true}).Migrate using one of the following patterns depending on intended semantics:
- Preserve previous behavior with an explicit patch model (optional properties)
model Pet {name: string;age: int32;}model PetPatch {name?: string;age?: int32;}@patch(#{implicitOptionality: true}) op updatePet(@body patch: Pet): void;@patch op updatePet(@body patch: PetPatch): void;- Use merge-patch semantics explicitly with
MergePatchUpdate<T>
model Pet {name: string;age: int32;}@patch op updatePet(@body patch: MergePatchUpdate<Pet>): void;Use
MergePatchCreateOrUpdate<T>when the operation supports create-or-update behavior.
Features
Section titled “Features”@typespec/compiler
Section titled “@typespec/compiler”-
#10558 Improve formatting of union expressions
-
#10352 Add support for configurable options on linter rules
Linter rules can now define typed options with defaults using
defaultOptions, and users can pass options when enabling rules intspconfig.yamlor rulesets.Defining a rule with options:
const myRule = createRule({name: "no-model-with-name",severity: "warning",description: "Bans models with a specific name",messages: { default: "This model name is not allowed" },defaultOptions: { bannedName: "Foo" },create(context) {return {model: (target) => {if (target.name === context.options.bannedName) {context.reportDiagnostic({ target });}},};},});Configuring options in
tspconfig.yaml:linter:enable:# Enable with default options"@typespec/my-lib/no-model-with-name": true# Enable with custom options"@typespec/my-lib/no-model-with-name":bannedName: "Bar" -
#10431 [init] Package dependencies are now populated with the actual latest version at the time (e.g.
^1.11.0) -
#10581 Added warnings for duplicate imports and self-imports in the same file
The compiler now warns when a file imports itself or contains duplicate import statements. These are likely mistakes and while they don’t cause errors, they add unnecessary noise.
import "./main.tsp"; // Warning: A file cannot import itself.import "./other.tsp";import "./other.tsp"; // Warning: Duplicate import of "./other.tsp"
@typespec/http
Section titled “@typespec/http”- #10180 [API] Operation returning a union of types without status code or content type will be treated as a single response
@typespec/protobuf (prerelease)
Section titled “@typespec/protobuf (prerelease)”- #10598 Map TypeSpec optionality (
?) to protobufoptionalwhere appropriate.optionalis applied to fields with protobuf scalar types to set explicit presence.optionalis not applied to fields with message types, because they always have explicit presence.- Attempting to convert a TypeSpec optional property where the type is an array or
Protobuf.Mapinstance produces a warning, because protobuf cannot differentiate between “empty” and “unset”repeated/map-typed fields.
Bug Fixes
Section titled “Bug Fixes”@typespec/compiler
Section titled “@typespec/compiler”- #10618 [LSP] Fix code fixes often not running when selected
- #10567 Fix server crashes caused by undefined symbol declarations: add null checks for
getSymNode()in hover, completion, type-details, and type-signature handlers, and use fallback name for empty DocumentSymbol names - #10351 Fix formatter crash when an operation’s parameter list contains only a block comment (e.g.
op find(/* conditions */): unknown;). Dangling comments in empty parameter lists are now preserved instead of being dropped. - #10556 Fix formatting of decorators on operations and augment decorators. Decorators on operations now break to separate lines when the total line exceeds the print width. Augment decorator arguments are now consistently indented when the line breaks, matching TypeScript/prettier function call formatting.
- #10580 Hide cursor during spinner animation to prevent flicker on Windows
@typespec/openapi3
Section titled “@typespec/openapi3”- #10180 Fix examples when operation return type have union of response mapping to same status code
- #10268 Fix missing discriminator mapping entry when the first union variant causes a circular emit, affecting both the OpenAPI 3.0 and 3.2 emitters.
typespec-vscode
Section titled “typespec-vscode”- #10567 Handle unhandled exceptions in VS Code extension: add custom error handler for server crashes with restart notification, wrap commands with graceful exception handling, and add null guards to prevent extension host errors when LSP client is unavailable
- #10523 Show “Launching TypeSpec language service…” progress in the status bar instead of as a notification to avoid blocking the UI
- #10527 Ensure operation telemetry events always carry a valid
resultvalue. Previously thestart-extensionevent (and any other operation whose callback returnedvoid) was sent withresult="undefined"and classified as an error event. ThedoOperationWithTelemetrycallback is now constrained to returnResultCode | Result<...>, so the result is always derived from the operation’s return value.