Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .chronus/changes/tcgc-clientOptions-2026-0-22-16-26-53.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

Add `@clientOption` flag for experimental, language-specific flags
Original file line number Diff line number Diff line change
Expand Up @@ -944,6 +944,36 @@ export type ClientDocDecorator = (
scope?: string,
) => DecoratorValidatorCallbacks | void;

/**
* Pass experimental flags or options to emitters without requiring TCGC reshipping.
* This decorator is intended for temporary workarounds or experimental features and requires
* suppression to acknowledge its experimental nature.
*
* **Warning**: This decorator always emits a warning that must be suppressed, and an additional
* warning if no scope is provided (since options are typically language-specific).
*
* @param target The type you want to apply the option to.
* @param name The name of the option (e.g., "enableFeatureFoo").
* @param value The value of the option. Can be any type; emitters will cast as needed.
* @param scope Specifies the target language emitters that the decorator should apply. If not set, the decorator will be applied to all language emitters by default.
* You can use "!" to exclude specific languages, for example: !(java, python) or !java, !python.
* @example Apply an experimental option for Python
* ```typespec
* #suppress "@azure-tools/typespec-client-generator-core/client-option"
* @clientOption("enableFeatureFoo", true, "python")
Copy link
Member

Choose a reason for hiding this comment

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

can we add a link to the website with a dedicated place where we can define what are the options and values

* model MyModel {
* prop: string;
* }
* ```
*/
export type ClientOptionDecorator = (
context: DecoratorContext,
target: Type,
name: string,
value: Type,
scope?: string,
) => DecoratorValidatorCallbacks | void;

export type AzureClientGeneratorCoreDecorators = {
clientName: ClientNameDecorator;
convenientAPI: ConvenientAPIDecorator;
Expand All @@ -965,4 +995,5 @@ export type AzureClientGeneratorCoreDecorators = {
responseAsBool: ResponseAsBoolDecorator;
clientLocation: ClientLocationDecorator;
clientDoc: ClientDocDecorator;
clientOption: ClientOptionDecorator;
};
25 changes: 25 additions & 0 deletions packages/typespec-client-generator-core/lib/decorators.tsp
Original file line number Diff line number Diff line change
Expand Up @@ -1022,3 +1022,28 @@ extern dec clientDoc(
mode: EnumMember,
scope?: valueof string
);

/**
* Pass experimental flags or options to emitters without requiring TCGC reshipping.
* This decorator is intended for temporary workarounds or experimental features and requires
* suppression to acknowledge its experimental nature.
*
* **Warning**: This decorator always emits a warning that must be suppressed, and an additional
* warning if no scope is provided (since options are typically language-specific).
*
* @param target The type you want to apply the option to.
* @param name The name of the option (e.g., "enableFeatureFoo").
* @param value The value of the option. Can be any type; emitters will cast as needed.
* @param scope Specifies the target language emitters that the decorator should apply. If not set, the decorator will be applied to all language emitters by default.
* You can use "!" to exclude specific languages, for example: !(java, python) or !java, !python.
*
* @example Apply an experimental option for Python
* ```typespec
* #suppress "@azure-tools/typespec-client-generator-core/client-option"
Copy link
Member

Choose a reason for hiding this comment

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

Show example of the suppression with a message

* @clientOption("enableFeatureFoo", true, "python")
* model MyModel {
* prop: string;
* }
* ```
*/
extern dec clientOption(target: unknown, name: valueof string, value: unknown, scope?: valueof string);
Copy link
Member

Choose a reason for hiding this comment

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

Shall we limit the value to value type?

1 change: 1 addition & 0 deletions packages/typespec-client-generator-core/src/configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export const defaultDecoratorsAllowList = [
"TypeSpec\\.Xml\\..*",
"Azure\\.Core\\.@useFinalStateVia",
"Autorest\\.@example",
"Azure\\.ClientGenerator\\.Core\\.@clientOption",
];
34 changes: 34 additions & 0 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
ClientInitializationDecorator,
ClientNameDecorator,
ClientNamespaceDecorator,
ClientOptionDecorator,
ConvenientAPIDecorator,
DeserializeEmptyStringAsNullDecorator,
OperationGroupDecorator,
Expand Down Expand Up @@ -1851,3 +1852,36 @@ export function isInScope(context: TCGCContext, entity: Operation | ModelPropert
}
return true;
}

export const clientOptionKey = createStateSymbol("ClientOption");

/**
* `@clientOption` decorator implementation.
* Pass experimental flags or options to emitters without requiring TCGC reshipping.
* The decorator data is stored as {name, value} and exposed via the decorators array.
*/
export const $clientOption: ClientOptionDecorator = (
context: DecoratorContext,
target: Type,
name: string,
value: Type,
scope?: LanguageScopes,
) => {
// Always emit warning that this is experimental
reportDiagnostic(context.program, {
code: "client-option",
target: target,
});

// Emit additional warning if scope is not provided
if (scope === undefined) {
reportDiagnostic(context.program, {
code: "client-option-requires-scope",
target: target,
});
}

// Store the option data - each decorator application is stored separately
// The decorator info will be exposed via the decorators array on SDK types
setScopedDecoratorData(context, $clientOption, clientOptionKey, target, { name, value }, scope);
};
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,14 @@ export function getTypeDecorators(
getDecoratorArgValue(context, decorator.args[i].jsValue, type, decoratorName),
);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tadelesh is there a reason we didn't filter raw decorators by scope before? I think we should because we only want actionable decorator values for a language emitter

Copy link
Member

Choose a reason for hiding this comment

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

Oh, that's a logic missing. Good catch. Thanks for the fix.

// Filter by scope - only include decorators that match the current emitter or have no scope
const scopeArg = decoratorInfo.arguments["scope"];
if (scopeArg !== undefined && scopeArg !== context.emitterName) {
// Skip this decorator if it has a scope that doesn't match the current emitter
continue;
}

retval.push(decoratorInfo);
}
}
Expand Down
14 changes: 14 additions & 0 deletions packages/typespec-client-generator-core/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,20 @@ export const $lib = createTypeSpecLibrary({
default: "All services must have the same server and auth definitions.",
},
},
"client-option": {
severity: "warning",
messages: {
default:
"@clientOption is experimental and should only be used for temporary workarounds. This usage must be suppressed.",
},
},
"client-option-requires-scope": {
severity: "warning",
messages: {
default:
"@clientOption should be applied with a specific language scope since it is highly likely this is language-specific.",
},
},
},
emitter: {
options: TCGCEmitterOptionsSchema,
Expand Down
2 changes: 2 additions & 0 deletions packages/typespec-client-generator-core/src/tsp-index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
$clientLocation,
$clientName,
$clientNamespace,
$clientOption,
$convenientAPI,
$deserializeEmptyStringAsNull,
$flattenProperty,
Expand Down Expand Up @@ -55,6 +56,7 @@ export const $decorators = {
responseAsBool: $responseAsBool,
clientDoc: $clientDoc,
clientLocation: $clientLocation,
clientOption: $clientOption,
} satisfies AzureClientGeneratorCoreDecorators,

"Azure.ClientGenerator.Core.Legacy": {
Expand Down
Loading
Loading