From 5a4f789c6c45912471793b3c932f8bda617d3214 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 18 Jul 2024 12:58:29 -0700 Subject: [PATCH 1/3] Autorest generated dec sig (#1199) --- ...st-generated-dec-sig-2024-6-18-11-30-47.md | 6 ++++ .../generated-defs/Autorest.ts | 29 ++++++++++++++++ .../generated-defs/Autorest.ts-test.ts | 14 ++++++++ packages/typespec-autorest/package.json | 3 +- packages/typespec-autorest/src/decorators.ts | 33 ++++++++++--------- packages/typespec-autorest/src/lib.ts | 12 ++++--- packages/typespec-autorest/tsconfig.json | 2 +- 7 files changed, 78 insertions(+), 21 deletions(-) create mode 100644 .chronus/changes/autorest-generated-dec-sig-2024-6-18-11-30-47.md create mode 100644 packages/typespec-autorest/generated-defs/Autorest.ts create mode 100644 packages/typespec-autorest/generated-defs/Autorest.ts-test.ts diff --git a/.chronus/changes/autorest-generated-dec-sig-2024-6-18-11-30-47.md b/.chronus/changes/autorest-generated-dec-sig-2024-6-18-11-30-47.md new file mode 100644 index 0000000000..beca6404cf --- /dev/null +++ b/.chronus/changes/autorest-generated-dec-sig-2024-6-18-11-30-47.md @@ -0,0 +1,6 @@ +--- +changeKind: internal +packages: + - "@azure-tools/typespec-autorest" +--- + diff --git a/packages/typespec-autorest/generated-defs/Autorest.ts b/packages/typespec-autorest/generated-defs/Autorest.ts new file mode 100644 index 0000000000..9d05d87d54 --- /dev/null +++ b/packages/typespec-autorest/generated-defs/Autorest.ts @@ -0,0 +1,29 @@ +import type { DecoratorContext, Model, ModelProperty, Operation } from "@typespec/compiler"; + +/** + * `@example` - attaches example files to an operation. Multiple examples can be specified. + * + * `@example` can be specified on Operations. + * + * @param pathOrUri path or Uri to the example file. + * @param title name or description of the example file. + */ +export type ExampleDecorator = ( + context: DecoratorContext, + target: Operation, + pathOrUri: string, + title: string +) => void; + +/** + * `@useRef` - is used to replace the TypeSpec model type in emitter output with a pre-existing named OpenAPI schema such as Azure Resource Manager common types. + * + * `@useRef` can be specified on Models and ModelProperty. + * + * @param jsonRef path or Uri to an OpenAPI schema. + */ +export type UseRefDecorator = ( + context: DecoratorContext, + entity: Model | ModelProperty, + jsonRef: string +) => void; diff --git a/packages/typespec-autorest/generated-defs/Autorest.ts-test.ts b/packages/typespec-autorest/generated-defs/Autorest.ts-test.ts new file mode 100644 index 0000000000..56153d3333 --- /dev/null +++ b/packages/typespec-autorest/generated-defs/Autorest.ts-test.ts @@ -0,0 +1,14 @@ +/** An error here would mean that the decorator is not exported or doesn't have the right name. */ +import { $example, $useRef } from "@azure-tools/typespec-autorest"; +import type { ExampleDecorator, UseRefDecorator } from "./Autorest.js"; + +type Decorators = { + $example: ExampleDecorator; + $useRef: UseRefDecorator; +}; + +/** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ +const _: Decorators = { + $example, + $useRef, +}; diff --git a/packages/typespec-autorest/package.json b/packages/typespec-autorest/package.json index 867b998e55..e930c718a4 100644 --- a/packages/typespec-autorest/package.json +++ b/packages/typespec-autorest/package.json @@ -35,8 +35,9 @@ }, "scripts": { "clean": "rimraf ./dist ./temp", - "build": "npm run regen-autorest-openapi-schema && tsc -p . && npm run lint-typespec-library", + "build": "npm run gen-extern-signature && npm run regen-autorest-openapi-schema && tsc -p . && npm run lint-typespec-library", "watch": "tsc -p . --watch", + "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .", "lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit", "regen-autorest-openapi-schema": "tsp compile ./schema/autorest-openapi-schema.tsp --warn-as-error && node ./.scripts/schema-json-to-js.js", "test": "vitest run", diff --git a/packages/typespec-autorest/src/decorators.ts b/packages/typespec-autorest/src/decorators.ts index ef306cc83c..bbdee5fbb3 100644 --- a/packages/typespec-autorest/src/decorators.ts +++ b/packages/typespec-autorest/src/decorators.ts @@ -1,5 +1,6 @@ import { DecoratorContext, Model, ModelProperty, Program, Type } from "@typespec/compiler"; -import { createStateSymbol, reportDiagnostic } from "./lib.js"; +import { ExampleDecorator, UseRefDecorator } from "../generated-defs/Autorest.js"; +import { AutorestStateKeys, reportDiagnostic } from "./lib.js"; export const namespace = "Autorest"; @@ -8,7 +9,6 @@ export interface Example { title: string; } -const exampleKey = createStateSymbol("example"); /** * `@example` - attaches example files to an operation. Multiple examples can be specified. * @@ -17,18 +17,18 @@ const exampleKey = createStateSymbol("example"); * * `@example` can be specified on Operations. */ -export function $example( +export const $example: ExampleDecorator = ( context: DecoratorContext, entity: Type, pathOrUri: string, title: string -) { +) => { const { program } = context; - if (!program.stateMap(exampleKey).has(entity)) { - program.stateMap(exampleKey).set(entity, []); + if (!program.stateMap(AutorestStateKeys.example).has(entity)) { + program.stateMap(AutorestStateKeys.example).set(entity, []); } else if ( program - .stateMap(exampleKey) + .stateMap(AutorestStateKeys.example) .get(entity) .find((e: Example) => e.title === title || e.pathOrUri === pathOrUri) ) { @@ -37,17 +37,16 @@ export function $example( target: entity, }); } - program.stateMap(exampleKey).get(entity).push({ + program.stateMap(AutorestStateKeys.example).get(entity).push({ pathOrUri, title, }); -} +}; export function getExamples(program: Program, entity: Type): Example[] | undefined { - return program.stateMap(exampleKey).get(entity); + return program.stateMap(AutorestStateKeys.example).get(entity); } -const refTargetsKey = createStateSymbol("autorest.ref"); /** * `@useRef` - is used to replace the TypeSpec model type in emitter output with a pre-existing named OpenAPI schema such as ARM common types. * @@ -55,11 +54,15 @@ const refTargetsKey = createStateSymbol("autorest.ref"); * * `@useRef` can be specified on Models and ModelProperty. */ -export function $useRef(context: DecoratorContext, entity: Model | ModelProperty, jsonRef: string) { - context.program.stateMap(refTargetsKey).set(entity, jsonRef); -} +export const $useRef: UseRefDecorator = ( + context: DecoratorContext, + entity: Model | ModelProperty, + jsonRef: string +) => { + context.program.stateMap(AutorestStateKeys.useRef).set(entity, jsonRef); +}; export function getRef(program: Program, entity: Type): string | undefined { - const refOrProducer = program.stateMap(refTargetsKey).get(entity); + const refOrProducer = program.stateMap(AutorestStateKeys.useRef).get(entity); return refOrProducer; } diff --git a/packages/typespec-autorest/src/lib.ts b/packages/typespec-autorest/src/lib.ts index ad244a04c0..ce4f5d8ac1 100644 --- a/packages/typespec-autorest/src/lib.ts +++ b/packages/typespec-autorest/src/lib.ts @@ -223,7 +223,7 @@ const EmitterOptionsSchema: JSONSchemaType = { required: [], }; -const libDef = { +export const $lib = createTypeSpecLibrary({ name: "@azure-tools/typespec-autorest", diagnostics: { "duplicate-body-types": { @@ -343,7 +343,11 @@ const libDef = { emitter: { options: EmitterOptionsSchema as JSONSchemaType, }, -} as const; -export const $lib = createTypeSpecLibrary(libDef); -export const { reportDiagnostic, createDiagnostic, createStateSymbol, getTracer } = $lib; + state: { + example: { description: "State for the @example decorator" }, + useRef: { description: "State for the @useRef decorator" }, + }, +} as const); + +export const { reportDiagnostic, createDiagnostic, stateKeys: AutorestStateKeys, getTracer } = $lib; diff --git a/packages/typespec-autorest/tsconfig.json b/packages/typespec-autorest/tsconfig.json index b1066a5a33..c836c7b426 100644 --- a/packages/typespec-autorest/tsconfig.json +++ b/packages/typespec-autorest/tsconfig.json @@ -14,5 +14,5 @@ "rootDir": ".", "tsBuildInfoFile": "temp/tsconfig.tsbuildinfo" }, - "include": ["src/**/*.ts", "test/**/*.ts"] + "include": ["src/**/*.ts", "generated-defs/**/*.ts", "test/**/*.ts"] } From 6058195b18e601e1d72afd5952640804d7620dd8 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 18 Jul 2024 13:51:55 -0700 Subject: [PATCH 2/3] Arm library generated TS decorator signatures (#1200) --- ...e-arm-dec-signatures-2024-6-18-11-55-26.md | 6 + .../reference/decorators.md | 2 +- .../typespec-azure-resource-manager/README.md | 2 +- ...ure.ResourceManager.CommonTypes.Private.ts | 38 +++ .../Azure.ResourceManager.Private.ts | 154 +++++++++++ .../generated-defs/Azure.ResourceManager.ts | 260 ++++++++++++++++++ .../Azure.ResourceManager.ts-test.ts | 96 +++++++ .../lib/decorators.tsp | 2 +- .../package.json | 3 +- .../src/common-types.ts | 12 +- .../src/commontypes.private.decorators.ts | 48 +++- .../src/index.ts | 4 +- .../src/namespace.ts | 24 +- .../src/operations.ts | 52 ++-- .../src/private.decorators.ts | 98 ++++--- .../src/resource.ts | 82 ++++-- .../tsconfig.json | 2 +- 17 files changed, 775 insertions(+), 110 deletions(-) create mode 100644 .chronus/changes/generate-arm-dec-signatures-2024-6-18-11-55-26.md create mode 100644 packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.CommonTypes.Private.ts create mode 100644 packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Private.ts create mode 100644 packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.ts create mode 100644 packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.ts-test.ts diff --git a/.chronus/changes/generate-arm-dec-signatures-2024-6-18-11-55-26.md b/.chronus/changes/generate-arm-dec-signatures-2024-6-18-11-55-26.md new file mode 100644 index 0000000000..b0d4b0d5ff --- /dev/null +++ b/.chronus/changes/generate-arm-dec-signatures-2024-6-18-11-55-26.md @@ -0,0 +1,6 @@ +--- +changeKind: internal +packages: + - "@azure-tools/typespec-azure-resource-manager" +--- + diff --git a/docs/libraries/azure-resource-manager/reference/decorators.md b/docs/libraries/azure-resource-manager/reference/decorators.md index e4c3b0c285..1b395a9dc5 100644 --- a/docs/libraries/azure-resource-manager/reference/decorators.md +++ b/docs/libraries/azure-resource-manager/reference/decorators.md @@ -92,7 +92,7 @@ property to the type of the Azure Resource Manager resource. #### Target -`Operation | Model` +`Operation` #### Parameters diff --git a/packages/typespec-azure-resource-manager/README.md b/packages/typespec-azure-resource-manager/README.md index 7ee4f4e84b..63ab62a5fd 100644 --- a/packages/typespec-azure-resource-manager/README.md +++ b/packages/typespec-azure-resource-manager/README.md @@ -171,7 +171,7 @@ property to the type of the Azure Resource Manager resource. ##### Target -`Operation | Model` +`Operation` ##### Parameters diff --git a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.CommonTypes.Private.ts b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.CommonTypes.Private.ts new file mode 100644 index 0000000000..f6d17de305 --- /dev/null +++ b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.CommonTypes.Private.ts @@ -0,0 +1,38 @@ +import type { DecoratorContext, Enum, Model, ModelProperty, Union } from "@typespec/compiler"; + +/** + * Marks an enum as representing the valid `common-types` versions. + */ +export type ArmCommonTypesVersionsDecorator = (context: DecoratorContext, target: Enum) => void; + +/** + * + * + * + * @param definitionName Definition name + * @param version Azure Resource Manager Version + * @param referenceFile Reference file + */ +export type ArmCommonParameterDecorator = ( + context: DecoratorContext, + target: ModelProperty, + definitionName?: string, + version?: unknown | unknown | string, + referenceFile?: string +) => void; + +/** + * + * + * + * @param definitionName Definition name + * @param version Azure Resource Manager Version + * @param referenceFile Reference file + */ +export type ArmCommonDefinitionDecorator = ( + context: DecoratorContext, + target: Model | Enum | Union, + definitionName?: string, + version?: unknown | unknown | string, + referenceFile?: string +) => void; diff --git a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Private.ts b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Private.ts new file mode 100644 index 0000000000..eed8b9f9e0 --- /dev/null +++ b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.Private.ts @@ -0,0 +1,154 @@ +import type { DecoratorContext, Model, ModelProperty, Operation, Type } from "@typespec/compiler"; + +/** + * + * + * + * @param propertyName Name of the property to omit + */ +export type ResourceBaseParametersOfDecorator = ( + context: DecoratorContext, + target: Model, + propertyName: Model +) => void; + +/** + * + * + * + * @param values Values + */ +export type ResourceParameterBaseForDecorator = ( + context: DecoratorContext, + target: ModelProperty, + values: Type +) => void; + +/** + * This decorator is used to identify Azure Resource Manager resource. In generated + * swagger definition, it will be marked with `x-ms-azure-resource`. + * + * It is *not* meant to be used directly by a spec author, + */ +export type AzureResourceBaseDecorator = (context: DecoratorContext, target: Model) => void; + +/** + * Omit a property in the target model. + * + * @internal + * + * + * @param propertyName Name of the property to omit + */ +export type OmitIfEmptyDecorator = ( + context: DecoratorContext, + target: Model, + propertyName: string +) => void; + +/** + * Please DO NOT USE in RestAPI specs. + * Internal decorator that deprecated direct usage of `x-ms-client-flatten` OpenAPI extension. + * It will programatically enabled/disable client flattening with + * + * @flattenProperty with autorest + * emitter flags to maintain compatibility in swagger. + */ +export type ConditionalClientFlattenDecorator = ( + context: DecoratorContext, + target: ModelProperty +) => void; + +/** + * + * + * + * @param resource Resource model + */ +export type AssignProviderNameValueDecorator = ( + context: DecoratorContext, + target: ModelProperty, + resource: Model +) => void; + +/** + * Update the Azure Resource Manager provider namespace for a given entity. + */ +export type ArmUpdateProviderNamespaceDecorator = ( + context: DecoratorContext, + target: Operation +) => void; + +/** + * This decorator is used to identify Azure Resource Manager resource types and extract their + * metadata. It is *not* meant to be used directly by a spec author, it instead + * gets implicitly applied when the spec author defines a model type in this form: + * + * `model Server is TrackedResource;` + * + * The `TrackedResource` type (and other associated base types) use the + * `@armResource` decorator, so it also gets applied to the type which absorbs the + * `TrackedResource` definition by using the `is` keyword. + * + * @param properties Azure Resource Manager resource properties + */ +export type ArmResourceInternalDecorator = ( + context: DecoratorContext, + target: Model, + properties: Model +) => void; + +/** + * Provides default name decoration on resource name property with + * camelcased and pluralized key and segment name + */ +export type DefaultResourceKeySegmentNameDecorator = ( + context: DecoratorContext, + target: ModelProperty, + armResource: Model, + keyName: string, + segment: string +) => void; + +/** + * Provides strict contraint type check. + * + * Due to TypeSpec language and all optional properties of `Foundations.Resource`, + * the `Resource extends Foundations.Resource` on many of the standard ARM templates is + * essentially equal to `Resource extends {}` and does not enforce the containt. + * + * Note, this is intended for internal use only for now. + */ +export type EnforceConstraintDecorator = ( + context: DecoratorContext, + target: Operation | Model, + sourceType: Model, + constraintType: Model +) => void; + +/** + * Marks the operation as being a collection action + * + * @param resourceType Resource + * @param parentTypeName : Parent type name. + * @param parentFriendlyTypeName Friendly name for parent. + * @param applyOperationRename If true, apply both doc and operation name update + */ +export type ArmRenameListByOperationDecorator = ( + context: DecoratorContext, + target: Operation, + resourceType: Model, + parentTypeName?: string, + parentFriendlyTypeName?: string, + applyOperationRename?: boolean +) => void; + +/** + * Please DO NOT USE in RestAPI specs. + * This decorator is used to adjust optionality on ARM Resource's `properties` field for brownfield service conversion. + */ +export type ArmResourcePropertiesOptionalityDecorator = ( + context: DecoratorContext, + target: ModelProperty, + isOptional: boolean +) => void; diff --git a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.ts b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.ts new file mode 100644 index 0000000000..71b8c41932 --- /dev/null +++ b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.ts @@ -0,0 +1,260 @@ +import type { + DecoratorContext, + EnumMember, + Interface, + Model, + Namespace, + Operation, + Type, +} from "@typespec/compiler"; + +/** + * Marks the operation as being a collection action + */ +export type ArmResourceCollectionActionDecorator = ( + context: DecoratorContext, + target: Operation +) => void; + +/** + * `@armResourceType` sets the value fo the decorated string + * property to the type of the Azure Resource Manager resource. + * + * @param resource The resource to get the type of + */ +export type ArmProviderNameValueDecorator = (context: DecoratorContext, target: Operation) => void; + +/** + * `@armProviderNamespace` sets the Azure Resource Manager provider name. It will default to use the + * Namespace element value unless an override value is specified. + * + * @example + * ```typespec + * @armProviderNamespace + * namespace Microsoft.Contoso; + * ``` + * + * ```typespec + * @armProviderNamespace("Microsoft.Contoso") + * namespace Microsoft.ContosoService; + * ``` + * @param providerNamespace Provider namespace + * @param libraryNamespaces a library namespace containing types for this namespace + */ +export type ArmProviderNamespaceDecorator = ( + context: DecoratorContext, + target: Namespace, + providerNamespace?: string +) => void; + +/** + * Declare the Azure Resource Manager library namespaces used in this provider. + * This allows sharing Azure Resource Manager resource types across specifications + * + * @param namespaces The namespaces of Azure Resource Manager libraries used in this provider + */ +export type UseLibraryNamespaceDecorator = ( + context: DecoratorContext, + target: Namespace, + ...namespaces: Namespace[] +) => void; + +/** + * `@armLibraryNamespace` designates a namespace as containign Azure Resource Manager Provider information. + * + * @example + * ```typespec + * @armLibraryNamespace + * namespace Microsoft.Contoso; + * ``` + */ +export type ArmLibraryNamespaceDecorator = (context: DecoratorContext, target: Namespace) => void; + +/** + * `@singleton` marks an Azure Resource Manager resource model as a singleton resource. + * + * Singleton resources only have a single instance with a fixed key name. + * `.../providers/Microsoft.Contoso/monthlyReports/default` + * + * See more details on [different Azure Resource Manager resource type here.](https://azure.github.io/typespec-azure/docs/howtos/ARM/resource-type) + * + * @param keyValue The name of the singleton resource. Default name is "default". + */ +export type SingletonDecorator = ( + context: DecoratorContext, + target: Model, + keyValue?: string | "default" +) => void; + +/** + * `@tenantResource` marks an Azure Resource Manager resource model as a Tenant resource/Root resource/Top-Level resource. + * + * Tenant resources have REST API paths like: + * `/provider/Microsoft.Contoso/FooResources` + * + * See more details on [different Azure Resource Manager resource type here.](https://azure.github.io/typespec-azure/docs/howtos/ARM/resource-type) + */ +export type TenantResourceDecorator = (context: DecoratorContext, target: Model) => void; + +/** + * `@subscriptionResource` marks an Azure Resource Manager resource model as a subscription resource. + * + * Subscription resources have REST API paths like: + * `/subscription/{id}/providers/Microsoft.Contoso/employees` + * + * See more details on [different Azure Resource Manager resource type here.](https://azure.github.io/typespec-azure/docs/howtos/ARM/resource-type) + */ +export type SubscriptionResourceDecorator = (context: DecoratorContext, target: Model) => void; + +/** + * `@locationResource` marks an Azure Resource Manager resource model as a location based resource. + * + * Location based resources have REST API paths like + * `/subscriptions/{subscriptionId}/locations/{location}/providers/Microsoft.Contoso/employees` + * + * See more details on [different Azure Resource Manager resource type here.](https://azure.github.io/typespec-azure/docs/howtos/ARM/resource-type) + */ +export type LocationResourceDecorator = (context: DecoratorContext, target: Model) => void; + +/** + * `@resourceGroupResource` marks an Azure Resource Manager resource model as a resource group level resource. + * This is the default option for Azure Resource Manager resources. It is provided for symmetry and clarity, and + * you typically do not need to specify it. + * + * `/subscription/{id}/resourcegroups/{rg}/providers/Microsoft.Contoso/employees` + * + * See more details on [different Azure Resource Manager resource type here.](https://azure.github.io/typespec-azure/docs/howtos/ARM/resource-type) + */ +export type ResourceGroupResourceDecorator = (context: DecoratorContext, target: Model) => void; + +/** + * `@extensionResource` marks an Azure Resource Manager resource model as an Extension resource. + * Extension resource extends other resource types. URL path is appended + * to another segment {scope} which refers to another Resource URL. + * + * `{resourceUri}/providers/Microsoft.Contoso/accessPermissions` + * + * See more details on [different Azure Resource Manager resource type here.](https://azure.github.io/typespec-azure/docs/howtos/ARM/resource-type) + */ +export type ExtensionResourceDecorator = (context: DecoratorContext, target: Model) => void; + +/** + * + * + * + * @param resourceType Resource model + */ +export type ArmResourceActionDecorator = ( + context: DecoratorContext, + target: Operation, + resourceType: Model +) => void; + +/** + * + * + * + * @param resourceType Resource model + */ +export type ArmResourceCreateOrUpdateDecorator = ( + context: DecoratorContext, + target: Operation, + resourceType: Model +) => void; + +/** + * + * + * + * @param resourceType Resource model + */ +export type ArmResourceReadDecorator = ( + context: DecoratorContext, + target: Operation, + resourceType: Model +) => void; + +/** + * + * + * + * @param resourceType Resource model + */ +export type ArmResourceUpdateDecorator = ( + context: DecoratorContext, + target: Operation, + resourceType: Model +) => void; + +/** + * + * + * + * @param resourceType Resource model + */ +export type ArmResourceDeleteDecorator = ( + context: DecoratorContext, + target: Operation, + resourceType: Model +) => void; + +/** + * + * + * + * @param resourceType Resource model + */ +export type ArmResourceListDecorator = ( + context: DecoratorContext, + target: Operation, + resourceType: Model +) => void; + +/** + * This decorator is used to identify interfaces containing resource operations. + * When applied, it marks the interface with the `@autoRoute` decorator so that + * all of its contained operations will have their routes generated + * automatically. + * + * It also adds a `@tag` decorator bearing the name of the interface so that all + * of the operations will be grouped based on the interface name in generated + * clients. + * + * @param _ DEPRECATED + */ +export type ArmResourceOperationsDecorator = ( + context: DecoratorContext, + target: Interface, + _?: Type +) => void; + +/** + * This decorator is used either on a namespace or a version enum value to indicate + * the version of the Azure Resource Manager common-types to use for refs in emitted Swagger files. + * + * @param version The Azure.ResourceManager.CommonTypes.Versions for the desired common-types version or an equivalent string value like "v5". + */ +export type ArmCommonTypesVersionDecorator = ( + context: DecoratorContext, + target: Namespace | EnumMember, + version: string | unknown +) => void; + +/** + * This decorator is used on Azure Resource Manager resources that are not based on + * Azure.ResourceManager common types. + * + * @param propertiesType : The type of the resource properties. + */ +export type ArmVirtualResourceDecorator = (context: DecoratorContext, target: Model) => void; + +/** + * This decorator sets the base type of the given resource. + * + * @param baseType The built-in parent of the resource, this can be "Tenant", "Subscription", "ResourceGroup", "Location", or "Extension" + */ +export type ResourceBaseTypeDecorator = ( + context: DecoratorContext, + target: Model, + baseType: Type +) => void; diff --git a/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.ts-test.ts b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.ts-test.ts new file mode 100644 index 0000000000..d689c67714 --- /dev/null +++ b/packages/typespec-azure-resource-manager/generated-defs/Azure.ResourceManager.ts-test.ts @@ -0,0 +1,96 @@ +/** An error here would mean that the decorator is not exported or doesn't have the right name. */ +import { + $armCommonTypesVersion, + $armLibraryNamespace, + $armProviderNameValue, + $armProviderNamespace, + $armResourceAction, + $armResourceCollectionAction, + $armResourceCreateOrUpdate, + $armResourceDelete, + $armResourceList, + $armResourceOperations, + $armResourceRead, + $armResourceUpdate, + $armVirtualResource, + $extensionResource, + $locationResource, + $resourceBaseType, + $resourceGroupResource, + $singleton, + $subscriptionResource, + $tenantResource, + $useLibraryNamespace, +} from "@azure-tools/typespec-azure-resource-manager"; +import type { + ArmCommonTypesVersionDecorator, + ArmLibraryNamespaceDecorator, + ArmProviderNameValueDecorator, + ArmProviderNamespaceDecorator, + ArmResourceActionDecorator, + ArmResourceCollectionActionDecorator, + ArmResourceCreateOrUpdateDecorator, + ArmResourceDeleteDecorator, + ArmResourceListDecorator, + ArmResourceOperationsDecorator, + ArmResourceReadDecorator, + ArmResourceUpdateDecorator, + ArmVirtualResourceDecorator, + ExtensionResourceDecorator, + LocationResourceDecorator, + ResourceBaseTypeDecorator, + ResourceGroupResourceDecorator, + SingletonDecorator, + SubscriptionResourceDecorator, + TenantResourceDecorator, + UseLibraryNamespaceDecorator, +} from "./Azure.ResourceManager.js"; + +type Decorators = { + $armResourceCollectionAction: ArmResourceCollectionActionDecorator; + $armProviderNameValue: ArmProviderNameValueDecorator; + $armProviderNamespace: ArmProviderNamespaceDecorator; + $useLibraryNamespace: UseLibraryNamespaceDecorator; + $armLibraryNamespace: ArmLibraryNamespaceDecorator; + $singleton: SingletonDecorator; + $tenantResource: TenantResourceDecorator; + $subscriptionResource: SubscriptionResourceDecorator; + $locationResource: LocationResourceDecorator; + $resourceGroupResource: ResourceGroupResourceDecorator; + $extensionResource: ExtensionResourceDecorator; + $armResourceAction: ArmResourceActionDecorator; + $armResourceCreateOrUpdate: ArmResourceCreateOrUpdateDecorator; + $armResourceRead: ArmResourceReadDecorator; + $armResourceUpdate: ArmResourceUpdateDecorator; + $armResourceDelete: ArmResourceDeleteDecorator; + $armResourceList: ArmResourceListDecorator; + $armResourceOperations: ArmResourceOperationsDecorator; + $armCommonTypesVersion: ArmCommonTypesVersionDecorator; + $armVirtualResource: ArmVirtualResourceDecorator; + $resourceBaseType: ResourceBaseTypeDecorator; +}; + +/** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ +const _: Decorators = { + $armResourceCollectionAction, + $armProviderNameValue, + $armProviderNamespace, + $useLibraryNamespace, + $armLibraryNamespace, + $singleton, + $tenantResource, + $subscriptionResource, + $locationResource, + $resourceGroupResource, + $extensionResource, + $armResourceAction, + $armResourceCreateOrUpdate, + $armResourceRead, + $armResourceUpdate, + $armResourceDelete, + $armResourceList, + $armResourceOperations, + $armCommonTypesVersion, + $armVirtualResource, + $resourceBaseType, +}; diff --git a/packages/typespec-azure-resource-manager/lib/decorators.tsp b/packages/typespec-azure-resource-manager/lib/decorators.tsp index 36fdc6b5bd..2b99ea3b1f 100644 --- a/packages/typespec-azure-resource-manager/lib/decorators.tsp +++ b/packages/typespec-azure-resource-manager/lib/decorators.tsp @@ -113,7 +113,7 @@ extern dec extensionResource(target: Model); * property to the type of the Azure Resource Manager resource. * @param resource The resource to get the type of */ -extern dec armProviderNameValue(target: Operation | Model); +extern dec armProviderNameValue(target: Operation); /** * Marks the operation as being a collection action diff --git a/packages/typespec-azure-resource-manager/package.json b/packages/typespec-azure-resource-manager/package.json index 4ad7e71005..52fbe7d192 100644 --- a/packages/typespec-azure-resource-manager/package.json +++ b/packages/typespec-azure-resource-manager/package.json @@ -35,8 +35,9 @@ }, "scripts": { "clean": "rimraf ./dist ./temp", - "build": "tsc -p . && npm run lint-typespec-library", + "build": "npm run gen-extern-signature && tsc -p . && npm run lint-typespec-library", "watch": "tsc -p . --watch", + "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .", "lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit", "test": "vitest run", "test:watch": "vitest -w", diff --git a/packages/typespec-azure-resource-manager/src/common-types.ts b/packages/typespec-azure-resource-manager/src/common-types.ts index 5b0deb562d..41e65d70c3 100644 --- a/packages/typespec-azure-resource-manager/src/common-types.ts +++ b/packages/typespec-azure-resource-manager/src/common-types.ts @@ -3,7 +3,6 @@ import { Diagnostic, Enum, EnumMember, - EnumValue, Model, ModelProperty, Namespace, @@ -14,6 +13,7 @@ import { isTypeSpecValueTypeOf, } from "@typespec/compiler"; import { $useDependency, getVersion } from "@typespec/versioning"; +import { ArmCommonTypesVersionDecorator } from "../generated-defs/Azure.ResourceManager.js"; import { ArmCommonTypeRecord, ArmCommonTypesDefaultVersion, @@ -83,11 +83,11 @@ export function isArmCommonType(entity: Type): boolean { * @param {DecoratorContext} context DecoratorContext object * @param {type} entity Target of the decorator. Must be `Namespace` or `EnumMember` type */ -export function $armCommonTypesVersion( +export const $armCommonTypesVersion: ArmCommonTypesVersionDecorator = ( context: DecoratorContext, entity: Namespace | EnumMember, - version: string | EnumValue -) { + version: unknown // TODO: switch to precise type when tspd supports it: string | EnumValue +) => { // try convert string to EnumMember let versionEnum: EnumMember; if (typeof version === "string") { @@ -102,7 +102,7 @@ export function $armCommonTypesVersion( } versionEnum = foundEnumMember as EnumMember; } else { - versionEnum = version.value; + versionEnum = (version as any).value; } context.program.stateMap(ArmStateKeys.armCommonTypesVersion).set(entity, versionEnum.name); @@ -116,7 +116,7 @@ export function $armCommonTypesVersion( } // Add @useDependency on version enum members or on unversioned namespace context.call($useDependency, entity, versionEnum); -} +}; /** * Returns the ARM common-types version used by the service. diff --git a/packages/typespec-azure-resource-manager/src/commontypes.private.decorators.ts b/packages/typespec-azure-resource-manager/src/commontypes.private.decorators.ts index 03d9654137..af477d84de 100644 --- a/packages/typespec-azure-resource-manager/src/commontypes.private.decorators.ts +++ b/packages/typespec-azure-resource-manager/src/commontypes.private.decorators.ts @@ -7,6 +7,11 @@ import { Program, Union, } from "@typespec/compiler"; +import { + ArmCommonDefinitionDecorator, + ArmCommonParameterDecorator, + ArmCommonTypesVersionsDecorator, +} from "../generated-defs/Azure.ResourceManager.CommonTypes.Private.js"; import { ArmStateKeys } from "./state.js"; export const namespace = "Azure.ResourceManager.CommonTypes.Private"; @@ -24,7 +29,7 @@ function storeCommonTypeRecord( name: string, version?: string | EnumValue | ArmCommonTypeVersionSpec, referenceFile?: string -): void { +) { const basePath: string = getArmTypesPath(context.program).trim(); // NOTE: Right now we don't try to prevent multiple versions from declaring that they are the default @@ -89,20 +94,27 @@ interface ArmCommonTypeVersionSpec { * @param {string?} referenceFile Optional common file path * @returns void */ -export function $armCommonParameter( +export const $armCommonParameter: ArmCommonParameterDecorator = ( context: DecoratorContext, entity: ModelProperty, parameterName?: string, - version?: string | EnumValue | ArmCommonTypeVersionSpec, + version?: unknown, // TODO: switch to precise type when tspd supports it: string | EnumValue | ArmCommonTypeVersionSpec, referenceFile?: string -): void { +) => { // Use the name of the model type if not specified if (!parameterName) { parameterName = entity.name; } - storeCommonTypeRecord(context, entity, "parameters", parameterName, version, referenceFile); -} + storeCommonTypeRecord( + context, + entity, + "parameters", + parameterName, + version as any, + referenceFile + ); +}; /** * Using ARM common definition for a Model @@ -113,20 +125,27 @@ export function $armCommonParameter( * @param {string?} referenceFile Optional common file path * @returns {void} */ -export function $armCommonDefinition( +export const $armCommonDefinition: ArmCommonDefinitionDecorator = ( context: DecoratorContext, entity: Model | Enum | Union, definitionName?: string, - version?: string | EnumValue | ArmCommonTypeVersionSpec, + version?: unknown, // TODO: switch to precise type when tspd supports it: string | EnumValue | ArmCommonTypeVersionSpec, referenceFile?: string -): void { +) => { // Use the name of the model type if not specified if (!definitionName) { definitionName = entity.name!; } - storeCommonTypeRecord(context, entity, "definitions", definitionName, version, referenceFile); -} + storeCommonTypeRecord( + context, + entity, + "definitions", + definitionName, + version as any, + referenceFile + ); +}; /** * Specify the ARM commont type version reference for a particular spec version or namespace. @@ -134,9 +153,12 @@ export function $armCommonDefinition( * @param enumType entity Decorator target type. Must be `Model` * @returns void */ -export function $armCommonTypesVersions(context: DecoratorContext, enumType: Enum) { +export const $armCommonTypesVersions: ArmCommonTypesVersionsDecorator = ( + context: DecoratorContext, + enumType: Enum +) => { context.program.stateMap(ArmStateKeys.armCommonTypesVersions).set(enumType, { type: enumType, allVersions: Array.from(enumType.members.values()).reverse(), }); -} +}; diff --git a/packages/typespec-azure-resource-manager/src/index.ts b/packages/typespec-azure-resource-manager/src/index.ts index 57d3df6fea..297151a356 100644 --- a/packages/typespec-azure-resource-manager/src/index.ts +++ b/packages/typespec-azure-resource-manager/src/index.ts @@ -12,8 +12,6 @@ export { type ArmCommonTypeVersions, } from "./common-types.js"; -export { isAzureResource } from "./private.decorators.js"; - export * from "./namespace.js"; export * from "./operations.js"; export * from "./resource.js"; @@ -21,7 +19,7 @@ export * from "./resource.js"; export { $lib } from "./lib.js"; export { $linter } from "./linter.js"; -export { isConditionallyFlattened } from "./private.decorators.js"; +export { isAzureResource, isConditionallyFlattened } from "./private.decorators.js"; export const $flags = definePackageFlags({ decoratorArgMarshalling: "new", diff --git a/packages/typespec-azure-resource-manager/src/namespace.ts b/packages/typespec-azure-resource-manager/src/namespace.ts index a95686a542..4af14394f0 100644 --- a/packages/typespec-azure-resource-manager/src/namespace.ts +++ b/packages/typespec-azure-resource-manager/src/namespace.ts @@ -15,6 +15,11 @@ import { import * as http from "@typespec/http"; import { getAuthentication, setAuthentication, setRouteOptionsForNamespace } from "@typespec/http"; import { getResourceTypeForKeyParam } from "@typespec/rest"; +import { + ArmLibraryNamespaceDecorator, + ArmProviderNamespaceDecorator, + UseLibraryNamespaceDecorator, +} from "../generated-defs/Azure.ResourceManager.js"; import { $armCommonTypesVersion } from "./common-types.js"; import { reportDiagnostic } from "./lib.js"; import { getSingletonResourceKey } from "./resource.js"; @@ -49,7 +54,10 @@ function setArmCommonTypesVersionIfDoesnotExist( * @param context The doecorator context, automatically supplied by the compiler * @param entity The decorated namespace */ -export function $armLibraryNamespace(context: DecoratorContext, entity: Namespace) { +export const $armLibraryNamespace: ArmLibraryNamespaceDecorator = ( + context: DecoratorContext, + entity: Namespace +) => { const { program } = context; // HACK HACK HACK: Disable the linter rule that raises `use-standard-operations` @@ -59,7 +67,7 @@ export function $armLibraryNamespace(context: DecoratorContext, entity: Namespac program.stateMap(ArmStateKeys.armLibraryNamespaces).set(entity, true); setArmCommonTypesVersionIfDoesnotExist(context, entity, "v3"); -} +}; /** * Check if the given namespace contains ARM library types @@ -84,11 +92,11 @@ function isArmNamespaceOverride(program: Program, entity: Namespace): boolean { * @param {Namespace} entity The namespace the decorator is applied to * @param {Namespace[]} namespaces The library namespaces that will be used in this namespace */ -export function $useLibraryNamespace( +export const $useLibraryNamespace: UseLibraryNamespaceDecorator = ( context: DecoratorContext, entity: Namespace, ...namespaces: Namespace[] -) { +) => { const { program } = context; const provider = program.stateMap(ArmStateKeys.armProviderNamespaces).get(entity); @@ -97,7 +105,7 @@ export function $useLibraryNamespace( } program.stateMap(ArmStateKeys.usesArmLibraryNamespaces).set(entity, namespaces); -} +}; /** * Determine which library namespaces are used in this provider @@ -123,11 +131,11 @@ function setLibraryNamespaceProvider(program: Program, provider: string, namespa * @param {type} entity Target of the decorator. Must be `namespace` type * @param {string} armProviderNamespace Provider namespace */ -export function $armProviderNamespace( +export const $armProviderNamespace: ArmProviderNamespaceDecorator = ( context: DecoratorContext, entity: Namespace, armProviderNamespace?: string -) { +) => { const { program } = context; const override = isArmNamespaceOverride(program, entity); @@ -244,7 +252,7 @@ export function $armProviderNamespace( }, }); } -} +}; /** * Get the ARM provider namespace for a given entity diff --git a/packages/typespec-azure-resource-manager/src/operations.ts b/packages/typespec-azure-resource-manager/src/operations.ts index 96363de03c..b5640223ab 100644 --- a/packages/typespec-azure-resource-manager/src/operations.ts +++ b/packages/typespec-azure-resource-manager/src/operations.ts @@ -9,6 +9,15 @@ import { } from "@typespec/compiler"; import { getHttpOperation, HttpOperation } from "@typespec/http"; import { $actionSegment, getActionSegment, getParentResource, getSegment } from "@typespec/rest"; +import { + ArmResourceActionDecorator, + ArmResourceCollectionActionDecorator, + ArmResourceCreateOrUpdateDecorator, + ArmResourceDeleteDecorator, + ArmResourceListDecorator, + ArmResourceReadDecorator, + ArmResourceUpdateDecorator, +} from "../generated-defs/Azure.ResourceManager.js"; import { reportDiagnostic } from "./lib.js"; import { isArmLibraryNamespace } from "./namespace.js"; import { @@ -133,19 +142,19 @@ function setResourceLifecycleOperation( operations.lifecycle[kind] = operation as ArmResourceOperation; } -export function $armResourceRead( +export const $armResourceRead: ArmResourceReadDecorator = ( context: DecoratorContext, target: Operation, resourceType: Model -) { +) => { setResourceLifecycleOperation(context, target, resourceType, "read", "@armResourceRead"); -} +}; -export function $armResourceCreateOrUpdate( +export const $armResourceCreateOrUpdate: ArmResourceCreateOrUpdateDecorator = ( context: DecoratorContext, target: Operation, resourceType: Model -) { +) => { setResourceLifecycleOperation( context, target, @@ -153,29 +162,29 @@ export function $armResourceCreateOrUpdate( "createOrUpdate", "@armResourceCreateOrUpdate" ); -} +}; -export function $armResourceUpdate( +export const $armResourceUpdate: ArmResourceUpdateDecorator = ( context: DecoratorContext, target: Operation, resourceType: Model -) { +) => { setResourceLifecycleOperation(context, target, resourceType, "update", "@armResourceUpdate"); -} +}; -export function $armResourceDelete( +export const $armResourceDelete: ArmResourceDeleteDecorator = ( context: DecoratorContext, target: Operation, resourceType: Model -) { +) => { setResourceLifecycleOperation(context, target, resourceType, "delete", "@armResourceDelete"); -} +}; -export function $armResourceList( +export const $armResourceList: ArmResourceListDecorator = ( context: DecoratorContext, target: Operation, resourceType: Model -) { +) => { // Only register methods from non-templated interface types if (target.interface === undefined || target.interface.node.templateParameters.length > 0) { return; @@ -192,7 +201,7 @@ export function $armResourceList( }; operations.lists[target.name] = operation as ArmResourceOperation; -} +}; export function armRenameListByOperationInternal( context: DecoratorContext, @@ -281,11 +290,11 @@ function getArmParentName(program: Program, resource: Model): string[] { } } -export function $armResourceAction( +export const $armResourceAction: ArmResourceActionDecorator = ( context: DecoratorContext, target: Operation, resourceType: Model -) { +) => { const { program } = context; // Only register methods from non-templated interface types @@ -310,7 +319,7 @@ export function $armResourceAction( // Also apply the @actionSegment decorator to the operation context.call($actionSegment, target, uncapitalize(target.name)); } -} +}; function uncapitalize(name: string): string { if (name === "") { @@ -319,9 +328,12 @@ function uncapitalize(name: string): string { return name[0].toLowerCase() + name.substring(1); } -export function $armResourceCollectionAction(context: DecoratorContext, target: Operation) { +export const $armResourceCollectionAction: ArmResourceCollectionActionDecorator = ( + context: DecoratorContext, + target: Operation +) => { context.program.stateMap(ArmStateKeys.armResourceCollectionAction).set(target, true); -} +}; export function isArmCollectionAction(program: Program, target: Operation): boolean { return program.stateMap(ArmStateKeys.armResourceCollectionAction).get(target) === true; diff --git a/packages/typespec-azure-resource-manager/src/private.decorators.ts b/packages/typespec-azure-resource-manager/src/private.decorators.ts index 02983d1fb2..50069c4312 100644 --- a/packages/typespec-azure-resource-manager/src/private.decorators.ts +++ b/packages/typespec-azure-resource-manager/src/private.decorators.ts @@ -9,12 +9,27 @@ import { Program, StringLiteral, Tuple, + Type, getKeyName, getTypeName, } from "@typespec/compiler"; import { $segment, getSegment } from "@typespec/rest"; import { camelCase } from "change-case"; import pluralize from "pluralize"; +import { + ArmRenameListByOperationDecorator, + ArmResourceInternalDecorator, + ArmResourcePropertiesOptionalityDecorator, + ArmUpdateProviderNamespaceDecorator, + AssignProviderNameValueDecorator, + AzureResourceBaseDecorator, + ConditionalClientFlattenDecorator, + DefaultResourceKeySegmentNameDecorator, + EnforceConstraintDecorator, + OmitIfEmptyDecorator, + ResourceBaseParametersOfDecorator, + ResourceParameterBaseForDecorator, +} from "../generated-defs/Azure.ResourceManager.Private.js"; import { reportDiagnostic } from "./lib.js"; import { getArmProviderNamespace, isArmLibraryNamespace } from "./namespace.js"; import { armRenameListByOperationInternal } from "./operations.js"; @@ -30,7 +45,12 @@ import { ArmStateKeys } from "./state.js"; export const namespace = "Azure.ResourceManager.Private"; -export function $omitIfEmpty(context: DecoratorContext, entity: Model, propertyName: string) { +/** @internal */ +export const $omitIfEmpty: OmitIfEmptyDecorator = ( + context: DecoratorContext, + entity: Model, + propertyName: string +) => { const modelProp = getProperty(entity, propertyName); if ( @@ -40,14 +60,14 @@ export function $omitIfEmpty(context: DecoratorContext, entity: Model, propertyN ) { entity.properties.delete(propertyName); } -} +}; -export function $enforceConstraint( +export const $enforceConstraint: EnforceConstraintDecorator = ( context: DecoratorContext, entity: Operation | Model, sourceType: Model, constraintType: Model -) { +) => { if (sourceType !== undefined && constraintType !== undefined) { // walk the baseModel chain until find a match or fail let baseType: Model | undefined = sourceType; @@ -66,13 +86,13 @@ export function $enforceConstraint( }, }); } -} +}; -export function $resourceBaseParametersOf( +export const $resourceBaseParametersOf: ResourceBaseParametersOfDecorator = ( context: DecoratorContext, entity: Model, resourceType: Model -) { +) => { const targetResourceBaseType: ResourceBaseType = getResourceBaseType( context.program, resourceType @@ -86,30 +106,31 @@ export function $resourceBaseParametersOf( for (const removedProperty of removedProperties) { entity.properties.delete(removedProperty); } -} +}; -export function $resourceParameterBaseFor( +export const $resourceParameterBaseFor: ResourceParameterBaseForDecorator = ( context: DecoratorContext, entity: ModelProperty, - values: Tuple -) { + values: Type +) => { const resolvedValues: string[] = []; - for (const value of values.values) { + // TODO this will crash if passed anything other than a tuple + for (const value of (values as Tuple).values) { if (value.kind !== "EnumMember") { return; } resolvedValues.push(value.name); } context.program.stateMap(ArmStateKeys.armResourceCollection).set(entity, resolvedValues); -} +}; -export function $defaultResourceKeySegmentName( +export const $defaultResourceKeySegmentName: DefaultResourceKeySegmentNameDecorator = ( context: DecoratorContext, entity: ModelProperty, resource: Model, keyName: string, segment: string -) { +) => { const modelName = camelCase(resource.name); const pluralName = pluralize(modelName); if (keyName.length > 0) { @@ -122,7 +143,7 @@ export function $defaultResourceKeySegmentName( } else { context.call($segment, entity, pluralName); } -} +}; export function getResourceParameterBases( program: Program, @@ -166,18 +187,18 @@ function isResourceParameterBaseForInternal( * @param {Type} target Target of this decorator. Must be a string `ModelProperty`. * @param {Type} resourceType Must be a `Model`. */ -export function $assignProviderNameValue( +export const $assignProviderNameValue: AssignProviderNameValueDecorator = ( context: DecoratorContext, target: ModelProperty, resourceType: Model -): void { +) => { const { program } = context; const armProviderNamespace = getArmProviderNamespace(program, resourceType as Model); if (armProviderNamespace) { (target.type as StringLiteral).value = armProviderNamespace; } -} +}; /** * Update the ARM provider namespace for a given entity. @@ -185,7 +206,10 @@ export function $assignProviderNameValue( * @param {Type} entity Entity to set namespace. Must be a `Operation`. * @returns */ -export function $armUpdateProviderNamespace(context: DecoratorContext, entity: Operation) { +export const $armUpdateProviderNamespace: ArmUpdateProviderNamespaceDecorator = ( + context: DecoratorContext, + entity: Operation +) => { const { program } = context; const operation = entity as Operation; @@ -209,7 +233,7 @@ export function $armUpdateProviderNamespace(context: DecoratorContext, entity: O } } } -} +}; /** * Check if an interface is extending the Azure.ResourceManager.Operations interface. @@ -238,11 +262,11 @@ export function isArmOperationsListInterface(program: Program, type: Interface): * decorator, so it also gets applied to the type which absorbs the `TrackedResource` * definition by using the `is` keyword. */ -export function $armResourceInternal( +export const $armResourceInternal: ArmResourceInternalDecorator = ( context: DecoratorContext, resourceType: Model, propertiesType: Model -) { +) => { const { program } = context; if (resourceType.namespace && getTypeName(resourceType.namespace) === "Azure.ResourceManager") { @@ -324,7 +348,7 @@ export function $armResourceInternal( }; program.stateMap(ArmStateKeys.armResources).set(resourceType, armResourceDetails); -} +}; export function listArmResources(program: Program): ArmResourceDetails[] { return [...program.stateMap(ArmStateKeys.armResources).values()]; @@ -352,9 +376,12 @@ function hasProperty(program: Program, model: Model): boolean { return false; } -export function $azureResourceBase(context: DecoratorContext, resourceType: Model) { +export const $azureResourceBase: AzureResourceBaseDecorator = ( + context: DecoratorContext, + resourceType: Model +) => { context.program.stateMap(ArmStateKeys.azureResourceBase).set(resourceType, true); -} +}; export function isAzureResource(program: Program, resourceType: Model): boolean { const isResourceBase = program.stateMap(ArmStateKeys.azureResourceBase).get(resourceType); @@ -367,23 +394,26 @@ export function isAzureResource(program: Program, resourceType: Model): boolean * It will programatically enabled/disable client flattening with @flattenProperty with autorest * emitter flags to maintain compatibility in swagger. */ -export function $conditionalClientFlatten(context: DecoratorContext, entity: ModelProperty) { +export const $conditionalClientFlatten: ConditionalClientFlattenDecorator = ( + context: DecoratorContext, + entity: ModelProperty +) => { context.program.stateMap(ArmStateKeys.armConditionalClientFlatten).set(entity, true); -} +}; export function isConditionallyFlattened(program: Program, entity: ModelProperty): boolean { const flatten = program.stateMap(ArmStateKeys.armConditionalClientFlatten).get(entity); return flatten ?? false; } -export function $armRenameListByOperation( +export const $armRenameListByOperation: ArmRenameListByOperationDecorator = ( context: DecoratorContext, entity: Operation, resourceType: Model, parentTypeName?: string, parentFriendlyTypeName?: string, applyOperationRename?: boolean -) { +) => { armRenameListByOperationInternal( context, entity, @@ -392,14 +422,14 @@ export function $armRenameListByOperation( parentFriendlyTypeName, applyOperationRename ); -} +}; -export function $armResourcePropertiesOptionality( +export const $armResourcePropertiesOptionality: ArmResourcePropertiesOptionalityDecorator = ( context: DecoratorContext, target: ModelProperty, isOptional: boolean -) { +) => { if (target.name === "properties") { target.optional = isOptional; } -} +}; diff --git a/packages/typespec-azure-resource-manager/src/resource.ts b/packages/typespec-azure-resource-manager/src/resource.ts index fc92f3765a..fb8d3c452b 100644 --- a/packages/typespec-azure-resource-manager/src/resource.ts +++ b/packages/typespec-azure-resource-manager/src/resource.ts @@ -16,6 +16,18 @@ import { } from "@typespec/compiler"; import { isPathParam } from "@typespec/http"; import { $autoRoute, getParentResource, getSegment } from "@typespec/rest"; +import { + ArmProviderNameValueDecorator, + ArmResourceOperationsDecorator, + ArmVirtualResourceDecorator, + ExtensionResourceDecorator, + LocationResourceDecorator, + ResourceBaseTypeDecorator, + ResourceGroupResourceDecorator, + SingletonDecorator, + SubscriptionResourceDecorator, + TenantResourceDecorator, +} from "../generated-defs/Azure.ResourceManager.js"; import { reportDiagnostic } from "./lib.js"; import { getArmProviderNamespace, isArmLibraryNamespace } from "./namespace.js"; import { ArmResourceOperations, resolveResourceOperations } from "./operations.js"; @@ -49,7 +61,10 @@ export interface ArmResourceDetails extends ArmResourceDetailsBase { * @param entity The resource model * @param propertiesType The type of the resource properties */ -export function $armVirtualResource(context: DecoratorContext, entity: Model) { +export const $armVirtualResource: ArmVirtualResourceDecorator = ( + context: DecoratorContext, + entity: Model +) => { const { program } = context; if (isTemplateDeclaration(entity)) return; program.stateMap(ArmStateKeys.armBuiltInResource).set(entity, "Virtual"); @@ -75,7 +90,7 @@ export function $armVirtualResource(context: DecoratorContext, entity: Model) { }); return; } -} +}; function getProperty( target: Model, @@ -231,7 +246,10 @@ export function getArmResourceKind(resourceType: Model): ArmResourceKind | undef * of the operations will be grouped based on the interface name in generated * clients. */ -export function $armResourceOperations(context: DecoratorContext, interfaceType: Interface): void { +export const $armResourceOperations: ArmResourceOperationsDecorator = ( + context: DecoratorContext, + interfaceType: Interface +) => { const { program } = context; // All resource interfaces should use @autoRoute @@ -241,7 +259,7 @@ export function $armResourceOperations(context: DecoratorContext, interfaceType: if (getTags(program, interfaceType).length === 0) { context.call($tag, interfaceType, interfaceType.name); } -} +}; /** * This decorator is used to mark a resource type as a "singleton", a type with @@ -249,13 +267,13 @@ export function $armResourceOperations(context: DecoratorContext, interfaceType: * such a resource type, they will generate the correct routes and parameter * lists. */ -export function $singleton( +export const $singleton: SingletonDecorator = ( context: DecoratorContext, resourceType: Model, keyValue: string = "default" -): void { +) => { context.program.stateMap(ArmStateKeys.armSingletonResources).set(resourceType, keyValue); -} +}; export function isSingletonResource(program: Program, resourceType: Model): boolean { return program.stateMap(ArmStateKeys.armSingletonResources).has(resourceType); @@ -273,34 +291,56 @@ export enum ResourceBaseType { Extension = "Extension", } -export function $resourceBaseType(context: DecoratorContext, entity: Model, baseType: Type) { +export const $resourceBaseType: ResourceBaseTypeDecorator = ( + context: DecoratorContext, + entity: Model, + baseType: Type +) => { let baseTypeString: string = ""; if (isNeverType(baseType)) return; if (baseType?.kind === "String") baseTypeString = baseType.value; setResourceBaseType(context.program, entity, baseTypeString); -} +}; -export function $tenantResource(context: DecoratorContext, entity: Model) { +export const $tenantResource: TenantResourceDecorator = ( + context: DecoratorContext, + entity: Model +) => { setResourceBaseType(context.program, entity, "Tenant"); -} +}; -export function $subscriptionResource(context: DecoratorContext, entity: Model) { +export const $subscriptionResource: SubscriptionResourceDecorator = ( + context: DecoratorContext, + entity: Model +) => { setResourceBaseType(context.program, entity, "Subscription"); -} +}; -export function $locationResource(context: DecoratorContext, entity: Model) { +export const $locationResource: LocationResourceDecorator = ( + context: DecoratorContext, + entity: Model +) => { setResourceBaseType(context.program, entity, "Location"); -} +}; -export function $resourceGroupResource(context: DecoratorContext, entity: Model) { +export const $resourceGroupResource: ResourceGroupResourceDecorator = ( + context: DecoratorContext, + entity: Model +) => { setResourceBaseType(context.program, entity, "ResourceGroup"); -} +}; -export function $extensionResource(context: DecoratorContext, entity: Model) { +export const $extensionResource: ExtensionResourceDecorator = ( + context: DecoratorContext, + entity: Model +) => { setResourceBaseType(context.program, entity, "Extension"); -} +}; -export function $armProviderNameValue(context: DecoratorContext, entity: Operation) { +export const $armProviderNameValue: ArmProviderNameValueDecorator = ( + context: DecoratorContext, + entity: Operation +) => { const armProvider = getServiceNamespace(context.program, entity); if (armProvider === undefined) return; for (const [_, property] of entity.parameters.properties) { @@ -308,7 +348,7 @@ export function $armProviderNameValue(context: DecoratorContext, entity: Operati if (segment === "providers" && property.type.kind === "String") property.type.value = armProvider; } -} +}; function getServiceNamespace(program: Program, type: Type | undefined): string | undefined { if (type === undefined) return undefined; diff --git a/packages/typespec-azure-resource-manager/tsconfig.json b/packages/typespec-azure-resource-manager/tsconfig.json index bf2fb9f39f..26e7edad86 100644 --- a/packages/typespec-azure-resource-manager/tsconfig.json +++ b/packages/typespec-azure-resource-manager/tsconfig.json @@ -13,5 +13,5 @@ "rootDir": ".", "tsBuildInfoFile": "temp/tsconfig.tsbuildinfo" }, - "include": ["src/**/*.ts", "test/**/*.ts"] + "include": ["src/**/*.ts", "generated-defs/**/*.ts", "test/**/*.ts"] } From 003c4a1b45bbcb93e7f336b8f23d3b703431722c Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Thu, 18 Jul 2024 14:35:45 -0700 Subject: [PATCH 3/3] TCGC generate decorator signatures (#1201) --- ...-tcgc-dec-signatures-2024-6-18-13-22-29.md | 6 + .../reference/decorators.md | 4 +- .../typespec-client-generator-core/README.md | 4 +- .../Azure.ClientGenerator.Core.ts | 433 ++++++++++++++++++ .../Azure.ClientGenerator.Core.ts-test.ts | 60 +++ .../lib/decorators.tsp | 2 +- .../package.json | 3 +- .../src/decorators.ts | 98 ++-- .../tsconfig.json | 2 +- 9 files changed, 570 insertions(+), 42 deletions(-) create mode 100644 .chronus/changes/generate-tcgc-dec-signatures-2024-6-18-13-22-29.md create mode 100644 packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts create mode 100644 packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts-test.ts diff --git a/.chronus/changes/generate-tcgc-dec-signatures-2024-6-18-13-22-29.md b/.chronus/changes/generate-tcgc-dec-signatures-2024-6-18-13-22-29.md new file mode 100644 index 0000000000..40b3c23d94 --- /dev/null +++ b/.chronus/changes/generate-tcgc-dec-signatures-2024-6-18-13-22-29.md @@ -0,0 +1,6 @@ +--- +changeKind: internal +packages: + - "@azure-tools/typespec-client-generator-core" +--- + diff --git a/docs/libraries/typespec-client-generator-core/reference/decorators.md b/docs/libraries/typespec-client-generator-core/reference/decorators.md index 4efe92395c..36bd651b37 100644 --- a/docs/libraries/typespec-client-generator-core/reference/decorators.md +++ b/docs/libraries/typespec-client-generator-core/reference/decorators.md @@ -152,7 +152,7 @@ op func8(@body body: Test5): void; Create a ClientGenerator.Core client out of a namespace or interface ```typespec -@Azure.ClientGenerator.Core.client(value?: {}, scope?: valueof string) +@Azure.ClientGenerator.Core.client(value?: Model, scope?: valueof string) ``` #### Target @@ -163,7 +163,7 @@ Create a ClientGenerator.Core client out of a namespace or interface | Name | Type | Description | | ----- | ---------------- | ------------------------------------------------------------------------------------------------------------- | -| value | `{}` | Optional configuration for the service. | +| value | `Model` | Optional configuration for the service. | | scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters | #### Examples diff --git a/packages/typespec-client-generator-core/README.md b/packages/typespec-client-generator-core/README.md index 76f11fbe28..13e4b1f39a 100644 --- a/packages/typespec-client-generator-core/README.md +++ b/packages/typespec-client-generator-core/README.md @@ -169,7 +169,7 @@ op func8(@body body: Test5): void; Create a ClientGenerator.Core client out of a namespace or interface ```typespec -@Azure.ClientGenerator.Core.client(value?: {}, scope?: valueof string) +@Azure.ClientGenerator.Core.client(value?: Model, scope?: valueof string) ``` ##### Target @@ -180,7 +180,7 @@ Create a ClientGenerator.Core client out of a namespace or interface | Name | Type | Description | | ----- | ---------------- | ------------------------------------------------------------------------------------------------------------- | -| value | `{}` | Optional configuration for the service. | +| value | `Model` | Optional configuration for the service. | | scope | `valueof string` | The language scope you want this decorator to apply to. If not specified, will apply to all language emitters | ##### Examples diff --git a/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts b/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts new file mode 100644 index 0000000000..c4facd7f8e --- /dev/null +++ b/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts @@ -0,0 +1,433 @@ +import type { + DecoratorContext, + Enum, + EnumMember, + Interface, + Model, + ModelProperty, + Namespace, + Operation, + Type, + Union, +} from "@typespec/compiler"; + +/** + * Changes the name of a method, parameter, property, or model generated in the client SDK + * + * @param rename The rename you want applied to the object + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example + * ```typespec + * @clientName("nameInClient") + * op nameInService: void; + * ``` + * @example + * ```typespec + * @clientName("nameForJava", "java") + * @clientName("name_for_python", "python") + * @clientName("nameForCsharp", "csharp") + * @clientName("nameForJavascript", "javascript") + * op nameInService: void; + * ``` + */ +export type ClientNameDecorator = ( + context: DecoratorContext, + target: Type, + rename: string, + scope?: string +) => void; + +/** + * Whether you want to generate an operation as a convenient operation. + * + * @param value Whether to generate the operation as convenience method or not. + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example + * ```typespec + * @convenientAPI(false) + * op test: void; + * ``` + */ +export type ConvenientAPIDecorator = ( + context: DecoratorContext, + target: Operation, + value?: boolean, + scope?: string +) => void; + +/** + * Whether you want to generate an operation as a protocol operation. + * + * @param value Whether to generate the operation as protocol or not. + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example + * ```typespec + * @protocolAPI(false) + * op test: void; + * ``` + */ +export type ProtocolAPIDecorator = ( + context: DecoratorContext, + target: Operation, + value?: boolean, + scope?: string +) => void; + +/** + * Create a ClientGenerator.Core client out of a namespace or interface + * + * @param value Optional configuration for the service. + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example Basic client setting + * ```typespec + * @client + * namespace MyService {} + * ``` + * @example Setting with other service + * ```typespec + * namespace MyService {} + * + * @client({service: MyService}) + * interface MyInterface {} + * ``` + * @example Changing client name if you don't want Client + * ```typespec + * @client({client: MySpecialClient}) + * interface MyInterface {} + * ``` + * @example + * + * + */ +export type ClientDecorator = ( + context: DecoratorContext, + target: Namespace | Interface, + value?: Model, + scope?: string +) => void; + +/** + * Create a ClientGenerator.Core operation group out of a namespace or interface + * + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example + * ```typespec + * @operationGroup + * interface MyInterface{} + * ``` + */ +export type OperationGroupDecorator = ( + context: DecoratorContext, + target: Namespace | Interface, + scope?: string +) => void; + +/** + * DEPRECATED: Use `@usage` and `@access` decorator instead. + * + * Whether to exclude a model from generation for specific languages. By default we generate + * all models that are included in operations. + * + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example + * ```typespec + * @exclude("python") + * model ModelToExclude { + * prop: string; + * } + * ``` + */ +export type ExcludeDecorator = (context: DecoratorContext, target: Model, scope?: string) => void; + +/** + * DEPRECATED: Use `@usage` and `@access` decorator instead. + * + * Whether to include a model in generation for specific languages. By default we generate + * all models that are included in operations. + * + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example + * ```typespec + * @include("python") + * model ModelToInclude { + * prop: string; + * } + * ``` + */ +export type IncludeDecorator = (context: DecoratorContext, target: Model, scope?: string) => void; + +/** + * DEPRECATED: Use `@encode` decorator in `@typespec/compiler` instead. + * + * Can be used to explain the client type that the current TYPESPEC + * type should map to. + * + * @param value The client format to apply. + * @example + * ```typespec + * model MyModel { + * @clientFormat("unixtime") + * created_at?: int64 + * } + * ``` + */ +export type ClientFormatDecorator = ( + context: DecoratorContext, + target: ModelProperty, + value: "unixtime" | "iso8601" | "rfc1123" | "seconds" +) => void; + +/** + * DEPRECATED: Use `@access` decorator instead. + * + * Whether to mark an operation as internal for specific languages, + * meaning it should not be exposed to end SDK users + * + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example + * ```typespec + * @internal("python") + * op test: void; + * ``` + */ +export type InternalDecorator = ( + context: DecoratorContext, + target: Operation, + scope?: string +) => void; + +/** + * Expand usage for models/enums. + * A model/enum's default usage info is always calculated by the operations that use it. + * You could use this decorator to expand the default usage info. + * For example, with operation definition `op test(): OutputModel`, + * the model `OutputModel` has default usage `Usage.output`. + * After adding decorator `@@usage(OutputModel, Usage.input)`, + * the final usage result for `OutputModel` is `Usage.input | Usage.output`. + * The calculation of default usage info for models will be propagated to models' properties, + * parent models, discriminated sub models. + * But the expanded usage from `@usage` decorator will not be propagated. + * If you want to do any customization for the usage of a model, + * you need to take care of all related models/enums. + * + * @param value The usage info you want to set for this model. + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example Expand usage for model + * ```typespec + * op test(): OutputModel; + * + * // usage result for `OutputModel` is `Usage.input | Usage.output` + * @usage(Usage.input) + * model OutputModel { + * prop: string + * } + * ``` + * @example Propagation of usage + * ```typespec + * // Usage.output + * @discriminator("kind") + * model Fish { + * age: int32; + * } + * + * // Usage.input | Usage.output + * @discriminator("sharktype") + * @usage(Usage.input) + * model Shark extends Fish { + * kind: "shark"; + * origin: Origin; + * } + * + * // Usage.output + * model Salmon extends Fish { + * kind: "salmon"; + * } + * + * // Usage.output + * model SawShark extends Shark { + * sharktype: "saw"; + * } + * + * // Usage.output + * model Origin { + * country: string; + * city: string; + * manufacture: string; + * } + * + * @get + * op getModel(): Fish; + * ``` + */ +export type UsageDecorator = ( + context: DecoratorContext, + target: Model | Enum | Union, + value: EnumMember | Union, + scope?: string +) => void; + +/** + * Set explicit access for operations, models and enums. + * When setting access for models, + * the access info wll not be propagated to models' properties, base models or sub models. + * When setting access for an operation, + * it will influence the access info for models/enums that are used by this operation. + * Models/enums that are used in any operations with `@access(Access.public)` will be implicitly set to access "public" + * Models/enums that are only used in operations with `@access(Access.internal)` will be implicitly set to access "internal". + * This influence will be propagated to models' properties, parent models, discriminated sub models. + * But this influence will be override by `@usage` decorator on models/enums directly. + * If an operation/model/enum has no `@access` decorator and is not influenced by any operation with `@access` decorator, + * the access result is undefined. + * + * @param value The access info you want to set for this model or operation. + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example Set access + * ```typespec + * // Access.internal + * @access(Access.internal) + * model ModelToHide { + * prop: string; + * } + * // Access.internal + * @access(Access.internal) + * op test: void; + * ``` + * @example Access propagation + * ```typespec + * // Access.internal + * @discriminator("kind") + * model Fish { + * age: int32; + * } + * + * // Access.internal + * @discriminator("sharktype") + * model Shark extends Fish { + * kind: "shark"; + * origin: Origin; + * } + * + * // Access.internal + * model Salmon extends Fish { + * kind: "salmon"; + * } + * + * // Access.internal + * model SawShark extends Shark { + * sharktype: "saw"; + * } + * + * // Access.internal + * model Origin { + * country: string; + * city: string; + * manufacture: string; + * } + * + * // Access.internal + * @get + * @access(Access.internal) + * op getModel(): Fish; + * ``` + * @example Access influence from operation + * ```typespec + * // Access.internal + * model Test1 { + * } + * + * // Access.internal + * @access(Access.internal) + * @route("/func1") + * op func1( + * @body body: Test1 + * ): void; + * + * // undefined + * model Test2 { + * } + * + * // undefined + * @route("/func2") + * op func2( + * @body body: Test2 + * ): void; + * + * // Access.public + * model Test3 { + * } + * + * // Access.public + * @access(Access.public) + * @route("/func3") + * op func3( + * @body body: Test3 + * ): void; + * + * // undefined + * model Test4 { + * } + * + * // Access.internal + * @access(Access.internal) + * @route("/func4") + * op func4( + * @body body: Test4 + * ): void; + * + * // undefined + * @route("/func5") + * op func5( + * @body body: Test4 + * ): void; + * + * // Access.public + * model Test5 { + * } + * + * // Access.internal + * @access(Access.internal) + * @route("/func6") + * op func6( + * @body body: Test5 + * ): void; + * + * // undefined + * @route("/func7") + * op func7( + * @body body: Test5 + * ): void; + * + * // Access.public + * @access(Access.public) + * @route("/func8") + * op func8( + * @body body: Test5 + * ): void; + * ``` + */ +export type AccessDecorator = ( + context: DecoratorContext, + target: Model | Operation | Enum | Union, + value: EnumMember, + scope?: string +) => void; + +/** + * Set whether a model property should be flattened or not. + * + * @param scope The language scope you want this decorator to apply to. If not specified, will apply to all language emitters + * @example + * ```typespec + * model Foo { + * @flattenProperty + * prop: Bar; + * } + * model Bar { + * } + * ``` + */ +export type FlattenPropertyDecorator = ( + context: DecoratorContext, + target: ModelProperty, + scope?: string +) => void; diff --git a/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts-test.ts b/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts-test.ts new file mode 100644 index 0000000000..87f2dbfe3e --- /dev/null +++ b/packages/typespec-client-generator-core/generated-defs/Azure.ClientGenerator.Core.ts-test.ts @@ -0,0 +1,60 @@ +/** An error here would mean that the decorator is not exported or doesn't have the right name. */ +import { + $access, + $client, + $clientFormat, + $clientName, + $convenientAPI, + $exclude, + $flattenProperty, + $include, + $internal, + $operationGroup, + $protocolAPI, + $usage, +} from "@azure-tools/typespec-client-generator-core"; +import type { + AccessDecorator, + ClientDecorator, + ClientFormatDecorator, + ClientNameDecorator, + ConvenientAPIDecorator, + ExcludeDecorator, + FlattenPropertyDecorator, + IncludeDecorator, + InternalDecorator, + OperationGroupDecorator, + ProtocolAPIDecorator, + UsageDecorator, +} from "./Azure.ClientGenerator.Core.js"; + +type Decorators = { + $clientName: ClientNameDecorator; + $convenientAPI: ConvenientAPIDecorator; + $protocolAPI: ProtocolAPIDecorator; + $client: ClientDecorator; + $operationGroup: OperationGroupDecorator; + $exclude: ExcludeDecorator; + $include: IncludeDecorator; + $clientFormat: ClientFormatDecorator; + $internal: InternalDecorator; + $usage: UsageDecorator; + $access: AccessDecorator; + $flattenProperty: FlattenPropertyDecorator; +}; + +/** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ +const _: Decorators = { + $clientName, + $convenientAPI, + $protocolAPI, + $client, + $operationGroup, + $exclude, + $include, + $clientFormat, + $internal, + $usage, + $access, + $flattenProperty, +}; diff --git a/packages/typespec-client-generator-core/lib/decorators.tsp b/packages/typespec-client-generator-core/lib/decorators.tsp index b258f72761..324590da89 100644 --- a/packages/typespec-client-generator-core/lib/decorators.tsp +++ b/packages/typespec-client-generator-core/lib/decorators.tsp @@ -77,7 +77,7 @@ extern dec protocolAPI(target: Operation, value?: valueof boolean, scope?: value * * @example */ -extern dec client(target: Namespace | Interface, value?: {}, scope?: valueof string); +extern dec client(target: Namespace | Interface, value?: Model, scope?: valueof string); /** * Create a ClientGenerator.Core operation group out of a namespace or interface diff --git a/packages/typespec-client-generator-core/package.json b/packages/typespec-client-generator-core/package.json index b67598c337..b8c780c918 100644 --- a/packages/typespec-client-generator-core/package.json +++ b/packages/typespec-client-generator-core/package.json @@ -36,8 +36,9 @@ }, "scripts": { "clean": "rimraf ./dist ./temp", - "build": "tsc -p . && npm run lint-typespec-library", + "build": "npm run gen-extern-signature && tsc -p . && npm run lint-typespec-library", "watch": "tsc -p . --watch", + "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .", "lint-typespec-library": "tsp compile . --warn-as-error --import @typespec/library-linter --no-emit", "test": "vitest run", "test:watch": "vitest -w", diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index 4735775550..2a7d8ad373 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -29,6 +29,20 @@ import { } from "@typespec/compiler"; import { isHeader } from "@typespec/http"; import { buildVersionProjections, getVersions } from "@typespec/versioning"; +import { + AccessDecorator, + ClientDecorator, + ClientFormatDecorator, + ClientNameDecorator, + ConvenientAPIDecorator, + ExcludeDecorator, + FlattenPropertyDecorator, + IncludeDecorator, + InternalDecorator, + OperationGroupDecorator, + ProtocolAPIDecorator, + UsageDecorator, +} from "../generated-defs/Azure.ClientGenerator.Core.js"; import { defaultDecoratorsAllowList } from "./configs.js"; import { AccessFlags, @@ -123,12 +137,12 @@ function isArm(service: Namespace): boolean { ); } -export function $client( +export const $client: ClientDecorator = ( context: DecoratorContext, target: Namespace | Interface, options?: Model, scope?: LanguageScopes -) { +) => { if ((context.decoratorTarget as Node).kind === SyntaxKind.AugmentDecoratorStatement) { reportDiagnostic(context.program, { code: "wrong-client-decorator", @@ -168,7 +182,7 @@ export function $client( crossLanguageDefinitionId: `${getNamespaceFullName(service)}.${name}`, }; setScopedDecoratorData(context, $client, clientKey, target, client, scope); -} +}; function findClientService( program: Program, @@ -330,11 +344,11 @@ export function listClients(context: TCGCContext): SdkClient[] { const operationGroupKey = createStateSymbol("operationGroup"); -export function $operationGroup( +export const $operationGroup: OperationGroupDecorator = ( context: DecoratorContext, target: Namespace | Interface, scope?: LanguageScopes -) { +) => { if ((context.decoratorTarget as Node).kind === SyntaxKind.AugmentDecoratorStatement) { reportDiagnostic(context.program, { code: "wrong-client-decorator", @@ -363,7 +377,7 @@ export function $operationGroup( }, scope ); -} +}; /** * Check a namespace or interface is an operation group. @@ -645,25 +659,25 @@ export function createSdkContext< const protocolAPIKey = createStateSymbol("protocolAPI"); -export function $protocolAPI( +export const $protocolAPI: ProtocolAPIDecorator = ( context: DecoratorContext, entity: Operation, - value: boolean, + value?: boolean, scope?: LanguageScopes -) { +) => { setScopedDecoratorData(context, $protocolAPI, protocolAPIKey, entity, value, scope); -} +}; const convenientAPIKey = createStateSymbol("convenientAPI"); -export function $convenientAPI( +export const $convenientAPI: ConvenientAPIDecorator = ( context: DecoratorContext, entity: Operation, - value: boolean, + value?: boolean, scope?: LanguageScopes -) { +) => { setScopedDecoratorData(context, $convenientAPI, convenientAPIKey, entity, value, scope); -} +}; export function shouldGenerateProtocol(context: TCGCContext, entity: Operation): boolean { const value = getScopedDecoratorData(context, protocolAPIKey, entity); @@ -680,18 +694,26 @@ const excludeKey = createStateSymbol("exclude"); /** * @deprecated Use `usage` and `access` decorator instead. */ -export function $exclude(context: DecoratorContext, entity: Model, scope?: LanguageScopes) { +export const $exclude: ExcludeDecorator = ( + context: DecoratorContext, + entity: Model, + scope?: LanguageScopes +) => { setScopedDecoratorData(context, $exclude, excludeKey, entity, true, scope); // eslint-disable-line deprecation/deprecation -} +}; const includeKey = createStateSymbol("include"); /** * @deprecated Use `usage` and `access` decorator instead. */ -export function $include(context: DecoratorContext, entity: Model, scope?: LanguageScopes) { +export const $include: IncludeDecorator = ( + context: DecoratorContext, + entity: Model, + scope?: LanguageScopes +) => { modelTransitiveSet(context, $include, includeKey, entity, true, scope); // eslint-disable-line deprecation/deprecation -} +}; /** * @deprecated This function is unused and will be removed in a future release. @@ -745,12 +767,12 @@ export type ClientFormat = "unixtime" | "iso8601" | "rfc1123" | "seconds"; /** * @deprecated Use `encode` decorator in `@typespec/core` instead. */ -export function $clientFormat( +export const $clientFormat: ClientFormatDecorator = ( context: DecoratorContext, target: ModelProperty, format: ClientFormat, scope?: LanguageScopes -) { +) => { const expectedTargetTypes = allowedClientFormatToTargetTypeMap[format]; if ( context.program.checker.isStdType(target.type) && @@ -764,7 +786,7 @@ export function $clientFormat( target: context.decoratorTarget, }); } -} +}; /** * Gets additional information on how to serialize / deserialize TYPESPEC standard types depending @@ -802,10 +824,16 @@ const internalKey = createStateSymbol("internal"); * @param target Operation to mark as internal * @param scope Names of the projection (e.g. "python", "csharp", "java", "javascript") * @deprecated Use `access` decorator instead. + * + * @internal */ -export function $internal(context: DecoratorContext, target: Operation, scope?: LanguageScopes) { +export const $internal: InternalDecorator = ( + context: DecoratorContext, + target: Operation, + scope?: LanguageScopes +) => { setScopedDecoratorData(context, $internal, internalKey, target, true, scope); // eslint-disable-line deprecation/deprecation -} +}; /** * Whether a model / operation is internal or not. If it's internal, emitters @@ -840,12 +868,12 @@ export function isInternal( const usageKey = createStateSymbol("usage"); -export function $usage( +export const $usage: UsageDecorator = ( context: DecoratorContext, entity: Model | Enum | Union, value: EnumMember | Union, scope?: LanguageScopes -) { +) => { const isValidValue = (value: number): boolean => value === 2 || value === 4; if (value.kind === "EnumMember") { @@ -876,7 +904,7 @@ export function $usage( format: {}, target: entity, }); -} +}; export function getUsageOverride( context: TCGCContext, @@ -893,12 +921,12 @@ export function getUsage(context: TCGCContext, entity: Model | Enum): UsageFlags const accessKey = createStateSymbol("access"); -export function $access( +export const $access: AccessDecorator = ( context: DecoratorContext, entity: Model | Enum | Operation | Union, value: EnumMember, scope?: LanguageScopes -) { +) => { if (typeof value.value !== "string" || (value.value !== "public" && value.value !== "internal")) { reportDiagnostic(context.program, { code: "access", @@ -907,7 +935,7 @@ export function $access( }); } setScopedDecoratorData(context, $access, accessKey, entity, value.value, scope); -} +}; export function getAccessOverride( context: TCGCContext, @@ -948,13 +976,13 @@ const flattenPropertyKey = createStateSymbol("flattenPropertyKey"); * @param scope Names of the projection (e.g. "python", "csharp", "java", "javascript") * @deprecated This decorator is not recommended to use. */ -export function $flattenProperty( +export const $flattenProperty: FlattenPropertyDecorator = ( context: DecoratorContext, target: ModelProperty, scope?: LanguageScopes -) { +) => { setScopedDecoratorData(context, $flattenProperty, flattenPropertyKey, target, true, scope); // eslint-disable-line deprecation/deprecation -} +}; /** * Whether a model property should be flattened or not. @@ -967,12 +995,12 @@ export function shouldFlattenProperty(context: TCGCContext, target: ModelPropert return getScopedDecoratorData(context, flattenPropertyKey, target) ?? false; } -export function $clientName( +export const $clientName: ClientNameDecorator = ( context: DecoratorContext, entity: Type, value: string, scope?: LanguageScopes -) { +) => { // workaround for current lack of functionality in compiler // https://github.com/microsoft/typespec/issues/2717 if (entity.kind === "Model" || entity.kind === "Operation") { @@ -1001,7 +1029,7 @@ export function $clientName( }); } setScopedDecoratorData(context, $clientName, clientNameKey, entity, value, scope); -} +}; export function getClientNameOverride( context: TCGCContext, diff --git a/packages/typespec-client-generator-core/tsconfig.json b/packages/typespec-client-generator-core/tsconfig.json index b58c2a0526..39afc5d83a 100644 --- a/packages/typespec-client-generator-core/tsconfig.json +++ b/packages/typespec-client-generator-core/tsconfig.json @@ -12,5 +12,5 @@ "rootDir": ".", "tsBuildInfoFile": "temp/tsconfig.tsbuildinfo" }, - "include": ["src/**/*.ts", "test/**/*.ts", "e2e/**/*.ts"] + "include": ["src/**/*.ts", "generated-defs/**/*.ts", "test/**/*.ts", "e2e/**/*.ts"] }