Support libraries using auto decorators in tspd - #11813
Conversation
commit: |
|
All changed packages have been documented.
Show changes
|
2b72c8c to
d989d8b
Compare
|
You can try these changes here
|
There was a problem hiding this comment.
🟡 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.yamland apply them totspd’s internalcompile()calls (withnoEmit: 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/graphqlgenerated 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.
There was a problem hiding this comment.
🟡 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
There was a problem hiding this comment.
🔵 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
resolveCompilerOptionsreturns diagnostics as well as options; currentlyresolveLibraryCompilerOptionsdiscards those diagnostics, which can hide invalid/misparsedtspconfig.yamlissues and lead to confusing behavior. Consider returning the diagnostics (or reporting them at call sites) so config errors are surfaced alongsideprogram.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
64aa2ae to
d6f1740
Compare
There was a problem hiding this comment.
🟡 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
resolveCompilerOptionsusescwdfor{cwd}interpolation intspconfig.yaml(seepackages/compiler/src/config/config-to-options.ts), but this helper passesprocess.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
resolveCompilerOptionsreturns[options, diagnostics], but this helper drops the diagnostics. That means tspd can silently ignore config parse/validation errors (from loadingtspconfig.yaml) becausecompile()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
d6f1740 to
6be39ee
Compare
There was a problem hiding this comment.
🟡 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
resolveCompilerOptionsreturns diagnostics for invalid/missing config and config-name validation, butresolveLibraryCompilerOptionscurrently drops them. That means a brokentspconfig.yamlwon’t be reported intspd(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/expectDiagnosticsinstead of checking only.codestrings so the assertions validate the diagnostic shape consistently with other tspd tests (seepackages/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
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.
0b8d804 to
c4f8592
Compare
There was a problem hiding this comment.
🟡 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
resolveCompilerOptionsreturns[CompilerOptions, readonly Diagnostic[]](e.g.packages/compiler/src/config/config-to-options.ts:46-50), but this helper discards the diagnostics. That means malformed/invalidtspconfig.yamlfiles (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
There was a problem hiding this comment.
🟡 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
resolveCompilerOptionsuses the providedcwdfor{cwd}interpolation in tspconfig (seepackages/compiler/src/config/config-to-options.ts:21-25). Usingprocess.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
resolveCompilerOptionsreturns[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), andcompile()does not re-run config loading, so invalidtspconfig.yamlissues 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.diagnosticscodes; TypeSpec tests in this repo consistently useexpectDiagnosticEmpty/expectDiagnosticsfrom@typespec/compiler/testingfor 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
expectDiagnosticson 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
Two things stand in the way of a library shipping
auto decin its public API.tspd never loads the library's own
tspconfig.yaml. Everycompile()call site passes onlyparseOptions, so any compiler feature the library opts into is invisible.@typespec/graphqlshowsthe symptom today — its
regen-docsprints one error per auto decorator:Fixed by running the library's entrypoint through the existing
resolveCompilerOptionsand handingthe result to
compile(), withnoEmit: truesince tspd only ever inspects a library.Generated accessors carried no doc comment, so a library re-exporting one failed api-extractor's
ae-undocumentedrule.get*andset*now inherit the description of the decorator they read orwrite, which is already the source of truth in the
.tsp.is*gets a generic one instead — adecorator description reads as an instruction to apply it, which says nothing about a boolean check:
Only the description is carried over; the decorator's
@paramtags describe its TypeSpec parameters,which do not line up with the accessor signatures. A
get*/set*pair for a decorator with no docstill 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
docprop, because that prop also emits@param {Type}tags whose typereferences 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.