diff --git a/packages/versioning/src/versioning.ts b/packages/versioning/src/versioning.ts index a26dab639b..c7ece18b6e 100644 --- a/packages/versioning/src/versioning.ts +++ b/packages/versioning/src/versioning.ts @@ -15,7 +15,6 @@ import { Type, Union, UnionVariant, - compilerAssert, getNamespaceFullName, } from "@typespec/compiler"; import { @@ -782,6 +781,26 @@ export enum Availability { Removed = "Removed", } +function getParentAddedVersion( + program: Program, + type: Type, + versions: Version[] +): Version | undefined { + let parentMap: Map | 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); + } + if (parentMap === undefined) return undefined; + for (const [key, value] of parentMap.entries()) { + if (value === Availability.Added) { + return versions.find((x) => x.name === key); + } + } + return undefined; +} + export function getAvailabilityMap( program: Program, type: Type @@ -792,7 +811,8 @@ export function getAvailabilityMap( // if unversioned then everything exists if (allVersions === undefined) return undefined; - const added = getAddedOnVersions(program, type) ?? []; + const parentAdded = getParentAddedVersion(program, type, allVersions); + let added = getAddedOnVersions(program, type) ?? []; const removed = getRemovedOnVersions(program, type) ?? []; const typeChanged = getTypeChangedFrom(program, type); const returnTypeChanged = getReturnTypeChangedFrom(program, type); @@ -807,27 +827,15 @@ export function getAvailabilityMap( ) return undefined; - let parentMap: Map | 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]); - } + if (!added.length && !parentAdded) { + // no version information on the item or its parent implicitly means it has always been available + added.push(allVersions[0]); + } else if (!added.length && parentAdded) { + // if no version info on type but is on parent, inherit that parent's "added" version + added.push(parentAdded); + } else if (added.length && parentAdded) { + // if "added" info on both the type and parent, combine them + added = [parentAdded, ...added]; } // something isn't available by default diff --git a/packages/versioning/test/versioning.test.ts b/packages/versioning/test/versioning.test.ts index 102055e31a..baeea5adfb 100644 --- a/packages/versioning/test/versioning.test.ts +++ b/packages/versioning/test/versioning.test.ts @@ -289,14 +289,16 @@ describe("versioning: logic", () => { `@added(Versions.v2) model Test { a: int32; - @removed(Versions.v3) b: int32; + @removed(Versions.v3) + @added(Versions.v4) + b: int32; } ` ); assertHasProperties(v2, ["a", "b"]); assertHasProperties(v3, ["a"]); - assertHasProperties(v4, ["a"]); + assertHasProperties(v4, ["a", "b"]); assertModelProjectsTo( [ @@ -1402,12 +1404,15 @@ describe("versioning: logic", () => { `@added(Versions.v2) interface Test { allVersions(): void; - @removed(Versions.v3) version2Only(): void; + @removed(Versions.v3) + @added(Versions.v4) + foo(): void; } ` ); - assertHasOperations(v2, ["allVersions", "version2Only"]); + assertHasOperations(v2, ["allVersions", "foo"]); assertHasOperations(v3, ["allVersions"]); + assertHasOperations(v4, ["allVersions", "foo"]); assertInterfaceProjectsTo( [ [v2, "v2"],