Fix issue with `@removed` decorator if model was not added from beginning (#3409)

Fixes #3210

While the issue was for model properties, it also applies to interfaces,
so tests are included for both.

---------

Co-authored-by: Timothee Guerin <timothee.guerin@outlook.com>
This commit is contained in:
Travis Prescott 2024-05-21 11:07:03 -07:00 коммит произвёл GitHub
Родитель eba81f0255
Коммит a6bcade296
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 81 добавлений и 1 удалений

Просмотреть файл

@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@typespec/versioning"
---
Using `@removed` on member types and `@added` on containing type could result in errors

Просмотреть файл

@ -786,6 +786,7 @@ function validateAvailabilityForContains(
for (const key of keySet) {
const sourceVal = sourceAvail.get(key)!;
const targetVal = targetAvail.get(key)!;
if (sourceVal === targetVal) continue;
if (
[Availability.Added].includes(targetVal) &&
[Availability.Removed, Availability.Unavailable].includes(sourceVal)

Просмотреть файл

@ -15,6 +15,7 @@ import {
Type,
Union,
UnionVariant,
compilerAssert,
getNamespaceFullName,
} from "@typespec/compiler";
import {
@ -806,11 +807,28 @@ export function getAvailabilityMap(
)
return undefined;
let parentMap: Map<string, Availability> | undefined = undefined;
if (type.kind === "ModelProperty" && type.model !== undefined) {
parentMap = getAvailabilityMap(program, type.model);
} else if (type.kind === "Operation" && type.interface !== undefined) {
parentMap = getAvailabilityMap(program, type.interface);
}
// implicitly, all versioned things are assumed to have been added at
// v1 if not specified
if (!added.length) {
if (parentMap !== undefined) {
parentMap.forEach((key, value) => {
if (key === Availability.Added.valueOf()) {
const match = allVersions.find((x) => x.name === value);
compilerAssert(match !== undefined, "Version not found");
added.push(match);
}
});
} else {
added.push(allVersions[0]);
}
}
// something isn't available by default
let isAvail = false;

Просмотреть файл

@ -280,6 +280,34 @@ describe("versioning: logic", () => {
);
});
it("can be removed respecting model versioning", async () => {
const {
source,
projections: [v2, v3, v4],
} = await versionedModel(
["v2", "v3", "v4"],
`@added(Versions.v2)
model Test {
a: int32;
@removed(Versions.v3) b: int32;
}
`
);
assertHasProperties(v2, ["a", "b"]);
assertHasProperties(v3, ["a"]);
assertHasProperties(v4, ["a"]);
assertModelProjectsTo(
[
[v2, "v2"],
[v3, "v3"],
[v3, "v4"],
],
source
);
});
it("can be renamed", async () => {
const {
source,
@ -1365,6 +1393,31 @@ describe("versioning: logic", () => {
);
});
it("can be removed respecting interface versioning", async () => {
const {
source,
projections: [v2, v3, v4],
} = await versionedInterface(
["v2", "v3", "v4"],
`@added(Versions.v2)
interface Test {
allVersions(): void;
@removed(Versions.v3) version2Only(): void;
}
`
);
assertHasOperations(v2, ["allVersions", "version2Only"]);
assertHasOperations(v3, ["allVersions"]);
assertInterfaceProjectsTo(
[
[v2, "v2"],
[v3, "v3"],
[v4, "v4"],
],
source
);
});
it("can be renamed", async () => {
const {
source,