From 0b1afda6a4531f1e0e458e34538958f890129810 Mon Sep 17 00:00:00 2001 From: iscai-msft <43154838+iscai-msft@users.noreply.github.com> Date: Thu, 20 Jun 2024 16:45:03 -0400 Subject: [PATCH] [tcgc] allow csv list of scopes (#1038) fixes #978 --------- Co-authored-by: iscai-msft Co-authored-by: tadelesh --- ..._with_detailed_access-2024-5-18-18-9-31.md | 7 ++ .../src/decorators.ts | 18 +++-- .../test/decorators.test.ts | 77 +++++++++++++++++++ 3 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 .chronus/changes/override_with_detailed_access-2024-5-18-18-9-31.md diff --git a/.chronus/changes/override_with_detailed_access-2024-5-18-18-9-31.md b/.chronus/changes/override_with_detailed_access-2024-5-18-18-9-31.md new file mode 100644 index 0000000000..011fa1140a --- /dev/null +++ b/.chronus/changes/override_with_detailed_access-2024-5-18-18-9-31.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@azure-tools/typespec-client-generator-core" +--- + +add support for list of scopes \ 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 20886a50c5..6f87c42dae 100644 --- a/packages/typespec-client-generator-core/src/decorators.ts +++ b/packages/typespec-client-generator-core/src/decorators.ts @@ -75,25 +75,31 @@ function setScopedDecoratorData( transitivity: boolean = false ): boolean { const targetEntry = context.program.stateMap(key).get(target); + const splitScopes = scope?.split(",").map((s) => s.trim()) || [AllScopes]; + // If target doesn't exist in decorator map, create a new entry if (!targetEntry) { - // value is going to be a list of tuples, each tuple is a value and a list of scopes - context.program.stateMap(key).set(target, { [scope ?? AllScopes]: value }); + const newObject = Object.fromEntries(splitScopes.map((scope) => [scope, value])); + context.program.stateMap(key).set(target, newObject); return true; } // If target exists, but there's a specified scope and it doesn't exist in the target entry, add mapping of scope and value to target entry const scopes = Reflect.ownKeys(targetEntry); - if (!scopes.includes(AllScopes) && scope && !scopes.includes(scope)) { - targetEntry[scope] = value; + if (!scopes.includes(AllScopes) && scope && !splitScopes.some((s) => scopes.includes(s))) { + const newObject = Object.fromEntries(splitScopes.map((scope) => [scope, value])); + context.program.stateMap(key).set(target, { ...targetEntry, ...newObject }); return true; } + // we only want to allow multiple decorators if they each specify a different scope if (!transitivity) { validateDecoratorUniqueOnNode(context, target, decorator); return false; } - if (!Reflect.ownKeys(targetEntry).includes(AllScopes) && !scope) { - context.program.stateMap(key).set(target, { AllScopes: value }); + // for transitivity situation, we could allow scope extension + if (!scopes.includes(AllScopes) && !scope) { + const newObject = Object.fromEntries(splitScopes.map((scope) => [scope, value])); + context.program.stateMap(key).set(target, { ...targetEntry, ...newObject }); } return false; } diff --git a/packages/typespec-client-generator-core/test/decorators.test.ts b/packages/typespec-client-generator-core/test/decorators.test.ts index c9a55d64da..a0e2bbfa18 100644 --- a/packages/typespec-client-generator-core/test/decorators.test.ts +++ b/packages/typespec-client-generator-core/test/decorators.test.ts @@ -1543,6 +1543,7 @@ describe("typespec-client-generator-core: decorators", () => { code: "duplicate-decorator", }); }); + it("duplicate-decorator diagnostic for multiple same scope", async () => { const diagnostics = await runner.diagnose(` @test @@ -1558,6 +1559,82 @@ describe("typespec-client-generator-core: decorators", () => { code: "duplicate-decorator", }); }); + + it("csv scope list", async () => { + function getCodeTemplate(language: string) { + return ` + @test + @access(Access.internal, "${language}") + model Test { + prop: string; + } + `; + } + const pythonRunner = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-python", + }); + const javaRunner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-java" }); + const csharpRunner = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-csharp", + }); + + const testCode = getCodeTemplate("python,csharp"); + const { Test: TestPython } = (await pythonRunner.compile(testCode)) as { Test: Model }; + strictEqual(getAccess(pythonRunner.context, TestPython), "internal"); + + const { Test: TestCSharp } = (await csharpRunner.compile(testCode)) as { Test: Model }; + strictEqual(getAccess(csharpRunner.context, TestCSharp), "internal"); + + const { Test: TestJava } = (await javaRunner.compile(testCode)) as { Test: Model }; + strictEqual(getAccess(javaRunner.context, TestJava), "public"); + }); + + it("csv scope list augment", async () => { + function getCodeTemplate(language: string) { + return ` + @test + model Test { + prop: string; + } + + @@access(Test, Access.public, "java, ts"); + @@access(Test, Access.internal, "${language}"); + `; + } + const pythonRunner = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-python", + }); + const javaRunner = await createSdkTestRunner({ emitterName: "@azure-tools/typespec-java" }); + const csharpRunner = await createSdkTestRunner({ + emitterName: "@azure-tools/typespec-csharp", + }); + + const testCode = getCodeTemplate("python,csharp"); + const { Test: TestPython } = (await pythonRunner.compile(testCode)) as { Test: Model }; + strictEqual(getAccess(pythonRunner.context, TestPython), "internal"); + + const { Test: TestCSharp } = (await csharpRunner.compile(testCode)) as { Test: Model }; + strictEqual(getAccess(csharpRunner.context, TestCSharp), "internal"); + + const { Test: TestJava } = (await javaRunner.compile(testCode)) as { Test: Model }; + strictEqual(getAccess(javaRunner.context, TestJava), "public"); + }); + + it("duplicate-decorator diagnostic for csv scope list", async () => { + const diagnostics = await runner.diagnose(` + @test + @access(Access.internal, "csharp,ts") + @access(Access.internal, "csharp") + op func( + @query("createdAt") + createdAt: utcDateTime; + ): void; + `); + + expectDiagnostics(diagnostics, { + code: "duplicate-decorator", + }); + }); }); describe("@access", () => {