Skip to content

Commit

Permalink
Fix typeChangedFrom of template/union to catch invalid versioning (#5191
Browse files Browse the repository at this point in the history
)

Fixes #4752

---------

Co-authored-by: Christopher Radek <[email protected]>
  • Loading branch information
chrisradek and Christopher Radek authored Dec 2, 2024
1 parent 7cde053 commit ba8c12b
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/versioning"
---

Fixes diagnostics for @typeChangedFrom to properly detect when an incompatible version is referenced inside of a template or union.
12 changes: 12 additions & 0 deletions packages/versioning/src/validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,18 @@ function validateMultiTypeReference(program: Program, source: Type, options?: Ty
const availMap = getAvailabilityMap(program, type);
const availability = availMap?.get(version.name) ?? Availability.Available;
if ([Availability.Added, Availability.Available].includes(availability)) {
// Check if there are any indexed/template arguments that are validated...
if (isTemplateInstance(type)) {
for (const arg of type.templateMapper.args) {
if (isType(arg)) {
validateReference(program, source, arg);
}
}
} else if (type.kind === "Union") {
for (const variant of type.variants.values()) {
validateReference(program, source, variant.type);
}
}
continue;
}
reportDiagnostic(program, {
Expand Down
100 changes: 100 additions & 0 deletions packages/versioning/test/incompatible-versioning.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,106 @@ describe("versioning: validate incompatible references", () => {
});
});

it("emit diagnostic when using @typeChangedFrom with a type parameter that does not yet exist in arrays", async () => {
const diagnostics = await runner.diagnose(`
@test
@added(Versions.v2)
model Original {}
@test
@added(Versions.v2)
model Updated {}
@test
model Test {
@typeChangedFrom(Versions.v2, Original[])
prop: Updated;
}
`);
expectDiagnostics(diagnostics, {
code: "@typespec/versioning/incompatible-versioned-reference",
severity: "error",
message:
"'TestService.Test.prop' was added in version 'v1' but referencing type 'TestService.Original' added in version 'v2'.",
});
});

it("emit diagnostic when using @typeChangedFrom with a type parameter that does not yet exist in records", async () => {
const diagnostics = await runner.diagnose(`
@test
@added(Versions.v2)
model Original {}
@test
@added(Versions.v2)
model Updated {}
@test
model Test {
@typeChangedFrom(Versions.v2, Record<Original>)
prop: Updated;
}
`);
expectDiagnostics(diagnostics, {
code: "@typespec/versioning/incompatible-versioned-reference",
severity: "error",
message:
"'TestService.Test.prop' was added in version 'v1' but referencing type 'TestService.Original' added in version 'v2'.",
});
});

it("emit diagnostic when using @typeChangedFrom with a type parameter that does not yet exist in unions", async () => {
const diagnostics = await runner.diagnose(`
@test
@added(Versions.v2)
model Original {}
@test
@added(Versions.v2)
model Updated {}
@test
model Test {
@typeChangedFrom(Versions.v2, Original | string)
prop: Updated;
}
`);
expectDiagnostics(diagnostics, {
code: "@typespec/versioning/incompatible-versioned-reference",
severity: "error",
message:
"'TestService.Test.prop' was added in version 'v1' but referencing type 'TestService.Original' added in version 'v2'.",
});
});

it("emit diagnostic when using @typeChangedFrom with a type parameter that does not yet exist in template", async () => {
const diagnostics = await runner.diagnose(`
@test
@added(Versions.v2)
model Original {}
@test
@added(Versions.v2)
model Updated {}
model Template<T> {
prop: T;
}
@test
model Test {
@typeChangedFrom(Versions.v2, Template<Original>)
prop: Updated;
}
`);
expectDiagnostics(diagnostics, {
code: "@typespec/versioning/incompatible-versioned-reference",
severity: "error",
message:
"'TestService.Test.prop' was added in version 'v1' but referencing type 'TestService.Original' added in version 'v2'.",
});
});

it("succeed if version are compatible in model", async () => {
const diagnostics = await runner.diagnose(`
@added(Versions.v2)
Expand Down

0 comments on commit ba8c12b

Please sign in to comment.