From d16a069202367a9e457c6ede4ca3cfc508db3a03 Mon Sep 17 00:00:00 2001 From: Chenjie Shi Date: Wed, 13 Nov 2024 11:59:39 +0800 Subject: [PATCH] [tcgc] add `disableUsageAccessPropagationToBase` flag and separate exception usage from output usage (#1834) resolve: https://github.com/Azure/typespec-azure/issues/1827 add `disableUsageAccessPropagationToBase` flag: this flag aims to support language that does not generate base model, it will skip the base model's propagation but will not skip the property or subtype of the base model. --- .../add_usage_helper-2024-10-11-17-1-36.md | 7 + .../src/decorators.ts | 3 + .../src/interfaces.ts | 1 + .../src/types.ts | 14 +- .../test/decorators/access.test.ts | 146 ++++++++++++++++++ .../test/decorators/usage.test.ts | 142 +++++++++++++++++ 6 files changed, 311 insertions(+), 2 deletions(-) create mode 100644 .chronus/changes/add_usage_helper-2024-10-11-17-1-36.md diff --git a/.chronus/changes/add_usage_helper-2024-10-11-17-1-36.md b/.chronus/changes/add_usage_helper-2024-10-11-17-1-36.md new file mode 100644 index 0000000000..806e96657e --- /dev/null +++ b/.chronus/changes/add_usage_helper-2024-10-11-17-1-36.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +add `disableUsageAccessPropagationToBase` to support language that does not generate base model \ No newline at end of file diff --git a/packages/typespec-client-generator-core/src/decorators.ts b/packages/typespec-client-generator-core/src/decorators.ts index fbee98ce08..31842b5204 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -615,6 +615,7 @@ export function createTCGCContext(program: Program, emitterName: string): TCGCCo __tspTypeToApiVersions: new Map(), __clientToApiVersionClientDefaultValue: new Map(), previewStringRegex: /-preview$/, + disableUsageAccessPropagationToBase: false, }; } @@ -626,6 +627,7 @@ interface VersioningStrategy { export interface CreateSdkContextOptions { readonly versioning?: VersioningStrategy; additionalDecorators?: string[]; + disableUsageAccessPropagationToBase?: boolean; // this flag is for some languages that has no need to generate base model, but generate model with composition } export async function createSdkContext< @@ -658,6 +660,7 @@ export async function createSdkContext< examplesDir: context.options["examples-dir"] ?? context.options["examples-directory"], decoratorsAllowList: [...defaultDecoratorsAllowList, ...(options?.additionalDecorators ?? [])], previewStringRegex: options?.versioning?.previewStringRegex || tcgcContext.previewStringRegex, + disableUsageAccessPropagationToBase: options?.disableUsageAccessPropagationToBase ?? false, }; sdkContext.sdkPackage = diagnostics.pipe(getSdkPackage(sdkContext)); for (const client of sdkContext.sdkPackage.clients) { diff --git a/packages/typespec-client-generator-core/src/interfaces.ts b/packages/typespec-client-generator-core/src/interfaces.ts index a146aa7786..a233d6d11c 100644 --- a/packages/typespec-client-generator-core/src/interfaces.ts +++ b/packages/typespec-client-generator-core/src/interfaces.ts @@ -53,6 +53,7 @@ export interface TCGCContext { examplesDir?: string; decoratorsAllowList?: string[]; previewStringRegex: RegExp; + disableUsageAccessPropagationToBase: boolean; } export interface SdkContext< diff --git a/packages/typespec-client-generator-core/src/types.ts b/packages/typespec-client-generator-core/src/types.ts index 723b1bcaf4..b7a1e8706c 100644 --- a/packages/typespec-client-generator-core/src/types.ts +++ b/packages/typespec-client-generator-core/src/types.ts @@ -1383,7 +1383,10 @@ function updateUsageOrAccess( if (options?.seenTypes === undefined) { options.seenTypes = new Set(); } - if (options.seenTypes.has(type)) return diagnostics.wrap(undefined); // avoid circular references + if (options.seenTypes.has(type)) { + options.skipFirst = false; + return diagnostics.wrap(undefined); // avoid circular references + } if (type.kind === "array" || type.kind === "dict") { diagnostics.pipe(updateUsageOrAccess(context, value, type.valueType, options)); return diagnostics.wrap(undefined); @@ -1443,10 +1446,12 @@ function updateUsageOrAccess( } } else { options.skipFirst = false; + if (typeof value !== "number") { + type.__accessSet = true; + } } if (type.kind === "enum") return diagnostics.wrap(undefined); - if (!options.propagation) return diagnostics.wrap(undefined); if (type.kind === "union") { for (const unionType of type.variantTypes) { diagnostics.pipe(updateUsageOrAccess(context, value, unionType, options)); @@ -1457,8 +1462,13 @@ function updateUsageOrAccess( diagnostics.pipe(updateUsageOrAccess(context, value, type.type, options)); return diagnostics.wrap(undefined); } + + if (!options.propagation) return diagnostics.wrap(undefined); if (type.baseModel) { options.ignoreSubTypeStack.push(true); + if (context.disableUsageAccessPropagationToBase) { + options.skipFirst = true; + } diagnostics.pipe(updateUsageOrAccess(context, value, type.baseModel, options)); options.ignoreSubTypeStack.pop(); } diff --git a/packages/typespec-client-generator-core/test/decorators/access.test.ts b/packages/typespec-client-generator-core/test/decorators/access.test.ts index f595f9240f..52e833a342 100644 --- a/packages/typespec-client-generator-core/test/decorators/access.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/access.test.ts @@ -730,4 +730,150 @@ describe("typespec-client-generator-core: @access", () => { code: "@azure-tools/typespec-client-generator-core/conflict-access-override", }); }); + + it("disableUsageAccessPropagationToBase true with override", async () => { + runner = await createSdkTestRunner( + { emitterName: "@azure-tools/typespec-python" }, + { disableUsageAccessPropagationToBase: true }, + ); + await runner.compileWithBuiltInService( + ` + model BaseClassThatsPruned { + id: int32; + } + model DerivedOne extends BaseClassThatsPruned { + name: string; + prop: UsedByProperty; + } + model UsedByProperty { + prop: string; + } + @@usage(DerivedOne, Usage.output); + @@access(DerivedOne, Access.public); + `, + ); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 2); + strictEqual(models[0].access, "public"); + strictEqual(models[0].name, "DerivedOne"); + strictEqual(models[1].access, "public"); + strictEqual(models[1].name, "UsedByProperty"); + }); + + it("disableUsageAccessPropagationToBase true", async () => { + runner = await createSdkTestRunner( + { emitterName: "@azure-tools/typespec-python" }, + { disableUsageAccessPropagationToBase: true }, + ); + await runner.compileWithBuiltInService( + ` + model BaseClassThatsPruned { + id: int32; + } + model DerivedOne extends BaseClassThatsPruned { + name: string; + prop: UsedByProperty; + } + model UsedByProperty { + prop: string; + } + + @access(Access.internal) + op test(): DerivedOne; + `, + ); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 2); + strictEqual(models[0].access, "internal"); + strictEqual(models[0].name, "DerivedOne"); + strictEqual(models[1].access, "internal"); + strictEqual(models[1].name, "UsedByProperty"); + }); + + it("disableUsageAccessPropagationToBase true property propagation", async () => { + runner = await createSdkTestRunner( + { emitterName: "@azure-tools/typespec-python" }, + { disableUsageAccessPropagationToBase: true }, + ); + await runner.compileWithBuiltInService( + ` + model BaseClassThatsPruned { + id: int32; + foo: UsedByBaseProperty; + } + model DerivedOne extends BaseClassThatsPruned { + name: string; + prop: UsedByProperty; + } + model UsedByProperty { + prop: string; + } + model UsedByBaseProperty { + prop: string; + } + + @access(Access.internal) + op test(): DerivedOne; + `, + ); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 3); + strictEqual(models[0].access, "internal"); + strictEqual(models[0].name, "DerivedOne"); + strictEqual(models[1].access, "internal"); + strictEqual(models[1].name, "UsedByProperty"); + strictEqual(models[2].access, "internal"); + strictEqual(models[2].name, "UsedByBaseProperty"); + }); + + it("disableUsageAccessPropagationToBase true discriminator propagation", async () => { + runner = await createSdkTestRunner( + { emitterName: "@azure-tools/typespec-python" }, + { disableUsageAccessPropagationToBase: true }, + ); + await runner.compileWithBuiltInService( + ` + @discriminator("kind") + model Fish { + age: int32; + } + + @discriminator("sharktype") + model Shark extends Fish { + kind: "shark"; + origin: Origin; + } + + model Salmon extends Fish { + kind: "salmon"; + } + + model SawShark extends Shark { + sharktype: "saw"; + } + + model Origin { + country: string; + city: string; + manufacture: string; + } + + @get + @access(Access.internal) + op getModel(): Fish; + `, + ); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 5); + strictEqual(models[0].access, "internal"); + strictEqual(models[0].name, "Fish"); + strictEqual(models[1].access, "internal"); + strictEqual(models[1].name, "Shark"); + strictEqual(models[2].access, "internal"); + strictEqual(models[2].name, "Origin"); + strictEqual(models[3].access, "internal"); + strictEqual(models[3].name, "SawShark"); + strictEqual(models[4].access, "internal"); + strictEqual(models[4].name, "Salmon"); + }); }); diff --git a/packages/typespec-client-generator-core/test/decorators/usage.test.ts b/packages/typespec-client-generator-core/test/decorators/usage.test.ts index 79b258b15e..90d27ea220 100644 --- a/packages/typespec-client-generator-core/test/decorators/usage.test.ts +++ b/packages/typespec-client-generator-core/test/decorators/usage.test.ts @@ -459,4 +459,146 @@ describe("typespec-client-generator-core: @usage", () => { strictEqual(models[1].usage, UsageFlags.Output); strictEqual(models[1].access, "public"); }); + + it("disableUsageAccessPropagationToBase true with override", async () => { + runner = await createSdkTestRunner( + { emitterName: "@azure-tools/typespec-python" }, + { disableUsageAccessPropagationToBase: true }, + ); + await runner.compileWithBuiltInService( + ` + model BaseClassThatsPruned { + id: int32; + } + model DerivedOne extends BaseClassThatsPruned { + name: string; + prop: UsedByProperty; + } + model UsedByProperty { + prop: string; + } + @@usage(DerivedOne, Usage.output); + `, + ); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 2); + strictEqual(models[0].usage, UsageFlags.Output); + strictEqual(models[0].name, "DerivedOne"); + strictEqual(models[1].usage, UsageFlags.Output); + strictEqual(models[1].name, "UsedByProperty"); + }); + + it("disableUsageAccessPropagationToBase true", async () => { + runner = await createSdkTestRunner( + { emitterName: "@azure-tools/typespec-python" }, + { disableUsageAccessPropagationToBase: true }, + ); + await runner.compileWithBuiltInService( + ` + model BaseClassThatsPruned { + id: int32; + } + model DerivedOne extends BaseClassThatsPruned { + name: string; + prop: UsedByProperty; + } + model UsedByProperty { + prop: string; + } + + op test(): DerivedOne; + `, + ); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 2); + strictEqual(models[0].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[0].name, "DerivedOne"); + strictEqual(models[1].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[1].name, "UsedByProperty"); + }); + + it("disableUsageAccessPropagationToBase true property propagation", async () => { + runner = await createSdkTestRunner( + { emitterName: "@azure-tools/typespec-python" }, + { disableUsageAccessPropagationToBase: true }, + ); + await runner.compileWithBuiltInService( + ` + model BaseClassThatsPruned { + id: int32; + foo: UsedByBaseProperty; + } + model DerivedOne extends BaseClassThatsPruned { + name: string; + prop: UsedByProperty; + } + model UsedByProperty { + prop: string; + } + model UsedByBaseProperty { + prop: string; + } + + op test(): DerivedOne; + `, + ); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 3); + strictEqual(models[0].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[0].name, "DerivedOne"); + strictEqual(models[1].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[1].name, "UsedByProperty"); + strictEqual(models[2].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[2].name, "UsedByBaseProperty"); + }); + + it("disableUsageAccessPropagationToBase true discriminator propagation", async () => { + runner = await createSdkTestRunner( + { emitterName: "@azure-tools/typespec-python" }, + { disableUsageAccessPropagationToBase: true }, + ); + await runner.compileWithBuiltInService( + ` + @discriminator("kind") + model Fish { + age: int32; + } + + @discriminator("sharktype") + model Shark extends Fish { + kind: "shark"; + origin: Origin; + } + + model Salmon extends Fish { + kind: "salmon"; + } + + model SawShark extends Shark { + sharktype: "saw"; + } + + model Origin { + country: string; + city: string; + manufacture: string; + } + + @get + op getModel(): Fish; + `, + ); + const models = runner.context.sdkPackage.models; + strictEqual(models.length, 5); + strictEqual(models[0].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[0].name, "Fish"); + strictEqual(models[1].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[1].name, "Shark"); + strictEqual(models[2].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[2].name, "Origin"); + strictEqual(models[3].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[3].name, "SawShark"); + strictEqual(models[4].usage, UsageFlags.Output | UsageFlags.Json); + strictEqual(models[4].name, "Salmon"); + }); });