Skip to content

Support libraries using auto decorators in tspd - #11813

Merged
Timothee Guerin (timotheeguerin) merged 2 commits into
microsoft:mainfrom
timotheeguerin:tspd-auto-decorator-support
Sep 4, 2026
Merged

Support libraries using auto decorators in tspd#11813
Timothee Guerin (timotheeguerin) merged 2 commits into
microsoft:mainfrom
timotheeguerin:tspd-auto-decorator-support

Conversation

@timotheeguerin

@timotheeguerin Timothee Guerin (timotheeguerin) commented Sep 2, 2026

Copy link
Copy Markdown
Member

Two things stand in the way of a library shipping auto dec in its public API.

tspd never loads the library's own tspconfig.yaml. Every compile() call site passes only
parseOptions, so any compiler feature the library opts into is invisible. @typespec/graphql shows
the symptom today — its regen-docs prints one error per auto decorator:

error auto-decorator-disabled: Auto decorators are experimental. Enable the `auto-decorators` feature...

Fixed by running the library's entrypoint through the existing resolveCompilerOptions and handing
the result to compile(), with noEmit: true since tspd only ever inspects a library.

Generated accessors carried no doc comment, so a library re-exporting one failed api-extractor's
ae-undocumented rule. get* and set* now inherit the description of the decorator they read or
write, which is already the source of truth in the .tsp. is* gets a generic one instead — a
decorator description reads as an instruction to apply it, which says nothing about a boolean check:

/** Check if the `@TypeSpec.GraphQL.nullable` decorator was applied on the given target. */
export function isNullable(program: Program, target: ModelProperty | Operation | Union | Model): boolean

/**
 * Mark a field, operation, or type as nullable in the emitted GraphQL schema.
 *
 * Applied automatically by the mutation engine when it strips `| null` from
 * union types, and can also be applied directly in TypeSpec source.
 */
export function setNullable(program: Program, target: ModelProperty | Operation | Union | Model): void

Only the description is carried over; the decorator's @param tags describe its TypeSpec parameters,
which do not line up with the accessor signatures. A get*/set* pair for a decorator with no doc
still generates no doc.

One detail worth knowing for anyone touching this later: the doc is emitted as a standalone comment
rather than through alloy's doc prop, because that prop also emits @param {Type} tags whose type
references count as value usages — which turns this file's type-only imports into value imports and
breaks verbatimModuleSyntax.

The only output change is @typespec/graphql's regenerated signatures.

@pkg-pr-new

pkg-pr-new Bot commented Sep 2, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@typespec/graphql@11813
npm i https://pkg.pr.new/@typespec/tspd@11813

commit: c4f8592

@microsoft-github-policy-service microsoft-github-policy-service Bot added the tspd Issues for the tspd tool label Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @typespec/graphql
  • @typespec/tspd
Show changes

@typespec/graphql - internal ✏️

Regenerate the decorator signatures to pick up the doc comments now emitted for auto decorator,> accessors.

@typespec/tspd - fix ✏️

Document the generated auto decorator accessors, so libraries re-exporting them satisfy,> api-extractor's ae-undocumented rule. get* and set* accessors carry the description of the,> decorator they read or write; is* accessors get a generic one, since a decorator description,> does not describe a boolean check.,> ,> ts,> /** Check if the `@TypeSpec.GraphQL.inputType` decorator was applied on the given target. */,> export function isInputType(program: Program, target: Model): boolean {,> return hasAutoDecorator(program, "TypeSpec.GraphQL.inputType", target);,> },> ,> /** Mark a model as a GraphQL input type in the emitted schema. */,> export function setInputType(program: Program, target: Model): void {,> setAutoDecorator(program, "TypeSpec.GraphQL.inputType", target);,> },>

@typespec/tspd - fix ✏️

Honor the library's own tspconfig.yaml when generating signatures and reference documentation, so,> libraries that opt into a compiler feature (such as auto-decorators) no longer report errors during,> gen-extern-signature and doc.

@timotheeguerin Timothee Guerin (timotheeguerin) changed the title Honor the library's tspconfig.yaml in tspd Support libraries using auto decorators in tspd Sep 2, 2026
@microsoft-github-policy-service microsoft-github-policy-service Bot added the emitter:graphql Issues for @typespec/graphql emitter label Sep 2, 2026
@azure-sdk-automation

azure-sdk-automation Bot commented Sep 2, 2026

Copy link
Copy Markdown

You can try these changes here

🛝 Playground 🌐 Website 🛝 VSCode Extension

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There are confirmed issues in the new helper/docs emission logic that can cause incorrect config interpolation and potentially broken generated TypeScript due to unescaped comment terminators.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Updates @typespec/tspd to better support libraries that expose auto dec in their public API by (1) honoring each library’s own tspconfig.yaml when compiling for docs/signature generation and (2) carrying decorator descriptions onto generated auto-decorator accessor functions so re-exports satisfy API Extractor’s documentation requirements.

Changes:

  • Add a helper to resolve compiler options from a library’s tspconfig.yaml and apply them to tspd’s internal compile() calls (with noEmit: true).
  • Emit doc comments for generated auto-decorator accessors using the decorator’s description (excluding tags), with tests covering single- and multi-line descriptions.
  • Regenerate @typespec/graphql generated defs and add Chronus entries describing the fixes.
File summaries
File Description
packages/tspd/test/gen-extern-signature/decorators-signatures.test.ts Adds coverage ensuring auto-decorator accessors inherit only the decorator description (and correct multi-line formatting).
packages/tspd/src/utils/library-config.ts Introduces shared compiler-option resolution from the library’s config for tspd’s compilation flows.
packages/tspd/src/ref-doc/extractor.ts Applies resolved library compiler options when compiling entrypoints for reference doc extraction.
packages/tspd/src/ref-doc/experimental.ts Applies resolved library compiler options when compiling for experimental docs pipeline.
packages/tspd/src/gen-extern-signatures/gen-extern-signatures.ts Applies resolved library compiler options when compiling exports prior to extern signature generation.
packages/tspd/src/gen-extern-signatures/components/auto-decorator-accessors.tsx Emits standalone JSDoc comments for auto-decorator accessors derived from the decorator’s description.
packages/graphql/generated-defs/TypeSpec.GraphQL.ts Regenerated output reflecting newly emitted accessor doc comments.
.chronus/changes/tspd-honor-library-config-2026-9-2.md Changelog entry for honoring library config.
.chronus/changes/tspd-auto-accessor-docs-2026-9-2.md Changelog entry for accessor doc comment behavior.
.chronus/changes/graphql-regen-auto-accessor-docs-2026-9-2.md Changelog entry for regenerated GraphQL signatures.
Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/tspd/src/gen-extern-signatures/components/auto-decorator-accessors.tsx Outdated
Comment thread packages/tspd/src/utils/library-config.ts
Comment thread packages/tspd/src/utils/library-config.ts
Copilot AI review requested due to automatic review settings September 3, 2026 17:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

packages/tspd/src/utils/library-config.ts currently contains duplicated imports and duplicated doc blocks that will cause TypeScript compile errors (duplicate identifier imports).

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread packages/tspd/src/utils/library-config.ts
Copilot AI review requested due to automatic review settings September 3, 2026 17:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

resolveLibraryCompilerOptions drops diagnostics returned by resolveCompilerOptions, which can hide invalid tspconfig.yaml issues and make failures harder to understand.

Review details

Suppressed comments (2)

Previously missed (1) — in code that hasn't changed since the last review.

packages/tspd/src/gen-extern-signatures/components/auto-decorator-accessors.tsx:220

  • The inline comment claims it escapes "internal and other tsdoc tags", but the implementation only replaces @internal. Update the comment to match the actual behavior (or expand the escaping logic if more tags are intended).

packages/tspd/src/utils/library-config.ts:20

  • resolveCompilerOptions returns diagnostics as well as options; currently resolveLibraryCompilerOptions discards those diagnostics, which can hide invalid/misparsed tspconfig.yaml issues and lead to confusing behavior. Consider returning the diagnostics (or reporting them at call sites) so config errors are surfaced alongside program.diagnostics.
  const cwd = getDirectoryPath(entrypoint);
  const [options] = await resolveCompilerOptions(host, { cwd, entrypoint });
  return { ...options, noEmit: true };
  • Files reviewed: 10/10 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new resolveLibraryCompilerOptions currently uses process.cwd() for {cwd} interpolation and drops resolveCompilerOptions diagnostics, which can break library config resolution and hide config errors.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

packages/tspd/src/utils/library-config.ts:17

  • resolveCompilerOptions uses cwd for {cwd} interpolation in tspconfig.yaml (see packages/compiler/src/config/config-to-options.ts), but this helper passes process.cwd(). When tspd is run from outside the target library folder, {cwd}-based config values (e.g. in imports/options) will resolve relative to the wrong directory, defeating the goal of honoring the library config.
  const [options] = await resolveCompilerOptions(host, { cwd: process.cwd(), entrypoint });

packages/tspd/src/utils/library-config.ts:18

  • resolveCompilerOptions returns [options, diagnostics], but this helper drops the diagnostics. That means tspd can silently ignore config parse/validation errors (from loading tspconfig.yaml) because compile() won’t re-run config resolution when options are passed in. Consider returning both options+diagnostics from this helper and adding those diagnostics to the existing collectors at each compile call site.
  const [options] = await resolveCompilerOptions(host, { cwd: process.cwd(), entrypoint });
  return { ...options, noEmit: true };
  • Files reviewed: 10/10 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread packages/tspd/src/gen-extern-signatures/gen-extern-signatures.ts

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new library config resolver currently uses an invocation-dependent cwd for config interpolation and drops resolveCompilerOptions diagnostics, and the new tests should use the standard diagnostic assertion helpers for consistency and stronger validation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

packages/tspd/src/utils/library-config.ts:18

  • resolveCompilerOptions returns diagnostics for invalid/missing config and config-name validation, but resolveLibraryCompilerOptions currently drops them. That means a broken tspconfig.yaml won’t be reported in tspd (unlike the compiler CLI path which pipes these diagnostics, e.g. packages/compiler/src/core/cli/actions/compile/args.ts:50-67). Consider returning { options, diagnostics } (or [options, diagnostics]) and having callers add those diagnostics to their collectors.
  const [options] = await resolveCompilerOptions(host, { cwd: process.cwd(), entrypoint });
  return { ...options, noEmit: true };

packages/tspd/test/utils/library-config.test.ts:38

  • Use expectDiagnosticEmpty / expectDiagnostics instead of checking only .code strings so the assertions validate the diagnostic shape consistently with other tspd tests (see packages/compiler/src/testing/expect.ts).
    const program = await compileLibrary();

    expect(program.diagnostics.map((x) => x.code)).toEqual([]);
  });

  it("reports the feature as disabled when the library did not opt in", async () => {
    const program = await compileLibrary();

    expect(program.diagnostics.map((x) => x.code)).toContain("auto-decorator-disabled");
  });
  • Files reviewed: 11/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread packages/tspd/src/utils/library-config.ts
Comment thread packages/tspd/test/utils/library-config.test.ts
Copilot AI review requested due to automatic review settings September 4, 2026 22:18
Load the config file when compiling a library for signature generation and
reference docs, so features it opts into (such as auto-decorators) apply.
Use the decorator's own description so libraries re-exporting the accessors
satisfy api-extractor.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new library config resolution helper drops resolveCompilerOptions diagnostics (potentially hiding config errors), and the new tests should use the standard diagnostic assertion helpers for reliable failures.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

packages/tspd/src/utils/library-config.ts:18

  • resolveCompilerOptions returns [CompilerOptions, readonly Diagnostic[]] (e.g. packages/compiler/src/config/config-to-options.ts:46-50), but this helper discards the diagnostics. That means malformed/invalid tspconfig.yaml files (and related config loader diagnostics) won't be surfaced by tspd even though it is now reading the config.
  const [options] = await resolveCompilerOptions(host, { cwd: process.cwd(), entrypoint });
  return { ...options, noEmit: true };
  • Files reviewed: 11/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

Comment thread packages/tspd/src/utils/library-config.ts
Comment thread packages/tspd/test/utils/library-config.test.ts
Copilot AI review requested due to automatic review settings September 4, 2026 22:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

resolveLibraryCompilerOptions currently risks mis-resolving {cwd}-interpolated paths and drops resolveCompilerOptions diagnostics, and the new tests should use the standard diagnostic assertion helpers for robustness.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (5)

packages/tspd/src/utils/library-config.ts:18

  • resolveCompilerOptions uses the provided cwd for {cwd} interpolation in tspconfig (see packages/compiler/src/config/config-to-options.ts:21-25). Using process.cwd() makes option resolution depend on where tspd is invoked instead of the library’s entrypoint location, which can mis-resolve paths in library configs.
  const [options] = await resolveCompilerOptions(host, { cwd: process.cwd(), entrypoint });
  return { ...options, noEmit: true };

packages/tspd/src/utils/library-config.ts:18

  • resolveCompilerOptions returns [CompilerOptions, Diagnostic[]], and tspd currently drops the diagnostics here. Those config-resolution diagnostics are normally surfaced separately by the compiler CLI (packages/compiler/src/core/cli/actions/compile/args.ts:50-83), and compile() does not re-run config loading, so invalid tspconfig.yaml issues can be silently lost.
  const [options] = await resolveCompilerOptions(host, { cwd: process.cwd(), entrypoint });
  return { ...options, noEmit: true };

packages/tspd/test/utils/library-config.test.ts:3

  • This test file asserts diagnostics by manually inspecting program.diagnostics codes; TypeSpec tests in this repo consistently use expectDiagnosticEmpty / expectDiagnostics from @typespec/compiler/testing for stronger assertions and better failure output (e.g. packages/tspd/test/test-utils.ts:3,21).
import { compile } from "@typespec/compiler";
import { createTestHost, resolveVirtualPath, type TestHost } from "@typespec/compiler/testing";
import { beforeEach, describe, expect, it } from "vitest";

packages/tspd/test/utils/library-config.test.ts:32

  • Prefer expectDiagnosticEmpty(program.diagnostics) here instead of comparing the mapped codes to [], so failures include formatted diagnostics (from @typespec/compiler/testing).
    const program = await compileLibrary();

    expect(program.diagnostics.map((x) => x.code)).toEqual([]);
  });

packages/tspd/test/utils/library-config.test.ts:38

  • This assertion only checks that the code appears somewhere; using expectDiagnostics on the filtered diagnostics ensures you’re asserting an actual diagnostic (with severity/target) and fails with useful output if the behavior changes.
    const program = await compileLibrary();

    expect(program.diagnostics.map((x) => x.code)).toContain("auto-decorator-disabled");
  });
  • Files reviewed: 11/11 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread .chronus/changes/tspd-auto-accessor-docs-2026-9-2.md
Merged via the queue into microsoft:main with commit 17011f1 Sep 4, 2026
33 checks passed
@timotheeguerin
Timothee Guerin (timotheeguerin) deleted the tspd-auto-decorator-support branch September 4, 2026 23:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

emitter:graphql Issues for @typespec/graphql emitter tspd Issues for the tspd tool

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants