@typespec/mutator-framework 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (221) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/index.d.ts +3 -0
  3. package/dist/src/index.d.ts.map +1 -0
  4. package/dist/src/index.js +4 -0
  5. package/dist/src/index.js.map +1 -0
  6. package/dist/src/mutation/index.d.ts +13 -0
  7. package/dist/src/mutation/index.d.ts.map +1 -0
  8. package/dist/src/mutation/index.js +13 -0
  9. package/dist/src/mutation/index.js.map +1 -0
  10. package/dist/src/mutation/interface.d.ts +11 -0
  11. package/dist/src/mutation/interface.d.ts.map +1 -0
  12. package/dist/src/mutation/interface.js +17 -0
  13. package/dist/src/mutation/interface.js.map +1 -0
  14. package/dist/src/mutation/intrinsic.d.ts +9 -0
  15. package/dist/src/mutation/intrinsic.d.ts.map +1 -0
  16. package/dist/src/mutation/intrinsic.js +11 -0
  17. package/dist/src/mutation/intrinsic.js.map +1 -0
  18. package/dist/src/mutation/literal.d.ts +9 -0
  19. package/dist/src/mutation/literal.d.ts.map +1 -0
  20. package/dist/src/mutation/literal.js +11 -0
  21. package/dist/src/mutation/literal.js.map +1 -0
  22. package/dist/src/mutation/model-property.d.ts +12 -0
  23. package/dist/src/mutation/model-property.d.ts.map +1 -0
  24. package/dist/src/mutation/model-property.js +18 -0
  25. package/dist/src/mutation/model-property.js.map +1 -0
  26. package/dist/src/mutation/model.d.ts +18 -0
  27. package/dist/src/mutation/model.d.ts.map +1 -0
  28. package/dist/src/mutation/model.js +34 -0
  29. package/dist/src/mutation/model.js.map +1 -0
  30. package/dist/src/mutation/mutation-engine.d.ts +80 -0
  31. package/dist/src/mutation/mutation-engine.d.ts.map +1 -0
  32. package/dist/src/mutation/mutation-engine.js +165 -0
  33. package/dist/src/mutation/mutation-engine.js.map +1 -0
  34. package/dist/src/mutation/mutation-engine.test.d.ts +2 -0
  35. package/dist/src/mutation/mutation-engine.test.d.ts.map +1 -0
  36. package/dist/src/mutation/mutation-engine.test.js +145 -0
  37. package/dist/src/mutation/mutation-engine.test.js.map +1 -0
  38. package/dist/src/mutation/mutation.d.ts +45 -0
  39. package/dist/src/mutation/mutation.d.ts.map +1 -0
  40. package/dist/src/mutation/mutation.js +29 -0
  41. package/dist/src/mutation/mutation.js.map +1 -0
  42. package/dist/src/mutation/operation.d.ts +13 -0
  43. package/dist/src/mutation/operation.d.ts.map +1 -0
  44. package/dist/src/mutation/operation.js +20 -0
  45. package/dist/src/mutation/operation.js.map +1 -0
  46. package/dist/src/mutation/scalar.d.ts +11 -0
  47. package/dist/src/mutation/scalar.d.ts.map +1 -0
  48. package/dist/src/mutation/scalar.js +17 -0
  49. package/dist/src/mutation/scalar.js.map +1 -0
  50. package/dist/src/mutation/simple-mutation-engine.d.ts +8 -0
  51. package/dist/src/mutation/simple-mutation-engine.d.ts.map +1 -0
  52. package/dist/src/mutation/simple-mutation-engine.js +11 -0
  53. package/dist/src/mutation/simple-mutation-engine.js.map +1 -0
  54. package/dist/src/mutation/union-variant.d.ts +10 -0
  55. package/dist/src/mutation/union-variant.d.ts.map +1 -0
  56. package/dist/src/mutation/union-variant.js +12 -0
  57. package/dist/src/mutation/union-variant.js.map +1 -0
  58. package/dist/src/mutation/union.d.ts +11 -0
  59. package/dist/src/mutation/union.d.ts.map +1 -0
  60. package/dist/src/mutation/union.js +18 -0
  61. package/dist/src/mutation/union.js.map +1 -0
  62. package/dist/src/mutation-node/enum-member.d.ts +7 -0
  63. package/dist/src/mutation-node/enum-member.d.ts.map +1 -0
  64. package/dist/src/mutation-node/enum-member.js +6 -0
  65. package/dist/src/mutation-node/enum-member.js.map +1 -0
  66. package/dist/src/mutation-node/enum-member.test.d.ts +2 -0
  67. package/dist/src/mutation-node/enum-member.test.d.ts.map +1 -0
  68. package/dist/src/mutation-node/enum-member.test.js +25 -0
  69. package/dist/src/mutation-node/enum-member.test.js.map +1 -0
  70. package/dist/src/mutation-node/enum.d.ts +8 -0
  71. package/dist/src/mutation-node/enum.d.ts.map +1 -0
  72. package/dist/src/mutation-node/enum.js +30 -0
  73. package/dist/src/mutation-node/enum.js.map +1 -0
  74. package/dist/src/mutation-node/enum.test.d.ts +2 -0
  75. package/dist/src/mutation-node/enum.test.d.ts.map +1 -0
  76. package/dist/src/mutation-node/enum.test.js +25 -0
  77. package/dist/src/mutation-node/enum.test.js.map +1 -0
  78. package/dist/src/mutation-node/factory.d.ts +17 -0
  79. package/dist/src/mutation-node/factory.d.ts.map +1 -0
  80. package/dist/src/mutation-node/factory.js +45 -0
  81. package/dist/src/mutation-node/factory.js.map +1 -0
  82. package/dist/src/mutation-node/index.d.ts +15 -0
  83. package/dist/src/mutation-node/index.d.ts.map +1 -0
  84. package/dist/src/mutation-node/index.js +16 -0
  85. package/dist/src/mutation-node/index.js.map +1 -0
  86. package/dist/src/mutation-node/interface.d.ts +8 -0
  87. package/dist/src/mutation-node/interface.d.ts.map +1 -0
  88. package/dist/src/mutation-node/interface.js +30 -0
  89. package/dist/src/mutation-node/interface.js.map +1 -0
  90. package/dist/src/mutation-node/intrinsic.d.ts +7 -0
  91. package/dist/src/mutation-node/intrinsic.d.ts.map +1 -0
  92. package/dist/src/mutation-node/intrinsic.js +6 -0
  93. package/dist/src/mutation-node/intrinsic.js.map +1 -0
  94. package/dist/src/mutation-node/literal.d.ts +7 -0
  95. package/dist/src/mutation-node/literal.d.ts.map +1 -0
  96. package/dist/src/mutation-node/literal.js +6 -0
  97. package/dist/src/mutation-node/literal.js.map +1 -0
  98. package/dist/src/mutation-node/model-property.d.ts +10 -0
  99. package/dist/src/mutation-node/model-property.d.ts.map +1 -0
  100. package/dist/src/mutation-node/model-property.js +48 -0
  101. package/dist/src/mutation-node/model-property.js.map +1 -0
  102. package/dist/src/mutation-node/model-property.test.d.ts +2 -0
  103. package/dist/src/mutation-node/model-property.test.d.ts.map +1 -0
  104. package/dist/src/mutation-node/model-property.test.js +108 -0
  105. package/dist/src/mutation-node/model-property.test.js.map +1 -0
  106. package/dist/src/mutation-node/model.d.ts +10 -0
  107. package/dist/src/mutation-node/model.d.ts.map +1 -0
  108. package/dist/src/mutation-node/model.js +82 -0
  109. package/dist/src/mutation-node/model.js.map +1 -0
  110. package/dist/src/mutation-node/model.test.d.ts +2 -0
  111. package/dist/src/mutation-node/model.test.d.ts.map +1 -0
  112. package/dist/src/mutation-node/model.test.js +133 -0
  113. package/dist/src/mutation-node/model.test.js.map +1 -0
  114. package/dist/src/mutation-node/mutation-edge.d.ts +18 -0
  115. package/dist/src/mutation-node/mutation-edge.d.ts.map +1 -0
  116. package/dist/src/mutation-node/mutation-edge.js +31 -0
  117. package/dist/src/mutation-node/mutation-edge.js.map +1 -0
  118. package/dist/src/mutation-node/mutation-node.d.ts +29 -0
  119. package/dist/src/mutation-node/mutation-node.d.ts.map +1 -0
  120. package/dist/src/mutation-node/mutation-node.js +82 -0
  121. package/dist/src/mutation-node/mutation-node.js.map +1 -0
  122. package/dist/src/mutation-node/mutation-node.test.d.ts +2 -0
  123. package/dist/src/mutation-node/mutation-node.test.d.ts.map +1 -0
  124. package/dist/src/mutation-node/mutation-node.test.js +88 -0
  125. package/dist/src/mutation-node/mutation-node.test.js.map +1 -0
  126. package/dist/src/mutation-node/mutation-subgraph.d.ts +17 -0
  127. package/dist/src/mutation-node/mutation-subgraph.d.ts.map +1 -0
  128. package/dist/src/mutation-node/mutation-subgraph.js +45 -0
  129. package/dist/src/mutation-node/mutation-subgraph.js.map +1 -0
  130. package/dist/src/mutation-node/operation.d.ts +9 -0
  131. package/dist/src/mutation-node/operation.d.ts.map +1 -0
  132. package/dist/src/mutation-node/operation.js +44 -0
  133. package/dist/src/mutation-node/operation.js.map +1 -0
  134. package/dist/src/mutation-node/scalar.d.ts +8 -0
  135. package/dist/src/mutation-node/scalar.d.ts.map +1 -0
  136. package/dist/src/mutation-node/scalar.js +28 -0
  137. package/dist/src/mutation-node/scalar.js.map +1 -0
  138. package/dist/src/mutation-node/scalar.test.d.ts +2 -0
  139. package/dist/src/mutation-node/scalar.test.d.ts.map +1 -0
  140. package/dist/src/mutation-node/scalar.test.js +40 -0
  141. package/dist/src/mutation-node/scalar.test.js.map +1 -0
  142. package/dist/src/mutation-node/tuple.d.ts +9 -0
  143. package/dist/src/mutation-node/tuple.d.ts.map +1 -0
  144. package/dist/src/mutation-node/tuple.js +32 -0
  145. package/dist/src/mutation-node/tuple.js.map +1 -0
  146. package/dist/src/mutation-node/tuple.test.d.ts +2 -0
  147. package/dist/src/mutation-node/tuple.test.d.ts.map +1 -0
  148. package/dist/src/mutation-node/tuple.test.js +29 -0
  149. package/dist/src/mutation-node/tuple.test.js.map +1 -0
  150. package/dist/src/mutation-node/union-variant.d.ts +8 -0
  151. package/dist/src/mutation-node/union-variant.d.ts.map +1 -0
  152. package/dist/src/mutation-node/union-variant.js +23 -0
  153. package/dist/src/mutation-node/union-variant.js.map +1 -0
  154. package/dist/src/mutation-node/union-variant.test.d.ts +2 -0
  155. package/dist/src/mutation-node/union-variant.test.d.ts.map +1 -0
  156. package/dist/src/mutation-node/union-variant.test.js +27 -0
  157. package/dist/src/mutation-node/union-variant.test.js.map +1 -0
  158. package/dist/src/mutation-node/union.d.ts +8 -0
  159. package/dist/src/mutation-node/union.d.ts.map +1 -0
  160. package/dist/src/mutation-node/union.js +30 -0
  161. package/dist/src/mutation-node/union.js.map +1 -0
  162. package/dist/src/mutation-node/union.test.d.ts +2 -0
  163. package/dist/src/mutation-node/union.test.d.ts.map +1 -0
  164. package/dist/src/mutation-node/union.test.js +25 -0
  165. package/dist/src/mutation-node/union.test.js.map +1 -0
  166. package/dist/test/test-host.d.ts +2 -0
  167. package/dist/test/test-host.d.ts.map +1 -0
  168. package/dist/test/test-host.js +6 -0
  169. package/dist/test/test-host.js.map +1 -0
  170. package/dist/test/utils.d.ts +4 -0
  171. package/dist/test/utils.d.ts.map +1 -0
  172. package/dist/test/utils.js +8 -0
  173. package/dist/test/utils.js.map +1 -0
  174. package/package.json +40 -0
  175. package/package.json.bak +42 -0
  176. package/readme.md +339 -0
  177. package/src/index.ts +4 -0
  178. package/src/mutation/index.ts +12 -0
  179. package/src/mutation/interface.ts +38 -0
  180. package/src/mutation/intrinsic.ts +23 -0
  181. package/src/mutation/literal.ts +29 -0
  182. package/src/mutation/model-property.ts +35 -0
  183. package/src/mutation/model.ts +58 -0
  184. package/src/mutation/mutation-engine.test.ts +202 -0
  185. package/src/mutation/mutation-engine.ts +288 -0
  186. package/src/mutation/mutation.ts +90 -0
  187. package/src/mutation/operation.ts +40 -0
  188. package/src/mutation/scalar.ts +36 -0
  189. package/src/mutation/simple-mutation-engine.ts +21 -0
  190. package/src/mutation/union-variant.ts +30 -0
  191. package/src/mutation/union.ts +39 -0
  192. package/src/mutation-node/enum-member.test.ts +26 -0
  193. package/src/mutation-node/enum-member.ts +8 -0
  194. package/src/mutation-node/enum.test.ts +26 -0
  195. package/src/mutation-node/enum.ts +33 -0
  196. package/src/mutation-node/factory.ts +95 -0
  197. package/src/mutation-node/index.ts +16 -0
  198. package/src/mutation-node/interface.ts +33 -0
  199. package/src/mutation-node/intrinsic.ts +8 -0
  200. package/src/mutation-node/literal.ts +10 -0
  201. package/src/mutation-node/model-property.test.ts +136 -0
  202. package/src/mutation-node/model-property.ts +53 -0
  203. package/src/mutation-node/model.test.ts +151 -0
  204. package/src/mutation-node/model.ts +89 -0
  205. package/src/mutation-node/mutation-edge.ts +43 -0
  206. package/src/mutation-node/mutation-node.test.ts +94 -0
  207. package/src/mutation-node/mutation-node.ts +110 -0
  208. package/src/mutation-node/mutation-subgraph.ts +59 -0
  209. package/src/mutation-node/operation.ts +49 -0
  210. package/src/mutation-node/scalar.test.ts +44 -0
  211. package/src/mutation-node/scalar.ts +32 -0
  212. package/src/mutation-node/tuple.test.ts +30 -0
  213. package/src/mutation-node/tuple.ts +35 -0
  214. package/src/mutation-node/union-variant.test.ts +28 -0
  215. package/src/mutation-node/union-variant.ts +26 -0
  216. package/src/mutation-node/union.test.ts +26 -0
  217. package/src/mutation-node/union.ts +33 -0
  218. package/test/test-host.ts +6 -0
  219. package/test/utils.ts +9 -0
  220. package/tsconfig.json +19 -0
  221. package/vitest.config.ts +4 -0
@@ -0,0 +1,30 @@
1
+ import type { MemberType, Type, UnionVariant } from "@typespec/compiler";
2
+ import type {
3
+ CustomMutationClasses,
4
+ MutationEngine,
5
+ MutationFor,
6
+ MutationOptions,
7
+ } from "./mutation-engine.js";
8
+ import { Mutation } from "./mutation.js";
9
+
10
+ export class UnionVariantMutation<
11
+ TOptions extends MutationOptions,
12
+ TCustomMutations extends CustomMutationClasses,
13
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
14
+ > extends Mutation<UnionVariant, TCustomMutations, TOptions, TEngine> {
15
+ readonly kind = "UnionVariant";
16
+ type!: MutationFor<TCustomMutations, Type["kind"]>;
17
+
18
+ constructor(
19
+ engine: TEngine,
20
+ sourceType: UnionVariant,
21
+ referenceTypes: MemberType[] = [],
22
+ options: TOptions,
23
+ ) {
24
+ super(engine, sourceType, referenceTypes, options);
25
+ }
26
+
27
+ mutate(): void {
28
+ this.type = this.engine.mutate(this.sourceType.type, this.options);
29
+ }
30
+ }
@@ -0,0 +1,39 @@
1
+ import type { MemberType, Union } from "@typespec/compiler";
2
+ import type {
3
+ CustomMutationClasses,
4
+ MutationEngine,
5
+ MutationFor,
6
+ MutationOptions,
7
+ } from "./mutation-engine.js";
8
+ import { Mutation } from "./mutation.js";
9
+
10
+ export class UnionMutation<
11
+ TOptions extends MutationOptions,
12
+ TCustomMutations extends CustomMutationClasses,
13
+ TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
14
+ > extends Mutation<Union, TCustomMutations, TOptions, TEngine> {
15
+ readonly kind = "Union";
16
+ variants: Map<string | symbol, MutationFor<TCustomMutations, "UnionVariant">> = new Map();
17
+
18
+ constructor(
19
+ engine: TEngine,
20
+ sourceType: Union,
21
+ referenceTypes: MemberType[] = [],
22
+ options: TOptions,
23
+ ) {
24
+ super(engine, sourceType, referenceTypes, options);
25
+ }
26
+
27
+ protected mutateVariants() {
28
+ this.variants = new Map(
29
+ [...this.sourceType.variants].map(([name, variant]) => [
30
+ name,
31
+ this.engine.mutate(variant, this.options),
32
+ ]),
33
+ );
34
+ }
35
+
36
+ mutate() {
37
+ this.mutateVariants();
38
+ }
39
+ }
@@ -0,0 +1,26 @@
1
+ import { t, type TesterInstance } from "@typespec/compiler/testing";
2
+ import { beforeEach, expect, it } from "vitest";
3
+ import { Tester } from "../../test/test-host.js";
4
+ import { getSubgraph } from "../../test/utils.js";
5
+ let runner: TesterInstance;
6
+ beforeEach(async () => {
7
+ runner = await Tester.createInstance();
8
+ });
9
+
10
+ it("handles mutation of member values", async () => {
11
+ const { program, Foo, a } = await runner.compile(t.code`
12
+ enum ${t.enum("Foo")} {
13
+ ${t.enumMember("a")}: "valueA";
14
+ b: "valueB";
15
+ }
16
+ `);
17
+
18
+ const subgraph = getSubgraph(program);
19
+ const fooNode = subgraph.getNode(Foo);
20
+ const aNode = subgraph.getNode(a);
21
+ aNode.mutate((clone) => (clone.value = "valueARenamed"));
22
+ expect(aNode.isMutated).toBe(true);
23
+ expect(fooNode.isMutated).toBe(true);
24
+ expect(fooNode.mutatedType.members.get("a") === aNode.mutatedType).toBe(true);
25
+ expect(aNode.mutatedType.value).toBe("valueARenamed");
26
+ });
@@ -0,0 +1,8 @@
1
+ import type { EnumMember } from "@typespec/compiler";
2
+ import { MutationNode } from "./mutation-node.js";
3
+
4
+ export class EnumMemberMutationNode extends MutationNode<EnumMember> {
5
+ readonly kind = "EnumMember";
6
+
7
+ traverse() {}
8
+ }
@@ -0,0 +1,26 @@
1
+ import { t, type TesterInstance } from "@typespec/compiler/testing";
2
+ import { beforeEach, expect, it } from "vitest";
3
+ import { Tester } from "../../test/test-host.js";
4
+ import { getSubgraph } from "../../test/utils.js";
5
+ let runner: TesterInstance;
6
+ beforeEach(async () => {
7
+ runner = await Tester.createInstance();
8
+ });
9
+
10
+ it("handles mutation of members", async () => {
11
+ const { program, Foo, a } = await runner.compile(t.code`
12
+ enum ${t.enum("Foo")} {
13
+ ${t.enumMember("a")};
14
+ b;
15
+ }
16
+ `);
17
+
18
+ const subgraph = getSubgraph(program);
19
+ const fooNode = subgraph.getNode(Foo);
20
+ const aNode = subgraph.getNode(a);
21
+ aNode.mutate((clone) => (clone.name = "aRenamed"));
22
+ expect(aNode.isMutated).toBe(true);
23
+ expect(fooNode.isMutated).toBe(true);
24
+ expect(fooNode.mutatedType.members.get("a") === undefined).toBeTruthy();
25
+ expect(fooNode.mutatedType.members.get("aRenamed") === aNode.mutatedType).toBeTruthy();
26
+ });
@@ -0,0 +1,33 @@
1
+ import type { Enum, EnumMember } from "@typespec/compiler";
2
+ import { MutationEdge } from "./mutation-edge.js";
3
+ import { MutationNode } from "./mutation-node.js";
4
+
5
+ export class EnumMutationNode extends MutationNode<Enum> {
6
+ readonly kind = "Enum";
7
+
8
+ traverse() {
9
+ for (const member of this.sourceType.members.values()) {
10
+ const memberNode = this.subgraph.getNode(member);
11
+ this.connectMember(memberNode, member.name);
12
+ }
13
+ }
14
+
15
+ connectMember(memberNode: MutationNode<EnumMember>, sourcePropName: string) {
16
+ MutationEdge.create(this, memberNode, {
17
+ onTailMutation: () => {
18
+ this.mutatedType.members.delete(sourcePropName);
19
+ this.mutatedType.members.set(memberNode.mutatedType.name, memberNode.mutatedType);
20
+ },
21
+ onTailDeletion: () => {
22
+ this.mutatedType.members.delete(sourcePropName);
23
+ },
24
+ onTailReplaced: (newTail) => {
25
+ if (newTail.mutatedType.kind !== "EnumMember") {
26
+ throw new Error("Cannot replace enum member with non-enum member type");
27
+ }
28
+ this.mutatedType.members.delete(sourcePropName);
29
+ this.mutatedType.members.set(newTail.mutatedType.name, newTail.mutatedType);
30
+ },
31
+ });
32
+ }
33
+ }
@@ -0,0 +1,95 @@
1
+ import type {
2
+ BooleanLiteral,
3
+ Enum,
4
+ EnumMember,
5
+ Interface,
6
+ IntrinsicType,
7
+ Model,
8
+ ModelProperty,
9
+ NumericLiteral,
10
+ Operation,
11
+ Scalar,
12
+ StringLiteral,
13
+ Tuple,
14
+ Type,
15
+ Union,
16
+ UnionVariant,
17
+ } from "@typespec/compiler";
18
+ import { EnumMemberMutationNode } from "./enum-member.js";
19
+ import { EnumMutationNode } from "./enum.js";
20
+ import { InterfaceMutationNode } from "./interface.js";
21
+ import { IntrinsicMutationNode } from "./intrinsic.js";
22
+ import { LiteralMutationNode } from "./literal.js";
23
+ import { ModelPropertyMutationNode } from "./model-property.js";
24
+ import { ModelMutationNode } from "./model.js";
25
+ import type { MutationSubgraph } from "./mutation-subgraph.js";
26
+ import { OperationMutationNode } from "./operation.js";
27
+ import { ScalarMutationNode } from "./scalar.js";
28
+ import { TupleMutationNode } from "./tuple.js";
29
+ import { UnionVariantMutationNode } from "./union-variant.js";
30
+ import { UnionMutationNode } from "./union.js";
31
+
32
+ export function mutationNodeFor<T extends Type>(
33
+ subgraph: MutationSubgraph,
34
+ sourceType: T,
35
+ ): MutationNodeForType<T> {
36
+ switch (sourceType.kind) {
37
+ case "Operation":
38
+ return new OperationMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
39
+ case "Interface":
40
+ return new InterfaceMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
41
+ case "Model":
42
+ return new ModelMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
43
+ case "ModelProperty":
44
+ return new ModelPropertyMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
45
+ case "Scalar":
46
+ return new ScalarMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
47
+ case "Tuple":
48
+ return new TupleMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
49
+ case "Union":
50
+ return new UnionMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
51
+ case "UnionVariant":
52
+ return new UnionVariantMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
53
+ case "Enum":
54
+ return new EnumMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
55
+ case "EnumMember":
56
+ return new EnumMemberMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
57
+ case "String":
58
+ case "Number":
59
+ case "Boolean":
60
+ return new LiteralMutationNode(
61
+ subgraph,
62
+ sourceType as StringLiteral | NumericLiteral | BooleanLiteral,
63
+ ) as MutationNodeForType<T>;
64
+ case "Intrinsic":
65
+ return new IntrinsicMutationNode(subgraph, sourceType) as MutationNodeForType<T>;
66
+ default:
67
+ throw new Error("Unsupported type kind: " + sourceType.kind);
68
+ }
69
+ }
70
+
71
+ export type MutationNodeForType<T extends Type> = T extends Model
72
+ ? ModelMutationNode
73
+ : T extends Interface
74
+ ? InterfaceMutationNode
75
+ : T extends Operation
76
+ ? OperationMutationNode
77
+ : T extends ModelProperty
78
+ ? ModelPropertyMutationNode
79
+ : T extends Scalar
80
+ ? ScalarMutationNode
81
+ : T extends Tuple
82
+ ? TupleMutationNode
83
+ : T extends Union
84
+ ? UnionMutationNode
85
+ : T extends UnionVariant
86
+ ? UnionVariantMutationNode
87
+ : T extends Enum
88
+ ? EnumMutationNode
89
+ : T extends EnumMember
90
+ ? EnumMemberMutationNode
91
+ : T extends StringLiteral | NumericLiteral | BooleanLiteral
92
+ ? LiteralMutationNode
93
+ : T extends IntrinsicType
94
+ ? IntrinsicMutationNode
95
+ : never;
@@ -0,0 +1,16 @@
1
+ export * from "./mutation-node.js";
2
+
3
+ export * from "./enum.js";
4
+ export * from "./interface.js";
5
+ export * from "./intrinsic.js";
6
+ export * from "./literal.js";
7
+ export * from "./model-property.js";
8
+ export * from "./model.js";
9
+ export * from "./mutation-edge.js";
10
+ export * from "./mutation-subgraph.js";
11
+ export * from "./operation.js";
12
+ export * from "./scalar.js";
13
+ export * from "./tuple.js";
14
+ export * from "./union-variant.js";
15
+ export * from "./union.js";
16
+ //export * from "./enum-member.js";
@@ -0,0 +1,33 @@
1
+ import type { Interface, Operation } from "@typespec/compiler";
2
+ import { MutationEdge } from "./mutation-edge.js";
3
+ import { MutationNode } from "./mutation-node.js";
4
+
5
+ export class InterfaceMutationNode extends MutationNode<Interface> {
6
+ readonly kind = "Interface";
7
+
8
+ traverse() {
9
+ for (const [opName, op] of this.sourceType.operations) {
10
+ const opNode = this.subgraph.getNode(op);
11
+ this.connectOperation(opNode, opName);
12
+ }
13
+ }
14
+
15
+ connectOperation(opNode: MutationNode<Operation>, opName: string) {
16
+ MutationEdge.create(this, opNode, {
17
+ onTailMutation: () => {
18
+ this.mutatedType.operations.delete(opName);
19
+ this.mutatedType.operations.set(opNode.mutatedType.name, opNode.mutatedType);
20
+ },
21
+ onTailDeletion: () => {
22
+ this.mutatedType.operations.delete(opName);
23
+ },
24
+ onTailReplaced: (newTail) => {
25
+ if (newTail.mutatedType.kind !== "Operation") {
26
+ throw new Error("Cannot replace operation with non-operation type");
27
+ }
28
+ this.mutatedType.operations.delete(opName);
29
+ this.mutatedType.operations.set(newTail.mutatedType.name, newTail.mutatedType);
30
+ },
31
+ });
32
+ }
33
+ }
@@ -0,0 +1,8 @@
1
+ import type { IntrinsicType } from "@typespec/compiler";
2
+ import { MutationNode } from "./mutation-node.js";
3
+
4
+ export class IntrinsicMutationNode extends MutationNode<IntrinsicType> {
5
+ readonly kind = "Intrinsic";
6
+
7
+ traverse() {}
8
+ }
@@ -0,0 +1,10 @@
1
+ import type { BooleanLiteral, NumericLiteral, StringLiteral } from "@typespec/compiler";
2
+ import { MutationNode } from "./mutation-node.js";
3
+
4
+ export class LiteralMutationNode extends MutationNode<
5
+ StringLiteral | NumericLiteral | BooleanLiteral
6
+ > {
7
+ readonly kind = "Literal";
8
+
9
+ traverse() {}
10
+ }
@@ -0,0 +1,136 @@
1
+ import { t, type TesterInstance } from "@typespec/compiler/testing";
2
+ import { $ } from "@typespec/compiler/typekit";
3
+ import { beforeEach, expect, it } from "vitest";
4
+ import { Tester } from "../../test/test-host.js";
5
+ import { getSubgraph } from "../../test/utils.js";
6
+
7
+ let runner: TesterInstance;
8
+ beforeEach(async () => {
9
+ runner = await Tester.createInstance();
10
+ });
11
+
12
+ it("handles mutation of property types", async () => {
13
+ const { prop, program } = await runner.compile(t.code`
14
+ model Foo {
15
+ ${t.modelProperty("prop")}: string;
16
+ }
17
+ `);
18
+ const subgraph = getSubgraph(program);
19
+ const propNode = subgraph.getNode(prop);
20
+ const stringNode = subgraph.getNode($(program).builtin.string);
21
+ stringNode.mutate();
22
+ expect(propNode.isMutated).toBe(true);
23
+ expect(propNode.mutatedType.type === stringNode.mutatedType).toBe(true);
24
+ });
25
+
26
+ it("handles mutating a reference", async () => {
27
+ const { Foo, Bar, prop, program } = await runner.compile(t.code`
28
+ model ${t.model("Foo")} {
29
+ ${t.modelProperty("prop")}: Bar;
30
+ };
31
+ model ${t.model("Bar")} {}
32
+ `);
33
+
34
+ const subgraph = getSubgraph(program);
35
+ const fooNode = subgraph.getNode(Foo);
36
+ const propNode = subgraph.getNode(prop);
37
+ const barPrime = subgraph.getReferenceNode(prop);
38
+
39
+ // initially the source type is just Bar.
40
+ expect(barPrime.sourceType === Bar).toBe(true);
41
+
42
+ barPrime.mutate();
43
+ expect(fooNode.isMutated).toBe(true);
44
+ expect(propNode.isMutated).toBe(true);
45
+ expect(barPrime.isMutated).toBe(true);
46
+ expect(fooNode.mutatedType.properties.get("prop")!.type === barPrime.mutatedType).toBeTruthy();
47
+
48
+ const barNode = subgraph.getNode(Bar);
49
+ barNode.mutate();
50
+ expect(barNode.isMutated).toBe(true);
51
+ expect(barPrime.isMutated).toBe(true);
52
+ // the mutated type doesn't change here.
53
+ expect(fooNode.mutatedType.properties.get("prop")!.type === barPrime.mutatedType).toBeTruthy();
54
+ });
55
+
56
+ it("handles replacing the model reference", async () => {
57
+ const { Foo, Bar, prop, program } = await runner.compile(t.code`
58
+ model ${t.model("Foo")} {
59
+ ${t.modelProperty("prop")}: Bar;
60
+ };
61
+ model ${t.model("Bar")} {}
62
+ `);
63
+ const tk = $(program);
64
+ const subgraph = getSubgraph(program);
65
+ const fooNode = subgraph.getNode(Foo);
66
+ const propNode = subgraph.getNode(prop);
67
+ const barPrime = subgraph.getReferenceNode(prop);
68
+ const unionType = tk.union.create({
69
+ variants: [
70
+ tk.unionVariant.create({ type: tk.builtin.string }),
71
+ tk.unionVariant.create({ type: Bar }),
72
+ ],
73
+ });
74
+
75
+ const replacedBarPrime = barPrime.replace(unionType);
76
+
77
+ // the subgraph now returns the new reference node
78
+ expect(subgraph.getReferenceNode(prop) === replacedBarPrime).toBe(true);
79
+
80
+ // foo and prop are marked mutated, barPrime is replaced
81
+ expect(fooNode.isMutated).toBe(true);
82
+ expect(propNode.isMutated).toBe(true);
83
+ expect(barPrime.isReplaced).toBe(true);
84
+
85
+ // prop's type is the replaced type
86
+ expect(tk.union.is(propNode.mutatedType.type)).toBe(true);
87
+ expect(
88
+ fooNode.mutatedType!.properties.get("prop")!.type === replacedBarPrime.mutatedType,
89
+ ).toBeTruthy();
90
+ });
91
+
92
+ it("handles mutating a reference to a reference", async () => {
93
+ const { myString, Foo, fprop, Bar, program } = await runner.compile(t.code`
94
+ scalar ${t.scalar("myString")} extends string;
95
+ model ${t.model("Foo")} {
96
+ ${t.modelProperty("fprop")}: myString;
97
+ };
98
+ model ${t.model("Bar")} {
99
+ bprop: Foo.fprop;
100
+ }
101
+ `);
102
+
103
+ const subgraph = getSubgraph(program);
104
+ const fooNode = subgraph.getNode(Foo);
105
+ const barNode = subgraph.getNode(Bar);
106
+ const myStringNode = subgraph.getNode(myString);
107
+
108
+ myStringNode.mutate();
109
+ expect(myStringNode.isMutated).toBe(true);
110
+ expect(fooNode.isMutated).toBe(true);
111
+ expect(barNode.isMutated).toBe(true);
112
+
113
+ // Foo.prop's type is the mutated myString
114
+ expect(
115
+ fooNode.mutatedType.properties.get("fprop")!.type === myStringNode.mutatedType,
116
+ ).toBeTruthy();
117
+
118
+ // Bar.prop's type is the mutated Foo.prop
119
+ expect(
120
+ barNode.mutatedType.properties.get("bprop")!.type ===
121
+ fooNode.mutatedType.properties.get("fprop")!,
122
+ ).toBeTruthy();
123
+
124
+ const fpropRefNode = subgraph.getReferenceNode(fprop);
125
+ fpropRefNode.mutate();
126
+ expect(fpropRefNode.isMutated).toBe(true);
127
+ expect(
128
+ fooNode.mutatedType.properties.get("fprop")!.type === fpropRefNode.mutatedType,
129
+ ).toBeTruthy();
130
+
131
+ // Bar.bprop references the mutated type (though is the same reference since fprop was already mutated)
132
+ expect(
133
+ barNode.mutatedType.properties.get("bprop")!.type ===
134
+ fooNode.mutatedType.properties.get("fprop")!,
135
+ ).toBeTruthy();
136
+ });
@@ -0,0 +1,53 @@
1
+ import type { ModelProperty, Type } from "@typespec/compiler";
2
+ import { MutationEdge } from "./mutation-edge.js";
3
+ import { MutationNode } from "./mutation-node.js";
4
+
5
+ export class ModelPropertyMutationNode extends MutationNode<ModelProperty> {
6
+ readonly kind = "ModelProperty";
7
+ #referenceMutated = false;
8
+
9
+ traverse() {
10
+ const typeNode = this.subgraph.getNode(this.sourceType.type);
11
+ const referenceNode = this.subgraph.getReferenceNode(this.sourceType);
12
+
13
+ this.connectType(typeNode);
14
+ this.connectReference(referenceNode);
15
+ }
16
+
17
+ connectReference(referenceNode: MutationNode<Type>) {
18
+ MutationEdge.create(this, referenceNode, {
19
+ onTailMutation: () => {
20
+ this.#referenceMutated = true;
21
+ this.mutatedType.type = referenceNode.mutatedType;
22
+ },
23
+ onTailDeletion: () => {
24
+ this.#referenceMutated = true;
25
+ this.mutatedType.type = this.$.intrinsic.any;
26
+ },
27
+ onTailReplaced: (newTail) => {
28
+ this.#referenceMutated = true;
29
+ this.mutatedType.type = newTail.mutatedType;
30
+ },
31
+ });
32
+ }
33
+
34
+ connectType(typeNode: MutationNode<Type>) {
35
+ MutationEdge.create(this, typeNode, {
36
+ onTailMutation: () => {
37
+ if (this.#referenceMutated) {
38
+ return;
39
+ }
40
+ this.mutatedType.type = typeNode.mutatedType;
41
+ },
42
+ onTailDeletion: () => {
43
+ if (this.#referenceMutated) {
44
+ return;
45
+ }
46
+ this.mutatedType.type = this.$.intrinsic.any;
47
+ },
48
+ onTailReplaced: (newTail) => {
49
+ this.mutatedType.type = newTail.mutatedType;
50
+ },
51
+ });
52
+ }
53
+ }
@@ -0,0 +1,151 @@
1
+ import type { Model, Type } from "@typespec/compiler";
2
+ import { t, type TesterInstance } from "@typespec/compiler/testing";
3
+ import { beforeEach, expect, it } from "vitest";
4
+ import { Tester } from "../../test/test-host.js";
5
+ import { getSubgraph } from "../../test/utils.js";
6
+
7
+ let runner: TesterInstance;
8
+ beforeEach(async () => {
9
+ runner = await Tester.createInstance();
10
+ });
11
+
12
+ it("handles mutation of properties", async () => {
13
+ const { Foo, program } = await runner.compile(t.code`
14
+ model ${t.model("Foo")} {
15
+ prop: string;
16
+ }
17
+ `);
18
+ const subgraph = getSubgraph(program);
19
+ const fooNode = subgraph.getNode(Foo);
20
+ const propNode = subgraph.getNode(Foo.properties.get("prop")!);
21
+ propNode.mutate();
22
+ expect(fooNode.isMutated).toBe(true);
23
+ expect(fooNode.mutatedType.properties.get("prop") === propNode.mutatedType).toBe(true);
24
+ });
25
+
26
+ it("handles deletion of properties", async () => {
27
+ const { Foo, program } = await runner.compile(t.code`
28
+ model ${t.model("Foo")} {
29
+ prop: string;
30
+ }
31
+ `);
32
+ const subgraph = getSubgraph(program);
33
+ const fooNode = subgraph.getNode(Foo);
34
+ const propNode = subgraph.getNode(Foo.properties.get("prop")!);
35
+ propNode.delete();
36
+ expect(fooNode.isMutated).toBe(true);
37
+ expect(fooNode.mutatedType.properties.get("prop")).toBeUndefined();
38
+ });
39
+
40
+ it("handles mutation of properties with name change", async () => {
41
+ const { Foo, program } = await runner.compile(t.code`
42
+ model ${t.model("Foo")} {
43
+ prop: string;
44
+ }
45
+ `);
46
+ const subgraph = getSubgraph(program);
47
+ const fooNode = subgraph.getNode(Foo);
48
+ const propNode = subgraph.getNode(Foo.properties.get("prop")!);
49
+ propNode.mutate((clone) => (clone.name = "propRenamed"));
50
+ expect(fooNode.isMutated).toBe(true);
51
+ expect(fooNode.mutatedType.properties.get("prop") === undefined).toBe(true);
52
+ expect(fooNode.mutatedType.properties.get("propRenamed") === propNode.mutatedType).toBe(true);
53
+ });
54
+
55
+ it("handles mutation of base models", async () => {
56
+ const { Foo, Bar, program } = await runner.compile(t.code`
57
+ model ${t.model("Foo")} extends Bar {
58
+ barProp: string;
59
+ }
60
+
61
+ model ${t.model("Bar")} {
62
+ bazProp: string;
63
+ }
64
+ `);
65
+ const subgraph = getSubgraph(program);
66
+ const fooNode = subgraph.getNode(Foo);
67
+ const barNode = subgraph.getNode(Bar);
68
+
69
+ barNode.mutate();
70
+ expect(barNode.isMutated).toBe(true);
71
+ expect(fooNode.isMutated).toBe(true);
72
+ expect(fooNode.mutatedType.baseModel === barNode.mutatedType).toBeTruthy();
73
+ });
74
+
75
+ it("handles deletion of base models", async () => {
76
+ const { Foo, Bar, program } = await runner.compile(t.code`
77
+ model ${t.model("Foo")} extends Bar {
78
+ barProp: string;
79
+ }
80
+
81
+ model ${t.model("Bar")} {
82
+ bazProp: string;
83
+ }
84
+ `);
85
+ const subgraph = getSubgraph(program);
86
+ const fooNode = subgraph.getNode(Foo);
87
+ const barNode = subgraph.getNode(Bar);
88
+
89
+ barNode.delete();
90
+ expect(barNode.isDeleted).toBe(true);
91
+ expect(fooNode.isMutated).toBe(true);
92
+ expect(fooNode.mutatedType.baseModel).toBeUndefined();
93
+ });
94
+
95
+ it("handles mutation of indexers", async () => {
96
+ const { Foo, Bar, program } = await runner.compile(t.code`
97
+ model ${t.model("Foo")} is Record<Bar> {};
98
+ model ${t.model("Bar")} {
99
+ bazProp: string;
100
+ }
101
+ `);
102
+ const subgraph = getSubgraph(program);
103
+ const fooNode = subgraph.getNode(Foo);
104
+ const barNode = subgraph.getNode(Bar);
105
+
106
+ barNode.mutate();
107
+ expect(barNode.isMutated).toBe(true);
108
+ expect(fooNode.isMutated).toBe(true);
109
+ expect((fooNode.mutatedType.indexer?.value as Type) === barNode.mutatedType).toBeTruthy();
110
+ });
111
+
112
+ it("handles mutation of arrays", async () => {
113
+ const { Foo, Bar, bazProp, program } = await runner.compile(t.code`
114
+ model ${t.model("Foo")} {};
115
+ model ${t.model("Bar")} {
116
+ ${t.modelProperty("bazProp")}: Foo[];
117
+ }
118
+ `);
119
+
120
+ const subgraph = getSubgraph(program);
121
+ const fooNode = subgraph.getNode(Foo);
122
+ const barNode = subgraph.getNode(Bar);
123
+ const bazPropNode = subgraph.getNode(bazProp);
124
+
125
+ fooNode.mutate();
126
+ expect(fooNode.isMutated).toBe(true);
127
+ expect(barNode.isMutated).toBe(true);
128
+ expect(bazPropNode.isMutated).toBe(true);
129
+ expect(
130
+ (bazPropNode.mutatedType.type as Model).indexer!.value === fooNode.mutatedType,
131
+ ).toBeTruthy();
132
+ });
133
+
134
+ it("handles circular models", async () => {
135
+ const { Foo, Bar, program } = await runner.compile(t.code`
136
+ model ${t.model("Foo")} {
137
+ bar: Bar;
138
+ };
139
+ model ${t.model("Bar")} {
140
+ foo: Foo;
141
+ }
142
+ `);
143
+
144
+ const subgraph = getSubgraph(program);
145
+ const fooNode = subgraph.getNode(Foo);
146
+ const barNode = subgraph.getNode(Bar);
147
+
148
+ fooNode.mutate();
149
+ expect(fooNode.isMutated).toBe(true);
150
+ expect(barNode.isMutated).toBe(true);
151
+ });