Skip to content

Commit

Permalink
[tcgc] allow csv list of scopes (#1038)
Browse files Browse the repository at this point in the history
fixes #978

---------

Co-authored-by: iscai-msft <[email protected]>
Co-authored-by: tadelesh <[email protected]>
  • Loading branch information
3 people authored Jun 20, 2024
1 parent eae81f8 commit 0b1afda
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: feature
packages:
- "@azure-tools/typespec-client-generator-core"
---

add support for list of scopes
18 changes: 12 additions & 6 deletions packages/typespec-client-generator-core/src/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
77 changes: 77 additions & 0 deletions packages/typespec-client-generator-core/test/decorators.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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", () => {
Expand Down

0 comments on commit 0b1afda

Please sign in to comment.