Versioning library is a big mess of things all into one file, this makes
it hard to contribute. Did this originally because I was going to deal
with multiple madeRequired and madeOptional over time but that will be
done later but finished the cleanup anyway.

Split the versioning.ts into multiple separate logical files:
- `decorators.ts`: Contains all the versioning decorators and accessor
- `internal-projection-functions.ts`: Contains implementation of the
helper function used inside the versioning projection(not meant for
external use)
- `projection.ts`: Contains the projection building functions
- `versioning.ts`: Contains the various versioning computation
function(timeline, etc.)


This makes it clearer of what is supposed to be public apis vs internal,
waht is just mean for decorator, projection, etc.
This commit is contained in:
Timothee Guerin 2024-05-23 15:43:36 -07:00 коммит произвёл GitHub
Родитель 5698be15fe
Коммит 0a70aa7f61
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
23 изменённых файлов: 972 добавлений и 910 удалений

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

@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: internal
packages:
- "@typespec/versioning"
---
Organize versioning library

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

@ -1,224 +1,192 @@
import "../dist/src/versioning.js";
import "../dist/src/decorators.js";
using TypeSpec.Reflection;
namespace TypeSpec {
namespace Versioning {
/**
* Identifies that the decorated namespace is versioned by the provided enum.
* @param versions The enum that describes the supported versions.
*
* @example
*
* ```tsp
* @versioned(Versions)
* namespace MyService;
* enum Versions {
* v1,
* v2,
* v3,
* }
* ```
*/
extern dec versioned(target: Namespace, versions: Enum);
namespace TypeSpec.Versioning;
/**
* Identifies that a namespace or a given versioning enum member relies upon a versioned package.
* @param versionRecords The dependent library version(s) for the target namespace or version.
*
* @example Select a single version of `MyLib` to use
*
* ```tsp
* @useDependency(MyLib.Versions.v1_1)
* namespace NonVersionedService;
* ```
*
* @example Select which version of the library match to which version of the service.
*
* ```tsp
* @versioned(Versions)
* namespace MyService1;
* enum Version {
* @useDependency(MyLib.Versions.v1_1) // V1 use lib v1_1
* v1,
* @useDependency(MyLib.Versions.v1_1) // V2 use lib v1_1
* v2,
* @useDependency(MyLib.Versions.v2) // V3 use lib v2
* v3,
* }
* ```
*/
extern dec useDependency(target: EnumMember | Namespace, ...versionRecords: EnumMember[]);
/**
* Identifies that the decorated namespace is versioned by the provided enum.
* @param versions The enum that describes the supported versions.
*
* @example
*
* ```tsp
* @versioned(Versions)
* namespace MyService;
* enum Versions {
* v1,
* v2,
* v3,
* }
* ```
*/
extern dec versioned(target: Namespace, versions: Enum);
/**
* Identifies when the target was added.
* @param version The version that the target was added in.
*
* @example
*
* ```tsp
* @added(Versions.v2)
* op addedInV2(): void;
*
* @added(Versions.v2)
* model AlsoAddedInV2 {}
*
* model Foo {
* name: string;
*
* @added(Versions.v3)
* addedInV3: string;
* }
* ```
*/
extern dec added(
target:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
version: EnumMember
);
/**
* Identifies that a namespace or a given versioning enum member relies upon a versioned package.
* @param versionRecords The dependent library version(s) for the target namespace or version.
*
* @example Select a single version of `MyLib` to use
*
* ```tsp
* @useDependency(MyLib.Versions.v1_1)
* namespace NonVersionedService;
* ```
*
* @example Select which version of the library match to which version of the service.
*
* ```tsp
* @versioned(Versions)
* namespace MyService1;
* enum Version {
* @useDependency(MyLib.Versions.v1_1) // V1 use lib v1_1
* v1,
* @useDependency(MyLib.Versions.v1_1) // V2 use lib v1_1
* v2,
* @useDependency(MyLib.Versions.v2) // V3 use lib v2
* v3,
* }
* ```
*/
extern dec useDependency(target: EnumMember | Namespace, ...versionRecords: EnumMember[]);
/**
* Identifies when the target was removed.
* @param version The version that the target was removed in.
*
* @example
* ```tsp
* @removed(Versions.v2)
* op removedInV2(): void;
*
* @removed(Versions.v2)
* model AlsoRemovedInV2 {}
*
* model Foo {
* name: string;
*
* @removed(Versions.v3)
* removedInV3: string;
* }
* ```
*/
extern dec removed(
target:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
version: EnumMember
);
/**
* Identifies when the target was added.
* @param version The version that the target was added in.
*
* @example
*
* ```tsp
* @added(Versions.v2)
* op addedInV2(): void;
*
* @added(Versions.v2)
* model AlsoAddedInV2 {}
*
* model Foo {
* name: string;
*
* @added(Versions.v3)
* addedInV3: string;
* }
* ```
*/
extern dec added(
target:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
version: EnumMember
);
/**
* Identifies when the target has been renamed.
* @param version The version that the target was renamed in.
* @param oldName The previous name of the target.
*
* @example
* ```tsp
* @renamedFrom(Versions.v2, "oldName")
* op newName(): void;
* ```
*/
extern dec renamedFrom(
target:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
version: EnumMember,
oldName: valueof string
);
/**
* Identifies when the target was removed.
* @param version The version that the target was removed in.
*
* @example
* ```tsp
* @removed(Versions.v2)
* op removedInV2(): void;
*
* @removed(Versions.v2)
* model AlsoRemovedInV2 {}
*
* model Foo {
* name: string;
*
* @removed(Versions.v3)
* removedInV3: string;
* }
* ```
*/
extern dec removed(
target:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
version: EnumMember
);
/**
* Identifies when a target was made optional.
* @param version The version that the target was made optional in.
*
* @example
*
* ```tsp
* model Foo {
* name: string;
* @madeOptional(Versions.v2)
* nickname: string;
* }
* ```
*/
extern dec madeOptional(target: ModelProperty, version: EnumMember);
/**
* Identifies when the target has been renamed.
* @param version The version that the target was renamed in.
* @param oldName The previous name of the target.
*
* @example
* ```tsp
* @renamedFrom(Versions.v2, "oldName")
* op newName(): void;
* ```
*/
extern dec renamedFrom(
target:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
version: EnumMember,
oldName: valueof string
);
/**
* Identifies when a target was made required.
* @param version The version that the target was made required in.
*
* @example
*
* ```tsp
* model Foo {
* name: string;
* @madeRequired(Versions.v2)
* nickname: string;
* }
* ```
*/
extern dec madeRequired(target: ModelProperty, version: EnumMember);
/**
* Identifies when a target was made optional.
* @param version The version that the target was made optional in.
*
* @example
*
* ```tsp
* model Foo {
* name: string;
* @madeOptional(Versions.v2)
* nickname: string;
* }
* ```
*/
extern dec madeOptional(target: ModelProperty, version: EnumMember);
/**
* Identifies when the target type changed.
* @param version The version that the target type changed in.
* @param oldType The previous type of the target.
*/
extern dec typeChangedFrom(target: ModelProperty, version: EnumMember, oldType: unknown);
/**
* Identifies when a target was made required.
* @param version The version that the target was made required in.
*
* @example
*
* ```tsp
* model Foo {
* name: string;
* @madeRequired(Versions.v2)
* nickname: string;
* }
* ```
*/
extern dec madeRequired(target: ModelProperty, version: EnumMember);
/**
* Identifies when the target type changed.
* @param version The version that the target type changed in.
* @param oldType The previous type of the target.
*/
extern dec returnTypeChangedFrom(target: Operation, version: EnumMember, oldType: unknown);
/**
* Identifies when the target type changed.
* @param version The version that the target type changed in.
* @param oldType The previous type of the target.
*/
extern dec typeChangedFrom(target: ModelProperty, version: EnumMember, oldType: unknown);
/**
* Returns whether the target exists for the given version.
* @param version The version to check.
*/
extern fn existsAtVersion(target: unknown, version: EnumMember): boolean;
/**
* Returns whether the target has a different name for the given version.
* @param version The version to check.
*/
extern fn hasDifferentNameAtVersion(target: unknown, version: EnumMember): boolean;
/**
* Returns whether the target was made optional after the given version.
* @param version The version to check.
*/
extern fn madeOptionalAfter(target: unknown, version: EnumMember): boolean;
/**
* Returns whether the target was made required after the given version.
* @param version The version to check.
*/
extern fn madeRequiredAfter(target: unknown, version: EnumMember): boolean;
/**
* Returns whether the version exists for the provided enum member.
* @param version The version to check.
*/
extern fn getVersionForEnumMember(target: unknown, version: EnumMember): boolean;
}
}
/**
* Identifies when the target type changed.
* @param version The version that the target type changed in.
* @param oldType The previous type of the target.
*/
extern dec returnTypeChangedFrom(target: Operation, version: EnumMember, oldType: unknown);

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

@ -0,0 +1,3 @@
import "./decorators.tsp";
import "./projection.tsp";
import "../dist/src/validate.js";

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

@ -1,7 +1,39 @@
import "./decorators.tsp";
import "../dist/src/validate.js";
import "../dist/src/internal-projection-functions.js";
using TypeSpec.Versioning;
using TypeSpec.Reflection;
namespace TypeSpec.Versioning;
/**
* Returns whether the target exists for the given version.
* @param version The version to check.
*/
extern fn existsAtVersion(target: unknown, version: EnumMember): boolean;
/**
* Returns whether the target has a different name for the given version.
* @param version The version to check.
*/
extern fn hasDifferentNameAtVersion(target: unknown, version: EnumMember): boolean;
/**
* Returns whether the target has a different return type for the given version.
* @param version The version to check.
*/
extern fn hasDifferentReturnTypeAtVersion(target: unknown, version: EnumMember): boolean;
/**
* Returns whether the target was made optional after the given version.
* @param version The version to check.
*/
extern fn madeOptionalAfter(target: unknown, version: EnumMember): boolean;
/**
* Returns whether the target was made required after the given version.
* @param version The version to check.
*/
extern fn madeRequiredAfter(target: unknown, version: EnumMember): boolean;
#suppress "projections-are-experimental"
projection op#v {

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

@ -18,7 +18,7 @@
],
"type": "module",
"main": "dist/src/index.js",
"tspMain": "lib/versioning.tsp",
"tspMain": "lib/main.tsp",
"exports": {
".": {
"types": "./dist/src/index.d.ts",

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

@ -0,0 +1,463 @@
import type {
DecoratorContext,
DiagnosticTarget,
Enum,
EnumMember,
Interface,
Model,
ModelProperty,
Namespace,
Operation,
Program,
Scalar,
Type,
Union,
UnionVariant,
} from "@typespec/compiler";
import type {
AddedDecorator,
MadeOptionalDecorator,
MadeRequiredDecorator,
RenamedFromDecorator,
ReturnTypeChangedFromDecorator,
TypeChangedFromDecorator,
VersionedDecorator,
} from "../generated-defs/TypeSpec.Versioning.js";
import { VersioningStateKeys, reportDiagnostic } from "./lib.js";
import type { Version } from "./types.js";
import { getVersionForEnumMember } from "./versioning.js";
export const namespace = "TypeSpec.Versioning";
function checkIsVersion(
program: Program,
enumMember: EnumMember,
diagnosticTarget: DiagnosticTarget
): Version | undefined {
const version = getVersionForEnumMember(program, enumMember);
if (!version) {
reportDiagnostic(program, {
code: "version-not-found",
target: diagnosticTarget,
format: { version: enumMember.name, enumName: enumMember.enum.name },
});
}
return version;
}
export const $added: AddedDecorator = (
context: DecoratorContext,
t:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
v: EnumMember
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
// retrieve statemap to update or create a new one
const record =
program.stateMap(VersioningStateKeys.addedOn).get(t as Type) ?? new Array<Version>();
record.push(version);
// ensure that records are stored in ascending order
(record as Version[]).sort((a, b) => a.index - b.index);
program.stateMap(VersioningStateKeys.addedOn).set(t as Type, record);
};
export function $removed(
context: DecoratorContext,
t:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
v: EnumMember
) {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
// retrieve statemap to update or create a new one
const record =
program.stateMap(VersioningStateKeys.removedOn).get(t as Type) ?? new Array<Version>();
record.push(version);
// ensure that records are stored in ascending order
(record as Version[]).sort((a, b) => a.index - b.index);
program.stateMap(VersioningStateKeys.removedOn).set(t as Type, record);
}
/**
* Returns the mapping of versions to old type values, if applicable
* @param p TypeSpec program
* @param t type to query
* @returns Map of versions to old types, if any
*/
export function getTypeChangedFrom(p: Program, t: Type): Map<Version, Type> | undefined {
return p.stateMap(VersioningStateKeys.typeChangedFrom).get(t) as Map<Version, Type>;
}
export const $typeChangedFrom: TypeChangedFromDecorator = (
context: DecoratorContext,
prop: ModelProperty,
v: EnumMember,
oldType: any
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
// retrieve statemap to update or create a new one
let record = getTypeChangedFrom(program, prop) ?? new Map<Version, any>();
record.set(version, oldType);
// ensure the map is sorted by version
record = new Map([...record.entries()].sort((a, b) => a[0].index - b[0].index));
program.stateMap(VersioningStateKeys.typeChangedFrom).set(prop, record);
};
/**
* Returns the mapping of versions to old return type values, if applicable
* @param p TypeSpec program
* @param t type to query
* @returns Map of versions to old types, if any
*/
export function getReturnTypeChangedFrom(p: Program, t: Type): Map<Version, Type> | undefined {
return p.stateMap(VersioningStateKeys.returnTypeChangedFrom).get(t) as Map<Version, Type>;
}
export const $returnTypeChangedFrom: ReturnTypeChangedFromDecorator = (
context: DecoratorContext,
op: Operation,
v: EnumMember,
oldReturnType: Type
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
// retrieve statemap to update or create a new one
let record = getReturnTypeChangedFrom(program, op) ?? new Map<Version, any>();
record.set(version, oldReturnType);
// ensure the map is sorted by version
record = new Map([...record.entries()].sort((a, b) => a[0].index - b[0].index));
program.stateMap(VersioningStateKeys.returnTypeChangedFrom).set(op, record);
};
interface RenamedFrom {
version: Version;
oldName: string;
}
export const $renamedFrom: RenamedFromDecorator = (
context: DecoratorContext,
t:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
v: EnumMember,
oldName: string
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
if (oldName === "") {
reportDiagnostic(program, {
code: "invalid-renamed-from-value",
target: t as Type,
});
}
// retrieve statemap to update or create a new one
const record = getRenamedFrom(program, t as Type) ?? [];
record.push({ version: version, oldName: oldName });
// ensure that records are stored in ascending order
record.sort((a, b) => a.version.index - b.version.index);
program.stateMap(VersioningStateKeys.renamedFrom).set(t as Type, record);
};
export const $madeOptional: MadeOptionalDecorator = (
context: DecoratorContext,
t: ModelProperty,
v: EnumMember
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
program.stateMap(VersioningStateKeys.madeOptional).set(t, version);
};
export const $madeRequired: MadeRequiredDecorator = (
context: DecoratorContext,
t: ModelProperty,
v: EnumMember
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
program.stateMap(VersioningStateKeys.madeRequired).set(t, version);
};
/**
* @returns version when the given type was made required if applicable.
*/
export function getMadeRequiredOn(p: Program, t: Type): Version | undefined {
return p.stateMap(VersioningStateKeys.madeRequired).get(t);
}
/**
* @returns the array of RenamedFrom metadata if applicable.
*/
export function getRenamedFrom(p: Program, t: Type): Array<RenamedFrom> | undefined {
return p.stateMap(VersioningStateKeys.renamedFrom).get(t) as Array<RenamedFrom>;
}
/**
* @returns the list of versions for which this decorator has been applied
*/
export function getRenamedFromVersions(p: Program, t: Type): Version[] | undefined {
return getRenamedFrom(p, t)?.map((x) => x.version);
}
export function getAddedOnVersions(p: Program, t: Type): Version[] | undefined {
return p.stateMap(VersioningStateKeys.addedOn).get(t) as Version[];
}
export function getRemovedOnVersions(p: Program, t: Type): Version[] | undefined {
return p.stateMap(VersioningStateKeys.removedOn).get(t) as Version[];
}
/**
* @returns version when the given type was made optional if applicable.
*/
export function getMadeOptionalOn(p: Program, t: Type): Version | undefined {
return p.stateMap(VersioningStateKeys.madeOptional).get(t);
}
export class VersionMap {
private map = new Map<EnumMember, Version>();
constructor(namespace: Namespace, enumType: Enum) {
let index = 0;
for (const member of enumType.members.values()) {
this.map.set(member, {
name: member.name,
value: member.value?.toString() ?? member.name,
enumMember: member,
index,
namespace,
});
index++;
}
}
public getVersionForEnumMember(member: EnumMember): Version | undefined {
return this.map.get(member);
}
public getVersions(): Version[] {
return [...this.map.values()];
}
public get size(): number {
return this.map.size;
}
}
export const $versioned: VersionedDecorator = (
context: DecoratorContext,
t: Namespace,
versions: Enum
) => {
context.program.stateMap(VersioningStateKeys.versions).set(t, new VersionMap(t, versions));
};
/**
* Get the version map of the namespace.
*/
export function getVersion(program: Program, namespace: Namespace): VersionMap | undefined {
return program.stateMap(VersioningStateKeys.versions).get(namespace);
}
export function findVersionedNamespace(
program: Program,
namespace: Namespace
): Namespace | undefined {
let current: Namespace | undefined = namespace;
while (current) {
if (program.stateMap(VersioningStateKeys.versions).has(current)) {
return current;
}
current = current.namespace;
}
return undefined;
}
export function $useDependency(
context: DecoratorContext,
target: EnumMember | Namespace,
...versionRecords: EnumMember[]
) {
const versions: Version[] = [];
// ensure only valid versions are passed in
for (const record of versionRecords) {
const ver = checkIsVersion(context.program, record, context.getArgumentTarget(0)!);
if (ver) {
versions.push(ver);
}
}
if (target.kind === "Namespace") {
let state = getNamespaceUseDependencyState(context.program, target);
if (!state) {
state = versions;
} else {
state.push(...versions);
}
context.program.stateMap(VersioningStateKeys.useDependencyNamespace).set(target, state);
} else if (target.kind === "EnumMember") {
const targetEnum = target.enum;
let state = context.program
.stateMap(VersioningStateKeys.useDependencyEnum)
.get(targetEnum) as Map<EnumMember, Version[]>;
if (!state) {
state = new Map<EnumMember, Version[]>();
}
// get any existing versions and combine them
const currentVersions = state.get(target) ?? [];
currentVersions.push(...versions);
state.set(target, currentVersions);
context.program.stateMap(VersioningStateKeys.useDependencyEnum).set(targetEnum, state);
}
}
function getNamespaceUseDependencyState(
program: Program,
target: Namespace
): Version[] | undefined {
return program.stateMap(VersioningStateKeys.useDependencyNamespace).get(target);
}
export function getUseDependencies(
program: Program,
target: Namespace | Enum,
searchEnum: boolean = true
): Map<Namespace, Map<Version, Version> | Version> | undefined {
const result = new Map<Namespace, Map<Version, Version> | Version>();
if (target.kind === "Namespace") {
let current: Namespace | undefined = target;
while (current) {
const data = getNamespaceUseDependencyState(program, current);
if (!data) {
// See if the namspace has a version enum
if (searchEnum) {
const versions = getVersion(program, current)?.getVersions();
if (versions?.length) {
const enumDeps = getUseDependencies(program, versions[0].enumMember.enum);
if (enumDeps) {
return enumDeps;
}
}
}
current = current.namespace;
} else {
for (const v of data) {
result.set(v.namespace, v);
}
return result;
}
}
return undefined;
} else if (target.kind === "Enum") {
const data = program.stateMap(VersioningStateKeys.useDependencyEnum).get(target) as Map<
EnumMember,
Version[]
>;
if (!data) {
return undefined;
}
const resolved = resolveVersionDependency(program, data);
if (resolved instanceof Map) {
for (const [enumVer, value] of resolved) {
for (const val of value) {
const targetNamespace = val.enumMember.enum.namespace;
if (!targetNamespace) {
reportDiagnostic(program, {
code: "version-not-found",
target: val.enumMember.enum,
format: { version: val.enumMember.name, enumName: val.enumMember.enum.name },
});
return undefined;
}
let subMap = result.get(targetNamespace) as Map<Version, Version>;
if (subMap) {
subMap.set(enumVer, val);
} else {
subMap = new Map([[enumVer, val]]);
}
result.set(targetNamespace, subMap);
}
}
}
}
return result;
}
function resolveVersionDependency(
program: Program,
data: Map<EnumMember, Version[]> | Version[]
): Map<Version, Version[]> | Version[] {
if (!(data instanceof Map)) {
return data;
}
const mapping = new Map<Version, Version[]>();
for (const [key, value] of data) {
const sourceVersion = getVersionForEnumMember(program, key);
if (sourceVersion !== undefined) {
mapping.set(sourceVersion, value);
}
}
return mapping;
}

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

@ -1,3 +1,26 @@
export {
$added,
$madeOptional,
$madeRequired,
$removed,
$renamedFrom,
$returnTypeChangedFrom,
$typeChangedFrom,
$useDependency,
$versioned,
VersionMap,
findVersionedNamespace,
getAddedOnVersions,
getMadeOptionalOn,
getRemovedOnVersions,
getRenamedFrom,
getRenamedFromVersions,
getReturnTypeChangedFrom,
getTypeChangedFrom,
getUseDependencies,
getVersion,
} from "./decorators.js";
export { buildVersionProjections, type VersionProjections } from "./projection.js";
export * from "./types.js";
export * from "./validate.js";
export * from "./versioning.js";

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

@ -0,0 +1,125 @@
import type { ObjectType, Program, Type } from "@typespec/compiler";
import {
getMadeOptionalOn,
getMadeRequiredOn,
getRenamedFrom,
getReturnTypeChangedFrom,
getTypeChangedFrom,
} from "./decorators.js";
import { VersioningStateKeys } from "./lib.js";
import { TimelineMoment, VersioningTimeline } from "./versioning-timeline.js";
import { Availability, getAvailabilityMapInTimeline } from "./versioning.js";
export const namespace = "TypeSpec.Versioning";
function getVersioningState(
program: Program,
versionKey: ObjectType
): {
timeline: VersioningTimeline;
projectingMoment: TimelineMoment;
} {
return program.stateMap(VersioningStateKeys.versionIndex).get(versionKey);
}
/**
* @returns get old name if applicable.
*/
export function getNameAtVersion(p: Program, t: Type, versionKey: ObjectType): string {
const versioningState = getVersioningState(p, versionKey);
const allValues = getRenamedFrom(p, t);
if (!allValues) return "";
for (const val of allValues) {
if (versioningState.timeline.isBefore(versioningState.projectingMoment, val.version)) {
return val.oldName;
}
}
return "";
}
/**
* @returns get old type if applicable.
*/
export function getTypeBeforeVersion(
p: Program,
t: Type,
versionKey: ObjectType
): Type | undefined {
const versioningState = getVersioningState(p, versionKey);
const map = getTypeChangedFrom(p, t);
if (!map) return undefined;
for (const [changedAtVersion, oldType] of map) {
if (versioningState.timeline.isBefore(versioningState.projectingMoment, changedAtVersion)) {
return oldType;
}
}
return undefined;
}
/**
* @returns get old type if applicable.
*/
export function getReturnTypeBeforeVersion(p: Program, t: Type, versionKey: ObjectType): any {
const versioningState = getVersioningState(p, versionKey);
const map = getReturnTypeChangedFrom(p, t);
if (!map) return "";
for (const [changedAtVersion, val] of map) {
if (versioningState.timeline.isBefore(versioningState.projectingMoment, changedAtVersion)) {
return val;
}
}
return "";
}
export function madeOptionalAfter(program: Program, type: Type, versionKey: ObjectType): boolean {
const versioningState = getVersioningState(program, versionKey);
const madeOptionalAtVersion = getMadeOptionalOn(program, type);
if (madeOptionalAtVersion === undefined) {
return false;
}
return versioningState.timeline.isBefore(versioningState.projectingMoment, madeOptionalAtVersion);
}
export function madeRequiredAfter(program: Program, type: Type, versionKey: ObjectType): boolean {
const versioningState = getVersioningState(program, versionKey);
const madeRequiredAtVersion = getMadeRequiredOn(program, type);
if (madeRequiredAtVersion === undefined) {
return false;
}
return versioningState.timeline.isBefore(versioningState.projectingMoment, madeRequiredAtVersion);
}
export function existsAtVersion(p: Program, type: Type, versionKey: ObjectType): boolean {
const versioningState = getVersioningState(p, versionKey);
// if unversioned then everything exists
const availability = getAvailabilityMapInTimeline(p, type, versioningState.timeline);
if (!availability) return true;
const isAvail = availability.get(versioningState.projectingMoment)!;
return isAvail === Availability.Added || isAvail === Availability.Available;
}
export function hasDifferentNameAtVersion(p: Program, type: Type, version: ObjectType): boolean {
return getNameAtVersion(p, type, version) !== "";
}
export function hasDifferentTypeAtVersion(p: Program, type: Type, version: ObjectType): boolean {
return getTypeBeforeVersion(p, type, version) !== undefined;
}
export function hasDifferentReturnTypeAtVersion(
p: Program,
type: Type,
version: ObjectType
): boolean {
return getReturnTypeBeforeVersion(p, type, version) !== "";
}

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

@ -1,6 +1,10 @@
import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
const libDef = {
export const {
reportDiagnostic,
createStateSymbol,
stateKeys: VersioningStateKeys,
} = createTypeSpecLibrary({
name: "@typespec/versioning",
diagnostics: {
"versioned-dependency-tuple": {
@ -102,5 +106,18 @@ const libDef = {
},
},
},
} as const;
export const { reportDiagnostic, createStateSymbol } = createTypeSpecLibrary(libDef);
state: {
versionIndex: { description: "Version index" },
addedOn: { description: "State for @addedOn decorator" },
removedOn: { description: "State for @removedOn decorator" },
versions: { description: "State for @versioned decorator" },
useDependencyNamespace: { description: "State for @useDependency decorator on Namespaces" },
useDependencyEnum: { description: "State for @useDependency decorator on Enums" },
renamedFrom: { description: "State for @renamedFrom decorator" },
madeOptional: { description: "State for @madeOptional decorator" },
madeRequired: { description: "State for @madeRequired decorator" },
typeChangedFrom: { description: "State for @typeChangedFrom decorator" },
returnTypeChangedFrom: { description: "State for @returnTypeChangedFrom decorator" },
},
});

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

@ -0,0 +1,58 @@
import type { Namespace, ObjectType, Program, ProjectionApplication } from "@typespec/compiler";
import { VersioningStateKeys } from "./lib.js";
import { TimelineMoment, VersioningTimeline } from "./versioning-timeline.js";
import { resolveVersions } from "./versioning.js";
/**
* Represent the set of projections used to project to that version.
*/
export interface VersionProjections {
version: string | undefined;
projections: ProjectionApplication[];
}
/**
* @internal
*/
export function indexTimeline(
program: Program,
timeline: VersioningTimeline,
projectingMoment: TimelineMoment
) {
const versionKey = program.checker.createType<ObjectType>({
kind: "Object",
properties: {},
} as any);
program
.stateMap(VersioningStateKeys.versionIndex)
.set(versionKey, { timeline, projectingMoment });
return versionKey;
}
export function buildVersionProjections(program: Program, rootNs: Namespace): VersionProjections[] {
const resolutions = resolveVersions(program, rootNs);
const timeline = new VersioningTimeline(
program,
resolutions.map((x) => x.versions)
);
return resolutions.map((resolution) => {
if (resolution.versions.size === 0) {
return { version: undefined, projections: [] };
} else {
const versionKey = indexTimeline(
program,
timeline,
timeline.get(resolution.versions.values().next().value)
);
return {
version: resolution.rootVersion?.value,
projections: [
{
projectionName: "v",
arguments: [versionKey],
},
],
};
}
});
}

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

@ -1,7 +1,7 @@
import {
createTestLibrary,
findTestPackageRoot,
TypeSpecTestLibrary,
type TypeSpecTestLibrary,
} from "@typespec/compiler/testing";
export const VersioningTestLibrary: TypeSpecTestLibrary = createTestLibrary({

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

@ -1,4 +1,4 @@
import { EnumMember, Namespace } from "@typespec/compiler";
import type { EnumMember, Namespace } from "@typespec/compiler";
export interface Version {
name: string;

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

@ -1,22 +1,18 @@
import {
NoTarget,
getNamespaceFullName,
getService,
getTypeName,
isTemplateInstance,
isType,
Namespace,
navigateProgram,
NoTarget,
Program,
Type,
TypeNameOptions,
type Namespace,
type Program,
type Type,
type TypeNameOptions,
} from "@typespec/compiler";
import { reportDiagnostic } from "./lib.js";
import { Version } from "./types.js";
import {
Availability,
findVersionedNamespace,
getAvailabilityMap,
getMadeOptionalOn,
getMadeRequiredOn,
getRenamedFrom,
@ -24,6 +20,12 @@ import {
getTypeChangedFrom,
getUseDependencies,
getVersion,
} from "./decorators.js";
import { reportDiagnostic } from "./lib.js";
import type { Version } from "./types.js";
import {
Availability,
getAvailabilityMap,
getVersionDependencies,
getVersions,
} from "./versioning.js";

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

@ -1,5 +1,5 @@
import { compilerAssert, getTypeName, Namespace, Program } from "@typespec/compiler";
import { Version } from "./types.js";
import { compilerAssert, getTypeName, type Namespace, type Program } from "@typespec/compiler";
import type { Version } from "./types.js";
import { getVersions } from "./versioning.js";
/**

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

@ -1,522 +1,17 @@
import type { Enum, EnumMember, Namespace, Program, Type } from "@typespec/compiler";
import { compilerAssert, getNamespaceFullName } from "@typespec/compiler";
import {
DecoratorContext,
DiagnosticTarget,
Enum,
EnumMember,
Interface,
Model,
ModelProperty,
Namespace,
ObjectType,
Operation,
Program,
ProjectionApplication,
Scalar,
Type,
Union,
UnionVariant,
compilerAssert,
getNamespaceFullName,
} from "@typespec/compiler";
import {
AddedDecorator,
MadeOptionalDecorator,
MadeRequiredDecorator,
RenamedFromDecorator,
ReturnTypeChangedFromDecorator,
TypeChangedFromDecorator,
VersionedDecorator,
} from "../generated-defs/TypeSpec.Versioning.js";
import { createStateSymbol, reportDiagnostic } from "./lib.js";
import { Version, VersionResolution } from "./types.js";
getAddedOnVersions,
getRemovedOnVersions,
getReturnTypeChangedFrom,
getTypeChangedFrom,
getUseDependencies,
getVersion,
type VersionMap,
} from "./decorators.js";
import type { Version, VersionResolution } from "./types.js";
import { TimelineMoment, VersioningTimeline } from "./versioning-timeline.js";
const addedOnKey = createStateSymbol("addedOn");
const removedOnKey = createStateSymbol("removedOn");
const versionsKey = createStateSymbol("versions");
const versionDependencyKey = createStateSymbol("versionDependency");
const useDependencyNamespaceKey = createStateSymbol("useDependencyNamespace");
const useDependencyEnumKey = createStateSymbol("useDependencyEnum");
const renamedFromKey = createStateSymbol("renamedFrom");
const madeOptionalKey = createStateSymbol("madeOptional");
const madeRequiredKey = createStateSymbol("madeRequired");
const typeChangedFromKey = createStateSymbol("typeChangedFrom");
const returnTypeChangedFromKey = createStateSymbol("returnTypeChangedFrom");
export const namespace = "TypeSpec.Versioning";
function checkIsVersion(
program: Program,
enumMember: EnumMember,
diagnosticTarget: DiagnosticTarget
): Version | undefined {
const version = getVersionForEnumMember(program, enumMember);
if (!version) {
reportDiagnostic(program, {
code: "version-not-found",
target: diagnosticTarget,
format: { version: enumMember.name, enumName: enumMember.enum.name },
});
}
return version;
}
export const $added: AddedDecorator = (
context: DecoratorContext,
t:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
v: EnumMember
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
// retrieve statemap to update or create a new one
const record = program.stateMap(addedOnKey).get(t as Type) ?? new Array<Version>();
record.push(version);
// ensure that records are stored in ascending order
(record as Version[]).sort((a, b) => a.index - b.index);
program.stateMap(addedOnKey).set(t as Type, record);
};
export function $removed(
context: DecoratorContext,
t:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
v: EnumMember
) {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
// retrieve statemap to update or create a new one
const record = program.stateMap(removedOnKey).get(t as Type) ?? new Array<Version>();
record.push(version);
// ensure that records are stored in ascending order
(record as Version[]).sort((a, b) => a.index - b.index);
program.stateMap(removedOnKey).set(t as Type, record);
}
/**
* Returns the mapping of versions to old type values, if applicable
* @param p TypeSpec program
* @param t type to query
* @returns Map of versions to old types, if any
*/
export function getTypeChangedFrom(p: Program, t: Type): Map<Version, Type> | undefined {
return p.stateMap(typeChangedFromKey).get(t) as Map<Version, Type>;
}
export const $typeChangedFrom: TypeChangedFromDecorator = (
context: DecoratorContext,
prop: ModelProperty,
v: EnumMember,
oldType: any
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
// retrieve statemap to update or create a new one
let record = getTypeChangedFrom(program, prop) ?? new Map<Version, any>();
record.set(version, oldType);
// ensure the map is sorted by version
record = new Map([...record.entries()].sort((a, b) => a[0].index - b[0].index));
program.stateMap(typeChangedFromKey).set(prop, record);
};
/**
* Returns the mapping of versions to old return type values, if applicable
* @param p TypeSpec program
* @param t type to query
* @returns Map of versions to old types, if any
*/
export function getReturnTypeChangedFrom(p: Program, t: Type): Map<Version, Type> | undefined {
return p.stateMap(returnTypeChangedFromKey).get(t) as Map<Version, Type>;
}
export const $returnTypeChangedFrom: ReturnTypeChangedFromDecorator = (
context: DecoratorContext,
op: Operation,
v: EnumMember,
oldReturnType: Type
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
// retrieve statemap to update or create a new one
let record = getReturnTypeChangedFrom(program, op) ?? new Map<Version, any>();
record.set(version, oldReturnType);
// ensure the map is sorted by version
record = new Map([...record.entries()].sort((a, b) => a[0].index - b[0].index));
program.stateMap(returnTypeChangedFromKey).set(op, record);
};
interface RenamedFrom {
version: Version;
oldName: string;
}
export const $renamedFrom: RenamedFromDecorator = (
context: DecoratorContext,
t:
| Model
| ModelProperty
| Operation
| Enum
| EnumMember
| Union
| UnionVariant
| Scalar
| Interface,
v: EnumMember,
oldName: string
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
if (oldName === "") {
reportDiagnostic(program, {
code: "invalid-renamed-from-value",
target: t as Type,
});
}
// retrieve statemap to update or create a new one
const record = getRenamedFrom(program, t as Type) ?? [];
record.push({ version: version, oldName: oldName });
// ensure that records are stored in ascending order
record.sort((a, b) => a.version.index - b.version.index);
program.stateMap(renamedFromKey).set(t as Type, record);
};
export const $madeOptional: MadeOptionalDecorator = (
context: DecoratorContext,
t: ModelProperty,
v: EnumMember
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
program.stateMap(madeOptionalKey).set(t, version);
};
export const $madeRequired: MadeRequiredDecorator = (
context: DecoratorContext,
t: ModelProperty,
v: EnumMember
) => {
const { program } = context;
const version = checkIsVersion(context.program, v, context.getArgumentTarget(0)!);
if (!version) {
return;
}
program.stateMap(madeRequiredKey).set(t, version);
};
/**
* @returns the array of RenamedFrom metadata if applicable.
*/
export function getRenamedFrom(p: Program, t: Type): Array<RenamedFrom> | undefined {
return p.stateMap(renamedFromKey).get(t) as Array<RenamedFrom>;
}
/**
* @returns the list of versions for which this decorator has been applied
*/
export function getRenamedFromVersions(p: Program, t: Type): Version[] | undefined {
return getRenamedFrom(p, t)?.map((x) => x.version);
}
/**
* @returns get old name if applicable.
*/
export function getNameAtVersion(p: Program, t: Type, versionKey: ObjectType): string {
const versioningState = getVersioningState(p, versionKey);
const allValues = getRenamedFrom(p, t);
if (!allValues) return "";
for (const val of allValues) {
if (versioningState.timeline.isBefore(versioningState.projectingMoment, val.version)) {
return val.oldName;
}
}
return "";
}
/**
* @returns get old type if applicable.
*/
export function getTypeBeforeVersion(
p: Program,
t: Type,
versionKey: ObjectType
): Type | undefined {
const versioningState = getVersioningState(p, versionKey);
const map = getTypeChangedFrom(p, t);
if (!map) return undefined;
for (const [changedAtVersion, oldType] of map) {
if (versioningState.timeline.isBefore(versioningState.projectingMoment, changedAtVersion)) {
return oldType;
}
}
return undefined;
}
/**
* @returns get old type if applicable.
*/
export function getReturnTypeBeforeVersion(p: Program, t: Type, versionKey: ObjectType): any {
const versioningState = getVersioningState(p, versionKey);
const map = getReturnTypeChangedFrom(p, t);
if (!map) return "";
for (const [changedAtVersion, val] of map) {
if (versioningState.timeline.isBefore(versioningState.projectingMoment, changedAtVersion)) {
return val;
}
}
return "";
}
export function getAddedOnVersions(p: Program, t: Type): Version[] | undefined {
return p.stateMap(addedOnKey).get(t) as Version[];
}
export function getRemovedOnVersions(p: Program, t: Type): Version[] | undefined {
return p.stateMap(removedOnKey).get(t) as Version[];
}
/**
* @returns version when the given type was made optional if applicable.
*/
export function getMadeOptionalOn(p: Program, t: Type): Version | undefined {
return p.stateMap(madeOptionalKey).get(t);
}
/**
* @returns version when the given type was made required if applicable.
*/
export function getMadeRequiredOn(p: Program, t: Type): Version | undefined {
return p.stateMap(madeRequiredKey).get(t);
}
export class VersionMap {
private map = new Map<EnumMember, Version>();
constructor(namespace: Namespace, enumType: Enum) {
let index = 0;
for (const member of enumType.members.values()) {
this.map.set(member, {
name: member.name,
value: member.value?.toString() ?? member.name,
enumMember: member,
index,
namespace,
});
index++;
}
}
public getVersionForEnumMember(member: EnumMember): Version | undefined {
return this.map.get(member);
}
public getVersions(): Version[] {
return [...this.map.values()];
}
public get size(): number {
return this.map.size;
}
}
export const $versioned: VersionedDecorator = (
context: DecoratorContext,
t: Namespace,
versions: Enum
) => {
context.program.stateMap(versionsKey).set(t, new VersionMap(t, versions));
};
/**
* Get the version map of the namespace.
*/
export function getVersion(program: Program, namespace: Namespace): VersionMap | undefined {
return program.stateMap(versionsKey).get(namespace);
}
export function findVersionedNamespace(
program: Program,
namespace: Namespace
): Namespace | undefined {
let current: Namespace | undefined = namespace;
while (current) {
if (program.stateMap(versionsKey).has(current)) {
return current;
}
current = current.namespace;
}
return undefined;
}
export function $useDependency(
context: DecoratorContext,
target: EnumMember | Namespace,
...versionRecords: EnumMember[]
) {
const versions: Array<Version> = [];
// ensure only valid versions are passed in
for (const record of versionRecords) {
const ver = checkIsVersion(context.program, record, context.getArgumentTarget(0)!);
if (ver) {
versions.push(ver);
}
}
if (target.kind === "Namespace") {
let state = context.program.stateMap(useDependencyNamespaceKey).get(target) as Version[];
if (!state) {
state = versions;
} else {
state.push(...versions);
}
context.program.stateMap(useDependencyNamespaceKey).set(target, state);
} else if (target.kind === "EnumMember") {
const targetEnum = target.enum;
let state = context.program.stateMap(useDependencyEnumKey).get(targetEnum) as Map<
EnumMember,
Version[]
>;
if (!state) {
state = new Map<EnumMember, Version[]>();
}
// get any existing versions and combine them
const currentVersions = state.get(target) ?? [];
currentVersions.push(...versions);
state.set(target, currentVersions);
context.program.stateMap(useDependencyEnumKey).set(targetEnum, state);
}
}
export function getUseDependencies(
program: Program,
target: Namespace | Enum,
searchEnum: boolean = true
): Map<Namespace, Map<Version, Version> | Version> | undefined {
const result = new Map<Namespace, Map<Version, Version> | Version>();
if (target.kind === "Namespace") {
let current: Namespace | undefined = target;
while (current) {
const data = program.stateMap(useDependencyNamespaceKey).get(current) as Version[];
if (!data) {
// See if the namspace has a version enum
if (searchEnum) {
const versions = getVersion(program, current)?.getVersions();
if (versions?.length) {
const enumDeps = getUseDependencies(program, versions[0].enumMember.enum);
if (enumDeps) {
return enumDeps;
}
}
}
current = current.namespace;
} else {
for (const v of data) {
result.set(v.namespace, v);
}
return result;
}
}
return undefined;
} else if (target.kind === "Enum") {
const data = program.stateMap(useDependencyEnumKey).get(target) as Map<EnumMember, Version[]>;
if (!data) {
return undefined;
}
const resolved = resolveVersionDependency(program, data);
if (resolved instanceof Map) {
for (const [enumVer, value] of resolved) {
for (const val of value) {
const targetNamespace = val.enumMember.enum.namespace;
if (!targetNamespace) {
reportDiagnostic(program, {
code: "version-not-found",
target: val.enumMember.enum,
format: { version: val.enumMember.name, enumName: val.enumMember.enum.name },
});
return undefined;
}
let subMap = result.get(targetNamespace) as Map<Version, Version>;
if (subMap) {
subMap.set(enumVer, val);
} else {
subMap = new Map([[enumVer, val]]);
}
result.set(targetNamespace, subMap);
}
}
}
}
return result;
}
function findVersionDependencyForNamespace(program: Program, namespace: Namespace) {
let current: Namespace | undefined = namespace;
while (current) {
const data = program.stateMap(versionDependencyKey).get(current);
if (data) {
return data;
}
current = current.namespace;
}
return undefined;
}
export function getVersionDependencies(
program: Program,
namespace: Namespace
@ -526,32 +21,7 @@ export function getVersionDependencies(
return useDeps;
}
const data = findVersionDependencyForNamespace(program, namespace);
if (data === undefined) {
return undefined;
}
const result = new Map();
for (const [key, value] of data) {
result.set(key, resolveVersionDependency(program, value));
}
return result;
}
function resolveVersionDependency(
program: Program,
data: Map<EnumMember, Version[]> | Version[]
): Map<Version, Version[]> | Version[] {
if (!(data instanceof Map)) {
return data;
}
const mapping = new Map<Version, Version[]>();
for (const [key, value] of data) {
const sourceVersion = getVersionForEnumMember(program, key);
if (sourceVersion !== undefined) {
mapping.set(sourceVersion, value);
}
}
return mapping;
return undefined;
}
/**
@ -625,69 +95,6 @@ export function resolveVersions(program: Program, namespace: Namespace): Version
}
}
/**
* Represent the set of projections used to project to that version.
*/
export interface VersionProjections {
version: string | undefined;
projections: ProjectionApplication[];
}
/**
* @internal
*/
export function indexTimeline(
program: Program,
timeline: VersioningTimeline,
projectingMoment: TimelineMoment
) {
const versionKey = program.checker.createType<ObjectType>({
kind: "Object",
properties: {},
} as any);
program.stateMap(versionIndexKey).set(versionKey, { timeline, projectingMoment });
return versionKey;
}
function getVersioningState(
program: Program,
versionKey: ObjectType
): {
timeline: VersioningTimeline;
projectingMoment: TimelineMoment;
} {
return program.stateMap(versionIndexKey).get(versionKey);
}
const versionIndexKey = createStateSymbol("version-index");
export function buildVersionProjections(program: Program, rootNs: Namespace): VersionProjections[] {
const resolutions = resolveVersions(program, rootNs);
const timeline = new VersioningTimeline(
program,
resolutions.map((x) => x.versions)
);
return resolutions.map((resolution) => {
if (resolution.versions.size === 0) {
return { version: undefined, projections: [] };
} else {
const versionKey = indexTimeline(
program,
timeline,
timeline.get(resolution.versions.values().next().value)
);
return {
version: resolution.rootVersion?.value,
projections: [
{
projectionName: "v",
arguments: [versionKey],
},
],
};
}
});
}
const versionCache = new WeakMap<Type, [Namespace, VersionMap] | []>();
function cacheVersion(key: Type, versions: [Namespace, VersionMap] | []) {
versionCache.set(key, versions);
@ -898,53 +305,6 @@ export function getAvailabilityMapInTimeline(
return avail;
}
export function existsAtVersion(p: Program, type: Type, versionKey: ObjectType): boolean {
const versioningState = getVersioningState(p, versionKey);
// if unversioned then everything exists
const availability = getAvailabilityMapInTimeline(p, type, versioningState.timeline);
if (!availability) return true;
const isAvail = availability.get(versioningState.projectingMoment)!;
return isAvail === Availability.Added || isAvail === Availability.Available;
}
export function hasDifferentNameAtVersion(p: Program, type: Type, version: ObjectType): boolean {
return getNameAtVersion(p, type, version) !== "";
}
export function madeOptionalAfter(program: Program, type: Type, versionKey: ObjectType): boolean {
const versioningState = getVersioningState(program, versionKey);
const madeOptionalAtVersion = getMadeOptionalOn(program, type);
if (madeOptionalAtVersion === undefined) {
return false;
}
return versioningState.timeline.isBefore(versioningState.projectingMoment, madeOptionalAtVersion);
}
export function madeRequiredAfter(program: Program, type: Type, versionKey: ObjectType): boolean {
const versioningState = getVersioningState(program, versionKey);
const madeRequiredAtVersion = getMadeRequiredOn(program, type);
if (madeRequiredAtVersion === undefined) {
return false;
}
return versioningState.timeline.isBefore(versioningState.projectingMoment, madeRequiredAtVersion);
}
export function hasDifferentTypeAtVersion(p: Program, type: Type, version: ObjectType): boolean {
return getTypeBeforeVersion(p, type, version) !== undefined;
}
export function hasDifferentReturnTypeAtVersion(
p: Program,
type: Type,
version: ObjectType
): boolean {
return getReturnTypeBeforeVersion(p, type, version) !== "";
}
export function getVersionForEnumMember(program: Program, member: EnumMember): Version | undefined {
// Always lookup for the original type. This ensure reference equality when comparing versions.
member = (member.projectionBase as EnumMember) ?? member;

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

@ -1,9 +1,9 @@
import {
BasicTestRunner,
TestHost,
createTestWrapper,
expectDiagnosticEmpty,
expectDiagnostics,
type BasicTestRunner,
type TestHost,
} from "@typespec/compiler/testing";
import { ok } from "assert";
import { beforeEach, describe, it } from "vitest";

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

@ -1,8 +1,8 @@
import { Namespace, projectProgram } from "@typespec/compiler";
import { BasicTestRunner, createTestWrapper } from "@typespec/compiler/testing";
import { projectProgram, type Namespace } from "@typespec/compiler";
import { createTestWrapper, type BasicTestRunner } from "@typespec/compiler/testing";
import { notStrictEqual, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
import { buildVersionProjections } from "../src/versioning.js";
import { buildVersionProjections } from "../src/projection.js";
import { createVersioningTestHost } from "./test-host.js";
describe("versioning: library loading", () => {
@ -30,7 +30,7 @@ describe("versioning: library loading", () => {
};
// Force loading a different version
const { buildVersionProjections: buildVersionProjectionsDifferent } = await import(
"../src/versioning.js?different=1" as any
"../src/projection.js?different=1" as any
);
notStrictEqual(
buildVersionProjections,

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

@ -1,8 +1,8 @@
import {
BasicTestRunner,
createTestHost,
createTestWrapper,
TestHost,
type BasicTestRunner,
type TestHost,
} from "@typespec/compiler/testing";
import { VersioningTestLibrary } from "../src/testing/index.js";

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

@ -1,4 +1,4 @@
import { Enum, Interface, Model, Union } from "@typespec/compiler";
import type { Enum, Interface, Model, Union } from "@typespec/compiler";
import { ok, strictEqual } from "assert";
export function assertHasProperties(model: Model, props: string[]) {

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

@ -1,13 +1,14 @@
import { Model, Namespace, Operation, Program, projectProgram } from "@typespec/compiler";
import type { Model, Namespace, Operation, Program } from "@typespec/compiler";
import { projectProgram } from "@typespec/compiler";
import {
BasicTestRunner,
createTestWrapper,
expectDiagnosticEmpty,
expectDiagnostics,
type BasicTestRunner,
} from "@typespec/compiler/testing";
import { ok, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
import { buildVersionProjections } from "../src/versioning.js";
import { buildVersionProjections } from "../src/projection.js";
import { createVersioningTestHost, createVersioningTestRunner } from "./test-host.js";
import { assertHasProperties } from "./utils.js";

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

@ -1,4 +1,4 @@
import { Namespace } from "@typespec/compiler";
import type { Namespace } from "@typespec/compiler";
import { deepStrictEqual } from "assert";
import { describe, it } from "vitest";
import { VersioningTimeline } from "../src/versioning-timeline.js";

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

@ -1,28 +1,29 @@
import {
Enum,
Interface,
IntrinsicType,
Model,
Namespace,
Operation,
Program,
ProjectionApplication,
Scalar,
Type,
Union,
projectProgram,
type Enum,
type Interface,
type IntrinsicType,
type Model,
type Namespace,
type Operation,
type Program,
type ProjectionApplication,
type Scalar,
type Type,
type Union,
} from "@typespec/compiler";
import {
BasicTestRunner,
createTestWrapper,
expectDiagnosticEmpty,
expectDiagnostics,
type BasicTestRunner,
} from "@typespec/compiler/testing";
import { deepStrictEqual, fail, ok, strictEqual } from "assert";
import { beforeEach, describe, it } from "vitest";
import { Version } from "../src/types.js";
import { buildVersionProjections, indexTimeline } from "../src/projection.js";
import type { Version } from "../src/types.js";
import { VersioningTimeline } from "../src/versioning-timeline.js";
import { buildVersionProjections, getVersions, indexTimeline } from "../src/versioning.js";
import { getVersions } from "../src/versioning.js";
import { createVersioningTestHost } from "./test-host.js";
import {
assertHasMembers,

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

@ -4,6 +4,7 @@
"compilerOptions": {
"outDir": "dist",
"rootDir": ".",
"verbatimModuleSyntax": true,
"tsBuildInfoFile": "temp/tsconfig.tsbuildinfo"
},
"include": ["src/**/*.ts", "generated-defs/**/*.ts", "test/**/*.ts"]