Skip to content

[tcgc] allow last param in decorator to accept scope or other options - #5305

Open
iscai-msft wants to merge 18 commits into
Azure:mainfrom
iscai-msft:iscai-msft-tcgc-scope-alias-255
Open

[tcgc] allow last param in decorator to accept scope or other options #5305
iscai-msft wants to merge 18 commits into
Azure:mainfrom
iscai-msft:iscai-msft-tcgc-scope-alias-255

Conversation

@iscai-msft

Copy link
Copy Markdown
Member

Summary

First small step toward #5254 (Make TCGC decorator scope arguments evolvable with typed options).

This PR centralizes the type used for the scope argument across all scoped TCGC decorators into a single Azure.ClientGenerator.Core.Scope alias, defined once in decorators.tsp and reused from legacy.tsp. Having one shared alias means the scope type can evolve (e.g. toward a typed options bag as described in the issue) in one place instead of updating every decorator signature individually.

Changes

  • Added alias Scope = string; in decorators.tsp with doc comments describing supported language identifiers and valid patterns (moved from the previous per-decorator @param scope docs).
  • Replaced every scope?: valueof string parameter in decorators.tsp with scope?: valueof Scope.
  • Replaced every scope?: valueof string parameter in legacy.tsp with scope?: valueof Azure.ClientGenerator.Core.Scope (different namespace).
  • Added tests in test/decorators/scope.test.ts verifying the alias can be referenced from user TypeSpec and that scoped decorators continue to behave identically.
  • Added a changeset.

Compatibility

No behavior or public API change — decorators still accept the existing string scope syntax ("python", "python, java", "!csharp", etc.). Verified generated-defs/*.ts is unchanged after rebuilding, and the full TCGC test suite (1405 tests) passes.

Testing

  • pnpm --filter @azure-tools/typespec-client-generator-core build
  • pnpm --filter @azure-tools/typespec-client-generator-core test (1405 passed, 2 skipped)
  • pnpm --filter @azure-tools/typespec-client-generator-core lint
  • pnpm format

Closes part of the first workstream in #5254; the remaining deliverables (typed options bag, diagnostics for conflicting scopes, codefixes, migration docs) are left for follow-up PRs.

iscai-msft and others added 3 commits April 30, 2025 16:17
Addresses Azure#5254 by centralizing the type used for the
`scope` argument across all scoped TCGC decorators into a single
`Azure.ClientGenerator.Core.Scope` alias, defined once in decorators.tsp
and reused by legacy.tsp. This lets the scope type evolve (e.g. toward a
typed options bag) in one place instead of updating every decorator
signature individually. No behavior change; all decorators keep accepting
the existing string scope syntax.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
iscai-msft and others added 2 commits August 25, 2026 13:47
…alias-255

# Conflicts:
#	packages/typespec-client-generator-core/lib/decorators.tsp
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

All changed packages have been documented.

  • @azure-tools/typespec-client-generator-core
Show changes

@azure-tools/typespec-client-generator-core - feature ✏️

Make the scope argument accepted by scoped TCGC decorators evolvable via a shared, typed Azure.ClientGenerator.Core.DecoratorOptions model. Every scoped decorator now accepts either the legacy plain-string scope (e.g. "csharp") or a typed options bag (e.g. #{ scope: "csharp" }), and individual decorators can later grow their own options model that extends DecoratorOptions without breaking others.,> ,> @client's ClientOptions and @clientInitialization's ClientInitializationOptions now also accept scope directly (both extend DecoratorOptions). If the options bag scope disagrees with the legacy positional argument, TCGC reports a conflicting-scope warning and prefers the options bag value. Decorators that already have an options bag keep a single options bag — the legacy positional scope stays a plain string purely for backward compatibility. See design-docs/decorator-options-migration.md for migration guidance and deprecation policy.

@github-actions

github-actions Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📦 Package size report

3 packages changed size, +11.24 KB (+0.1%) packed overall.

Package Packed (base → head) Δ Packed Unpacked (base → head) Δ Unpacked
@azure-tools/typespec-ts 532.67 KB → 536.70 KB +4.03 KB (+0.8%) 🔴 2.56 MB → 2.58 MB +17.99 KB (+0.7%) 🔴
@azure-tools/typespec-client-generator-core 229.25 KB → 233.26 KB +4.01 KB (+1.7%) 🔴 1.23 MB → 1.25 MB +23.54 KB (+1.9%) 🔴
@azure-tools/typespec-go 259.66 KB → 261.24 KB +1.58 KB (+0.6%) 🔴 1.33 MB → 1.34 MB +6.84 KB (+0.5%) 🔴
10 package(s) with no notable change
Package Packed (base → head) Δ Packed Unpacked (base → head) Δ Unpacked
@azure-tools/typespec-java 13.51 MB → 13.51 MB +1.29 KB (+0.0%) 15.03 MB → 15.03 MB +3.05 KB (+0.0%)
@azure-tools/typespec-autorest 80.93 KB → 81.01 KB +85 B (+0.1%) 395.06 KB → 395.40 KB +348 B (+0.1%)
@azure-tools/azure-http-specs 146.63 KB → 146.71 KB +76 B (+0.1%) 1.16 MB → 1.16 MB +415 B (+0.0%)
@azure-tools/typespec-azure-resource-manager 172.85 KB → 172.92 KB +71 B (+0.0%) 1.05 MB → 1.05 MB +633 B (+0.1%)
@azure-tools/typespec-azure-rulesets 5.16 KB → 5.22 KB +61 B (+1.2%) 32.09 KB → 32.57 KB +492 B (+1.5%)
@azure-tools/typespec-azure-core 130.81 KB → 130.84 KB +39 B (+0.0%) 709.51 KB → 709.84 KB +336 B (+0.0%)
@azure-tools/typespec-azure-portal-core 42.40 KB → 42.40 KB -3 B (-0.0%) 192.91 KB → 192.91 KB
@azure-tools/typespec-autorest-canonical 7.42 KB → 7.42 KB 26.00 KB → 26.00 KB
@azure-tools/typespec-metadata 15.91 KB → 15.91 KB 62.26 KB → 62.26 KB
@azure-tools/typespec-python 42.22 KB → 42.22 KB 164.91 KB → 164.91 KB

Packed = gzipped .tgz published to npm. Unpacked = total extracted size. 🆕 added, 🗑️ removed. Packages from the core/ submodule are not included.
🔴 grew · 🟢 shrank — only changes of at least 512 B and 0.5% are marked.

@iscai-msft iscai-msft changed the title [tcgc] Introduce shared Scope alias for decorator scope arguments [tcgc] allow last param in decorator to accept scope or other options Aug 25, 2026
@pkg-pr-new

pkg-pr-new Bot commented Aug 25, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@azure-tools/typespec-client-generator-core@5305

commit: 90adbf1

Per issue feedback, the shared Scope alias is now ScopeOptions | string
instead of a plain string alias, so decorators accept either the legacy
string scope or a typed options bag (e.g. #{ scope: "csharp" }).
Individual decorators can later define their own options model extending
ScopeOptions without breaking others. Centralizes scope normalization in
internal-utils.ts, updates all decorator implementations and generated
defs/docs, and adds tests covering the options-bag form and its
equivalence with the legacy string form.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9d81d1b-44c2-44db-87a1-87df6a805d39
@microsoft-github-policy-service microsoft-github-policy-service Bot added the meta:website TypeSpec.io updates label Aug 25, 2026
@azure-sdk-automation

Copy link
Copy Markdown
Contributor

You can try these changes here

🛝 Playground 🌐 Website

- Add scope to @client's ClientOptions with a conflicting-scope diagnostic when it disagrees with the legacy positional argument
- Add an invalid-scope diagnostic for malformed scope strings (empty, whitespace-only, empty grouped negation, empty comma-list entries), for both legacy string and options-bag forms
- Add a scope-options-migration linter rule (enabled by default) that flags legacy positional string scope arguments and offers a code fix to rewrite them as the typed options bag
- Document the deprecation policy and migration guidance in design-docs/scope-options-migration.md
- Expand scope compatibility tests to cover more decorators and grouped/negated/mixed forms
- Update the changeset to describe the full set of changes

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9d81d1b-44c2-44db-87a1-87df6a805d39
@microsoft-github-policy-service microsoft-github-policy-service Bot added int:azure-specs Run integration tests against azure-rest-api-specs linter Issues related to linter rules labels Aug 25, 2026
iscai-msft and others added 4 commits August 25, 2026 15:02
…-options behavior

- Removed the default-enabled scope-options-migration linter rule and its
  codefix, doc, and tests. The issue only asked for migration guidance, not
  a default-on rule that would surface warnings across existing specs.
- Trimmed the changeset description to the main points.
- Documented that passing a value typed with additional properties beyond
  ScopeOptions to a decorator still using the shared Scope alias will not
  type-check; a decorator must update its own scope parameter type to
  accept an extended options model.
- Regenerated linter reference docs and README after removing the rule.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9d81d1b-44c2-44db-87a1-87df6a805d39
- Removed the invalid-scope diagnostic and isValidScopeString validation.
  There was no prior scope-format validation, and the issue didn't call for
  adding it; malformed scope strings are now handled the same way they
  always were (parsed as-is, with no diagnostic).
- Changed conflicting-scope from an error to a warning. When @client's
  ClientOptions.scope disagrees with the legacy positional scope argument,
  TCGC now uses the options bag value and ignores the legacy argument,
  instead of skipping the decorator entirely.
- Updated diagnostic docs, design doc, changeset, and tests accordingly.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9d81d1b-44c2-44db-87a1-87df6a805d39
Updated the stale doc comment for @client's options param (still said
"produces a diagnostic" without noting it's now a warning that prefers the
options bag value and regenerated derived docs/generated-defs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9d81d1b-44c2-44db-87a1-87df6a805d39
EOF
)
- Rename the shared scoped decorator options model to DecoratorOptions.
- Remove the public Scope alias and use DecoratorOptions | string directly
  in scoped decorator signatures to avoid collisions with service models named Scope.
- Update legacy signatures, generated definitions, tests, reference docs,
  and migration/deprecation guidance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9d81d1b-44c2-44db-87a1-87df6a805d39

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.

Pull request overview

This PR advances TCGC’s “scoped decorator” story by introducing a shared, typed DecoratorOptions options-bag model for decorator scope, updating all scoped decorator signatures to accept either the legacy string scope or an options bag, and extending @client to also accept scope on ClientOptions with a warning diagnostic when the two scope mechanisms conflict.

Changes:

  • Add Azure.ClientGenerator.Core.DecoratorOptions and update all scoped decorators (including Legacy) to accept DecoratorOptions | string for the final scope parameter.
  • Centralize scope-shape normalization in normalizeScope and introduce the conflicting-scope warning for @client when ClientOptions.scope disagrees with the legacy positional scope argument.
  • Add/extend unit tests, update generated defs, and refresh documentation + a changeset to reflect the new options-bag shape.

Reviewed changes

Copilot reviewed 15 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/index.mdx Adds DecoratorOptions to the reference index.
website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/decorators.md Updates decorator signatures/docs to reflect `DecoratorOptions
website/src/content/docs/docs/libraries/typespec-client-generator-core/reference/data-types.md Documents the new DecoratorOptions model.
packages/typespec-client-generator-core/test/decorators/scope.test.ts Adds coverage for legacy vs options-bag scope equivalence across decorators.
packages/typespec-client-generator-core/test/decorators/client.test.ts Adds coverage for @client scope via ClientOptions.scope and conflict warnings.
packages/typespec-client-generator-core/src/lib.ts Registers the conflicting-scope warning diagnostic message.
packages/typespec-client-generator-core/src/internal-utils.ts Adds normalizeScope helper to unify handling of string vs options-bag scope.
packages/typespec-client-generator-core/src/interfaces.ts Introduces runtime TS DecoratorOptions interface for decorator implementations.
packages/typespec-client-generator-core/src/diagnostics/conflicting-scope.md Adds diagnostic documentation for conflicting @client scopes.
packages/typespec-client-generator-core/src/decorators.ts Updates all scoped decorators to accept options-bag scope and implements @client conflict warning behavior.
packages/typespec-client-generator-core/README.md Updates README decorator reference signatures to include the options-bag scope type.
packages/typespec-client-generator-core/lib/legacy.tsp Updates legacy extern decorator signatures to accept `DecoratorOptions
packages/typespec-client-generator-core/lib/decorators.tsp Defines DecoratorOptions, updates extern decorator signatures, and makes ClientOptions extend DecoratorOptions.
packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts Updates generated decorator types and emits DecoratorOptions interface.
packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.Legacy.ts Updates generated legacy decorator types to accept `DecoratorOptions
packages/typespec-client-generator-core/design-docs/decorator-options-migration.md Adds migration/deprecation policy guidance for the options-bag approach.
.chronus/changes/tcgc-scope-alias-2026-8-25-13-40-0.md Adds a feature changeset describing the new evolvable scope/options shape.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread packages/typespec-client-generator-core/src/decorators.ts Outdated
…on doc

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9d81d1b-44c2-44db-87a1-87df6a805d39
Comment thread packages/typespec-client-generator-core/src/decorators.ts Outdated
Comment thread packages/typespec-client-generator-core/src/decorators.ts
Comment thread packages/typespec-client-generator-core/src/decorators.ts Outdated
Comment thread packages/typespec-client-generator-core/src/decorators.ts
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a9d81d1b-44c2-44db-87a1-87df6a805d39
Comment thread packages/typespec-client-generator-core/src/decorators.ts Outdated
iscai-msft and others added 4 commits September 1, 2026 13:57
Canonicalize each scope string into the set of emitters it actually
selects before reporting a conflict, so mixed forms like "!java" and
"csharp, !java" no longer produce a false-positive warning.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- Revert @client, @operationGroup, and @clientInitialization scope parameters to accept only string (LanguageScopes), not DecoratorOptions
- Make ClientInitializationOptions extend DecoratorOptions for consistency
- Move scope configuration to the options model properties instead of the scope parameter
- This eliminates confusion from having two option bags and provides a single, unified options interface

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81756fca-9e84-4b3d-8480-b53d855093d2
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81756fca-9e84-4b3d-8480-b53d855093d2
…zation

Both @client (ClientOptions) and @clientInitialization (ClientInitializationOptions)
already have their own options bag. Keep a single options bag per decorator: scope
lives in that bag (model extends DecoratorOptions) and the legacy positional scope
argument stays a plain string purely for backward compatibility.

- Extract a shared resolveScopeFromOptions helper that reads options.scope, reports
  the conflicting-scope diagnostic when it disagrees with the legacy positional scope,
  and prefers the options bag value.
- Wire both $client and $clientInitialization through the helper. Previously
  ClientInitializationOptions extended DecoratorOptions but its scope property was
  silently ignored.
- Regenerate extern signatures and reference docs; update changeset and migration doc.
- Add @clientInitialization scope-in-options tests (positive, conflict, equivalent).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81756fca-9e84-4b3d-8480-b53d855093d2
Comment thread packages/typespec-client-generator-core/src/internal-utils.ts
Comment thread packages/typespec-client-generator-core/src/decorators.ts
Comment thread packages/typespec-client-generator-core/src/decorators.ts Outdated
Comment thread .chronus/changes/tcgc-scope-alias-2026-8-25-13-40-0.md
Addresses JoshLove-msft's review batch:

- P1: getTypeDecorators now normalizes the scope argument (legacy string or
  DecoratorOptions bag) before materializing decorator arguments, so an
  options-bag scope on an allowlisted decorator like @clientoption no longer
  crashes SDK generation with 'scope.match is not a function'.
- P1: @clientInitialization only reads scope from a genuine ClientInitializationOptions
  bag. A legacy raw client-parameters model that happens to contain a scope
  property is preserved as a real client parameter instead of being consumed as
  an emitter selector.
- P2: a scope-only options bag ({ scope: 'csharp' }) is no longer misclassified as
  the legacy parameters model, so it does not surface an unintended 'scope' client
  initialization parameter.
- P2: @operationGroup now accepts 'DecoratorOptions | string' and normalizes the
  scope before delegating to @client, so @operationGroup(#{ scope: 'python' }) works.

Adds regression tests for each case and regenerates extern signatures and docs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 81756fca-9e84-4b3d-8480-b53d855093d2
return true;
}
return (
options.name === "" &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

P2: Use model provenance instead of anonymity to identify initialization options

The options.name === "" heuristic reverses the intended behavior for two valid shapes:

model CSharpOptions extends ClientInitializationOptions {
  scope: "csharp";
}

@@clientInitialization(MyService, CSharpOptions);

Because this options model is named, it is treated as the legacy raw parameters model; scope becomes a public constructor parameter for every emitter instead of selecting C#. Conversely, the previously supported anonymous legacy form:

@@clientInitialization(
  MyService,
  { scope: "https://management.azure.com/.default" }
);

is now treated as the options bag, so the real client parameter disappears. Please use explicit ClientInitializationOptions ancestry/source-model information where available, and define a backward-compatible disambiguation for the anonymous scope-only shape rather than using the model name.

--generated by Copilot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

int:azure-specs Run integration tests against azure-rest-api-specs lib:tcgc Issues for @azure-tools/typespec-client-generator-core library linter Issues related to linter rules meta:website TypeSpec.io updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants