@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.
- package/LICENSE +21 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +4 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/mutation/index.d.ts +13 -0
- package/dist/src/mutation/index.d.ts.map +1 -0
- package/dist/src/mutation/index.js +13 -0
- package/dist/src/mutation/index.js.map +1 -0
- package/dist/src/mutation/interface.d.ts +11 -0
- package/dist/src/mutation/interface.d.ts.map +1 -0
- package/dist/src/mutation/interface.js +17 -0
- package/dist/src/mutation/interface.js.map +1 -0
- package/dist/src/mutation/intrinsic.d.ts +9 -0
- package/dist/src/mutation/intrinsic.d.ts.map +1 -0
- package/dist/src/mutation/intrinsic.js +11 -0
- package/dist/src/mutation/intrinsic.js.map +1 -0
- package/dist/src/mutation/literal.d.ts +9 -0
- package/dist/src/mutation/literal.d.ts.map +1 -0
- package/dist/src/mutation/literal.js +11 -0
- package/dist/src/mutation/literal.js.map +1 -0
- package/dist/src/mutation/model-property.d.ts +12 -0
- package/dist/src/mutation/model-property.d.ts.map +1 -0
- package/dist/src/mutation/model-property.js +18 -0
- package/dist/src/mutation/model-property.js.map +1 -0
- package/dist/src/mutation/model.d.ts +18 -0
- package/dist/src/mutation/model.d.ts.map +1 -0
- package/dist/src/mutation/model.js +34 -0
- package/dist/src/mutation/model.js.map +1 -0
- package/dist/src/mutation/mutation-engine.d.ts +80 -0
- package/dist/src/mutation/mutation-engine.d.ts.map +1 -0
- package/dist/src/mutation/mutation-engine.js +165 -0
- package/dist/src/mutation/mutation-engine.js.map +1 -0
- package/dist/src/mutation/mutation-engine.test.d.ts +2 -0
- package/dist/src/mutation/mutation-engine.test.d.ts.map +1 -0
- package/dist/src/mutation/mutation-engine.test.js +145 -0
- package/dist/src/mutation/mutation-engine.test.js.map +1 -0
- package/dist/src/mutation/mutation.d.ts +45 -0
- package/dist/src/mutation/mutation.d.ts.map +1 -0
- package/dist/src/mutation/mutation.js +29 -0
- package/dist/src/mutation/mutation.js.map +1 -0
- package/dist/src/mutation/operation.d.ts +13 -0
- package/dist/src/mutation/operation.d.ts.map +1 -0
- package/dist/src/mutation/operation.js +20 -0
- package/dist/src/mutation/operation.js.map +1 -0
- package/dist/src/mutation/scalar.d.ts +11 -0
- package/dist/src/mutation/scalar.d.ts.map +1 -0
- package/dist/src/mutation/scalar.js +17 -0
- package/dist/src/mutation/scalar.js.map +1 -0
- package/dist/src/mutation/simple-mutation-engine.d.ts +8 -0
- package/dist/src/mutation/simple-mutation-engine.d.ts.map +1 -0
- package/dist/src/mutation/simple-mutation-engine.js +11 -0
- package/dist/src/mutation/simple-mutation-engine.js.map +1 -0
- package/dist/src/mutation/union-variant.d.ts +10 -0
- package/dist/src/mutation/union-variant.d.ts.map +1 -0
- package/dist/src/mutation/union-variant.js +12 -0
- package/dist/src/mutation/union-variant.js.map +1 -0
- package/dist/src/mutation/union.d.ts +11 -0
- package/dist/src/mutation/union.d.ts.map +1 -0
- package/dist/src/mutation/union.js +18 -0
- package/dist/src/mutation/union.js.map +1 -0
- package/dist/src/mutation-node/enum-member.d.ts +7 -0
- package/dist/src/mutation-node/enum-member.d.ts.map +1 -0
- package/dist/src/mutation-node/enum-member.js +6 -0
- package/dist/src/mutation-node/enum-member.js.map +1 -0
- package/dist/src/mutation-node/enum-member.test.d.ts +2 -0
- package/dist/src/mutation-node/enum-member.test.d.ts.map +1 -0
- package/dist/src/mutation-node/enum-member.test.js +25 -0
- package/dist/src/mutation-node/enum-member.test.js.map +1 -0
- package/dist/src/mutation-node/enum.d.ts +8 -0
- package/dist/src/mutation-node/enum.d.ts.map +1 -0
- package/dist/src/mutation-node/enum.js +30 -0
- package/dist/src/mutation-node/enum.js.map +1 -0
- package/dist/src/mutation-node/enum.test.d.ts +2 -0
- package/dist/src/mutation-node/enum.test.d.ts.map +1 -0
- package/dist/src/mutation-node/enum.test.js +25 -0
- package/dist/src/mutation-node/enum.test.js.map +1 -0
- package/dist/src/mutation-node/factory.d.ts +17 -0
- package/dist/src/mutation-node/factory.d.ts.map +1 -0
- package/dist/src/mutation-node/factory.js +45 -0
- package/dist/src/mutation-node/factory.js.map +1 -0
- package/dist/src/mutation-node/index.d.ts +15 -0
- package/dist/src/mutation-node/index.d.ts.map +1 -0
- package/dist/src/mutation-node/index.js +16 -0
- package/dist/src/mutation-node/index.js.map +1 -0
- package/dist/src/mutation-node/interface.d.ts +8 -0
- package/dist/src/mutation-node/interface.d.ts.map +1 -0
- package/dist/src/mutation-node/interface.js +30 -0
- package/dist/src/mutation-node/interface.js.map +1 -0
- package/dist/src/mutation-node/intrinsic.d.ts +7 -0
- package/dist/src/mutation-node/intrinsic.d.ts.map +1 -0
- package/dist/src/mutation-node/intrinsic.js +6 -0
- package/dist/src/mutation-node/intrinsic.js.map +1 -0
- package/dist/src/mutation-node/literal.d.ts +7 -0
- package/dist/src/mutation-node/literal.d.ts.map +1 -0
- package/dist/src/mutation-node/literal.js +6 -0
- package/dist/src/mutation-node/literal.js.map +1 -0
- package/dist/src/mutation-node/model-property.d.ts +10 -0
- package/dist/src/mutation-node/model-property.d.ts.map +1 -0
- package/dist/src/mutation-node/model-property.js +48 -0
- package/dist/src/mutation-node/model-property.js.map +1 -0
- package/dist/src/mutation-node/model-property.test.d.ts +2 -0
- package/dist/src/mutation-node/model-property.test.d.ts.map +1 -0
- package/dist/src/mutation-node/model-property.test.js +108 -0
- package/dist/src/mutation-node/model-property.test.js.map +1 -0
- package/dist/src/mutation-node/model.d.ts +10 -0
- package/dist/src/mutation-node/model.d.ts.map +1 -0
- package/dist/src/mutation-node/model.js +82 -0
- package/dist/src/mutation-node/model.js.map +1 -0
- package/dist/src/mutation-node/model.test.d.ts +2 -0
- package/dist/src/mutation-node/model.test.d.ts.map +1 -0
- package/dist/src/mutation-node/model.test.js +133 -0
- package/dist/src/mutation-node/model.test.js.map +1 -0
- package/dist/src/mutation-node/mutation-edge.d.ts +18 -0
- package/dist/src/mutation-node/mutation-edge.d.ts.map +1 -0
- package/dist/src/mutation-node/mutation-edge.js +31 -0
- package/dist/src/mutation-node/mutation-edge.js.map +1 -0
- package/dist/src/mutation-node/mutation-node.d.ts +29 -0
- package/dist/src/mutation-node/mutation-node.d.ts.map +1 -0
- package/dist/src/mutation-node/mutation-node.js +82 -0
- package/dist/src/mutation-node/mutation-node.js.map +1 -0
- package/dist/src/mutation-node/mutation-node.test.d.ts +2 -0
- package/dist/src/mutation-node/mutation-node.test.d.ts.map +1 -0
- package/dist/src/mutation-node/mutation-node.test.js +88 -0
- package/dist/src/mutation-node/mutation-node.test.js.map +1 -0
- package/dist/src/mutation-node/mutation-subgraph.d.ts +17 -0
- package/dist/src/mutation-node/mutation-subgraph.d.ts.map +1 -0
- package/dist/src/mutation-node/mutation-subgraph.js +45 -0
- package/dist/src/mutation-node/mutation-subgraph.js.map +1 -0
- package/dist/src/mutation-node/operation.d.ts +9 -0
- package/dist/src/mutation-node/operation.d.ts.map +1 -0
- package/dist/src/mutation-node/operation.js +44 -0
- package/dist/src/mutation-node/operation.js.map +1 -0
- package/dist/src/mutation-node/scalar.d.ts +8 -0
- package/dist/src/mutation-node/scalar.d.ts.map +1 -0
- package/dist/src/mutation-node/scalar.js +28 -0
- package/dist/src/mutation-node/scalar.js.map +1 -0
- package/dist/src/mutation-node/scalar.test.d.ts +2 -0
- package/dist/src/mutation-node/scalar.test.d.ts.map +1 -0
- package/dist/src/mutation-node/scalar.test.js +40 -0
- package/dist/src/mutation-node/scalar.test.js.map +1 -0
- package/dist/src/mutation-node/tuple.d.ts +9 -0
- package/dist/src/mutation-node/tuple.d.ts.map +1 -0
- package/dist/src/mutation-node/tuple.js +32 -0
- package/dist/src/mutation-node/tuple.js.map +1 -0
- package/dist/src/mutation-node/tuple.test.d.ts +2 -0
- package/dist/src/mutation-node/tuple.test.d.ts.map +1 -0
- package/dist/src/mutation-node/tuple.test.js +29 -0
- package/dist/src/mutation-node/tuple.test.js.map +1 -0
- package/dist/src/mutation-node/union-variant.d.ts +8 -0
- package/dist/src/mutation-node/union-variant.d.ts.map +1 -0
- package/dist/src/mutation-node/union-variant.js +23 -0
- package/dist/src/mutation-node/union-variant.js.map +1 -0
- package/dist/src/mutation-node/union-variant.test.d.ts +2 -0
- package/dist/src/mutation-node/union-variant.test.d.ts.map +1 -0
- package/dist/src/mutation-node/union-variant.test.js +27 -0
- package/dist/src/mutation-node/union-variant.test.js.map +1 -0
- package/dist/src/mutation-node/union.d.ts +8 -0
- package/dist/src/mutation-node/union.d.ts.map +1 -0
- package/dist/src/mutation-node/union.js +30 -0
- package/dist/src/mutation-node/union.js.map +1 -0
- package/dist/src/mutation-node/union.test.d.ts +2 -0
- package/dist/src/mutation-node/union.test.d.ts.map +1 -0
- package/dist/src/mutation-node/union.test.js +25 -0
- package/dist/src/mutation-node/union.test.js.map +1 -0
- package/dist/test/test-host.d.ts +2 -0
- package/dist/test/test-host.d.ts.map +1 -0
- package/dist/test/test-host.js +6 -0
- package/dist/test/test-host.js.map +1 -0
- package/dist/test/utils.d.ts +4 -0
- package/dist/test/utils.d.ts.map +1 -0
- package/dist/test/utils.js +8 -0
- package/dist/test/utils.js.map +1 -0
- package/package.json +40 -0
- package/package.json.bak +42 -0
- package/readme.md +339 -0
- package/src/index.ts +4 -0
- package/src/mutation/index.ts +12 -0
- package/src/mutation/interface.ts +38 -0
- package/src/mutation/intrinsic.ts +23 -0
- package/src/mutation/literal.ts +29 -0
- package/src/mutation/model-property.ts +35 -0
- package/src/mutation/model.ts +58 -0
- package/src/mutation/mutation-engine.test.ts +202 -0
- package/src/mutation/mutation-engine.ts +288 -0
- package/src/mutation/mutation.ts +90 -0
- package/src/mutation/operation.ts +40 -0
- package/src/mutation/scalar.ts +36 -0
- package/src/mutation/simple-mutation-engine.ts +21 -0
- package/src/mutation/union-variant.ts +30 -0
- package/src/mutation/union.ts +39 -0
- package/src/mutation-node/enum-member.test.ts +26 -0
- package/src/mutation-node/enum-member.ts +8 -0
- package/src/mutation-node/enum.test.ts +26 -0
- package/src/mutation-node/enum.ts +33 -0
- package/src/mutation-node/factory.ts +95 -0
- package/src/mutation-node/index.ts +16 -0
- package/src/mutation-node/interface.ts +33 -0
- package/src/mutation-node/intrinsic.ts +8 -0
- package/src/mutation-node/literal.ts +10 -0
- package/src/mutation-node/model-property.test.ts +136 -0
- package/src/mutation-node/model-property.ts +53 -0
- package/src/mutation-node/model.test.ts +151 -0
- package/src/mutation-node/model.ts +89 -0
- package/src/mutation-node/mutation-edge.ts +43 -0
- package/src/mutation-node/mutation-node.test.ts +94 -0
- package/src/mutation-node/mutation-node.ts +110 -0
- package/src/mutation-node/mutation-subgraph.ts +59 -0
- package/src/mutation-node/operation.ts +49 -0
- package/src/mutation-node/scalar.test.ts +44 -0
- package/src/mutation-node/scalar.ts +32 -0
- package/src/mutation-node/tuple.test.ts +30 -0
- package/src/mutation-node/tuple.ts +35 -0
- package/src/mutation-node/union-variant.test.ts +28 -0
- package/src/mutation-node/union-variant.ts +26 -0
- package/src/mutation-node/union.test.ts +26 -0
- package/src/mutation-node/union.ts +33 -0
- package/test/test-host.ts +6 -0
- package/test/utils.ts +9 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +4 -0
package/readme.md
ADDED
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
# Mutator Framework
|
|
2
|
+
|
|
3
|
+
** WARNING: THIS PACKAGE IS EXPERIMENTAL AND WILL CHANGE **
|
|
4
|
+
|
|
5
|
+
This package provides utilities for building mutations of the TypeSpec type
|
|
6
|
+
graph. Mutations are modifications to the original type graph that live in a
|
|
7
|
+
parallel type graph and contain additional metadata relevant to consumers of
|
|
8
|
+
those mutated types.
|
|
9
|
+
|
|
10
|
+
At a high level you:
|
|
11
|
+
|
|
12
|
+
- Create mutation classes that control how each TypeSpec type (models,
|
|
13
|
+
properties, unions, scalars, literals, operations, etc.) is traversed and
|
|
14
|
+
transformed.
|
|
15
|
+
- Use strongly-typed helper APIs on mutation nodes to mutate into new types or
|
|
16
|
+
traverse to related nodes.
|
|
17
|
+
- Instantiate a `MutationEngine` subtype (e.g. `SimpleMutationEngine`) with the
|
|
18
|
+
`Typekit` from the TypeSpec program you want to mutate.
|
|
19
|
+
|
|
20
|
+
The key APIs are:
|
|
21
|
+
|
|
22
|
+
- `MutationEngine` – orchestrates creation, caching, and traversal of mutation nodes.
|
|
23
|
+
- `SimpleMutationEngine` – a convenience engine that exposes a single default mutation subgraph.
|
|
24
|
+
- `MutationOptions` – lets you parameterize a mutation run and cache its results.
|
|
25
|
+
- `ModelMutation`, `ModelPropertyMutation`, `UnionMutation`, `UnionVariantMutation`,
|
|
26
|
+
`OperationMutation`, etc. – base classes for crafting custom mutations per TypeSpec kind.
|
|
27
|
+
- `MutationSubgraph` – creates an isolated graph of mutated types that can be inspected or
|
|
28
|
+
retrieved later.
|
|
29
|
+
- `ModelMutationNode`, `ModelPropertyMutationNode`, `UnionMutationNode`, etc. -
|
|
30
|
+
nodes which represent the possible mutation of a particular type graph type.
|
|
31
|
+
|
|
32
|
+
## Getting Started
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import type { Model, Program } from "@typespec/compiler";
|
|
36
|
+
import { $, type Typekit } from "@typespec/compiler/typekit";
|
|
37
|
+
import { SimpleMutationEngine } from "@typespec/mutator-framework";
|
|
38
|
+
|
|
39
|
+
// Create a typekit for the program
|
|
40
|
+
const tk: Typekit = $(program);
|
|
41
|
+
|
|
42
|
+
// Instantiate an engine for running the mutations.
|
|
43
|
+
// Might be the built-in SimpleMutationEngine, or a
|
|
44
|
+
// custom `MutationEngine` subclass.
|
|
45
|
+
const engine = new SimpleMutationEngine(tk, {
|
|
46
|
+
Model: RenameModelMutation, // defined later in this guide
|
|
47
|
+
});
|
|
48
|
+
const renamedMutation = engine.mutate(someType);
|
|
49
|
+
const mutatedType = renamedMutation.mutatedType;
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Defining Custom Mutation Options
|
|
53
|
+
|
|
54
|
+
Options derive from `MutationOptions`. They let you tune mutations (for example,
|
|
55
|
+
to switch on features or change naming rules) and provide a cache key used to
|
|
56
|
+
memoize results. Extend the class and override `cacheKey()` to represent your
|
|
57
|
+
configuration.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
// rename-mutations.ts
|
|
61
|
+
import { MutationOptions } from "@typespec/mutator-framework";
|
|
62
|
+
|
|
63
|
+
export class RenameMutationOptions extends MutationOptions {
|
|
64
|
+
constructor(
|
|
65
|
+
readonly prefix: string,
|
|
66
|
+
readonly suffix: string,
|
|
67
|
+
) {
|
|
68
|
+
super();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override cacheKey() {
|
|
72
|
+
return `${this.prefix}-${this.suffix}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Creating a Custom Mutation Engine
|
|
78
|
+
|
|
79
|
+
`MutationEngine` is responsible for coordinating mutation nodes and subgraphs. Supply constructors
|
|
80
|
+
for each type kind you want to customize. Anything you omit defaults to the base implementations
|
|
81
|
+
(`ModelMutation`, `ModelPropertyMutation`, etc.).
|
|
82
|
+
|
|
83
|
+
You can also register additional mutation subgraphs. Each subgraph represents an isolated view of
|
|
84
|
+
the mutated graph. This is useful when you want to compare alternative transformations side by side
|
|
85
|
+
(for example, with different naming conventions).
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
// rename-mutations.ts
|
|
89
|
+
import { MutationEngine, type MutationSubgraph } from "@typespec/mutator-framework";
|
|
90
|
+
import type { Typekit } from "@typespec/compiler/typekit";
|
|
91
|
+
|
|
92
|
+
export class RenameMutationEngine extends MutationEngine<{ Model: RenameModelMutation }> {
|
|
93
|
+
constructor(typekit: Typekit) {
|
|
94
|
+
super(typekit, { Model: RenameModelMutation });
|
|
95
|
+
this.registerSubgraph("prefix");
|
|
96
|
+
this.registerSubgraph("suffix");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getPrefix(options: RenameMutationOptions): MutationSubgraph {
|
|
100
|
+
return this.getMutationSubgraph(options, "prefix");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getSuffix(options: RenameMutationOptions): MutationSubgraph {
|
|
104
|
+
return this.getMutationSubgraph(options, "suffix");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The base `MutationEngine` does not define a default subgraph. If you just need a single mutated
|
|
110
|
+
view, use the `SimpleMutationEngine`. It auto-registers a `"subgraph"` and wires
|
|
111
|
+
`getDefaultMutationSubgraph` for you:
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { SimpleMutationEngine } from "@typespec/mutator-framework";
|
|
115
|
+
|
|
116
|
+
const engine = new SimpleMutationEngine(tk, {
|
|
117
|
+
Model: RenameModelMutation,
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Writing Mutation Classes
|
|
122
|
+
|
|
123
|
+
Mutation classes derive from the base classes included in the framework. Each class receives the
|
|
124
|
+
engine, the source TypeSpec type, the list of reference members that referenced that type (if any),
|
|
125
|
+
and the options. Override `mutate()` to perform your transformations.
|
|
126
|
+
|
|
127
|
+
Inside `mutate()` you can:
|
|
128
|
+
|
|
129
|
+
- Traverse connected types via `this.engine.mutate(...)` or `this.engine.mutateReference(...)`.
|
|
130
|
+
- Retrieve or create mutation nodes with `this.getMutationNode()`.
|
|
131
|
+
- Mutate values using `this.mutateType()` or `engine.mutateType(...)`.
|
|
132
|
+
- Switch subgraphs by calling `this.engine.getMutationSubgraph(...)`.
|
|
133
|
+
|
|
134
|
+
### Example: Renaming Models in Multiple Subgraphs
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// rename-mutations.ts
|
|
138
|
+
import { ModelMutation } from "@typespec/mutator-framework";
|
|
139
|
+
|
|
140
|
+
export class RenameModelMutation extends ModelMutation<
|
|
141
|
+
RenameMutationOptions,
|
|
142
|
+
{ Model: RenameModelMutation },
|
|
143
|
+
RenameMutationEngine
|
|
144
|
+
> {
|
|
145
|
+
get withPrefix() {
|
|
146
|
+
return this.getMutatedType(this.engine.getPrefix(this.options));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
get withSuffix() {
|
|
150
|
+
return this.getMutatedType(this.engine.getSuffix(this.options));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
mutate() {
|
|
154
|
+
if ("name" in this.sourceType && typeof this.sourceType.name === "string") {
|
|
155
|
+
this.mutateType(this.engine.getPrefix(this.options), (model) => {
|
|
156
|
+
model.name = `${this.options.prefix}${model.name}`;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
this.mutateType(this.engine.getSuffix(this.options), (model) => {
|
|
160
|
+
model.name = `${model.name}${this.options.suffix}`;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Always call super.mutate() if you still want the base implementation
|
|
165
|
+
// to traverse properties, base models, indexers, etc. with the same options.
|
|
166
|
+
super.mutate();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Running the Mutation
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import type { Model, Program } from "@typespec/compiler";
|
|
175
|
+
import { $ } from "@typespec/compiler/typekit";
|
|
176
|
+
import { RenameMutationEngine, RenameMutationOptions } from "./rename-mutations.js";
|
|
177
|
+
|
|
178
|
+
export function applyRename(program: Program, fooModel: Model) {
|
|
179
|
+
const engine = new RenameMutationEngine($(program));
|
|
180
|
+
const options = new RenameMutationOptions("Pre", "Suf");
|
|
181
|
+
|
|
182
|
+
const fooMutation = engine.mutate(fooModel, options);
|
|
183
|
+
const prefixFoo = fooMutation.withPrefix;
|
|
184
|
+
const suffixFoo = fooMutation.withSuffix;
|
|
185
|
+
|
|
186
|
+
const propMutation = fooMutation.properties.get("prop")!;
|
|
187
|
+
const barMutation = propMutation.type as RenameModelMutation;
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
prefixFoo,
|
|
191
|
+
suffixFoo,
|
|
192
|
+
barWithSuffix: barMutation.withSuffix,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Example: Mutating Referenced Types
|
|
198
|
+
|
|
199
|
+
`ModelPropertyMutation` exposes `mutateReference` and `replaceReferencedType`
|
|
200
|
+
helpers that make it easy to mutate types referenced by properties. When
|
|
201
|
+
mutating references, a clone of the referenced type is made, so changes to not
|
|
202
|
+
affect the referenced type. This enables references to reference a unique type
|
|
203
|
+
with mutations that are particular to that type when referenced in that context.
|
|
204
|
+
For example, if the model property contains a decorator that affects the
|
|
205
|
+
mutation of a referenced scalar.
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
import type { Model, Program } from "@typespec/compiler";
|
|
209
|
+
import { $ } from "@typespec/compiler/typekit";
|
|
210
|
+
import {
|
|
211
|
+
ModelMutation,
|
|
212
|
+
ModelPropertyMutation,
|
|
213
|
+
MutationOptions,
|
|
214
|
+
SimpleMutationEngine,
|
|
215
|
+
} from "@typespec/mutator-framework";
|
|
216
|
+
|
|
217
|
+
class UnionifyOptions extends MutationOptions {}
|
|
218
|
+
|
|
219
|
+
class UnionifyModel extends ModelMutation<
|
|
220
|
+
UnionifyOptions,
|
|
221
|
+
UnionifyMutations,
|
|
222
|
+
SimpleMutationEngine<UnionifyMutations>
|
|
223
|
+
> {
|
|
224
|
+
get unionified() {
|
|
225
|
+
return this.getMutatedType();
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
class UnionifyProperty extends ModelPropertyMutation<
|
|
230
|
+
UnionifyOptions,
|
|
231
|
+
UnionifyMutations,
|
|
232
|
+
SimpleMutationEngine<UnionifyMutations>
|
|
233
|
+
> {
|
|
234
|
+
get unionified() {
|
|
235
|
+
return this.getMutatedType();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
mutate() {
|
|
239
|
+
if (!this.engine.$.union.is(this.sourceType.type)) {
|
|
240
|
+
const unionVariant = this.engine.$.unionVariant.create({ type: this.sourceType.type });
|
|
241
|
+
const fallbackVariant = this.engine.$.unionVariant.create({
|
|
242
|
+
type: this.engine.$.builtin.string,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const unionType = this.engine.$.union.create({ variants: [unionVariant, fallbackVariant] });
|
|
246
|
+
|
|
247
|
+
this.type = this.replaceReferencedType(
|
|
248
|
+
this.engine.getDefaultMutationSubgraph(this.options),
|
|
249
|
+
unionType,
|
|
250
|
+
);
|
|
251
|
+
} else {
|
|
252
|
+
super.mutate();
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
interface UnionifyMutations {
|
|
258
|
+
Model: UnionifyModel;
|
|
259
|
+
ModelProperty: UnionifyProperty;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export function createUnionifyEngine(program: Program) {
|
|
263
|
+
const tk = $(program);
|
|
264
|
+
return new SimpleMutationEngine(tk, {
|
|
265
|
+
Model: UnionifyModel,
|
|
266
|
+
ModelProperty: UnionifyProperty,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function unionifyModel(program: Program, fooModel: Model) {
|
|
271
|
+
const engine = createUnionifyEngine(program);
|
|
272
|
+
const fooMutation = engine.mutate(fooModel, new UnionifyOptions());
|
|
273
|
+
const propMutation = fooMutation.properties.get("prop")!;
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
property: propMutation.unionified,
|
|
277
|
+
model: fooMutation.unionified,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Core Mutation Base Classes
|
|
283
|
+
|
|
284
|
+
| Class | Source Type | Responsibilities |
|
|
285
|
+
| ----------------------- | ------------------------------ | ---------------------------------------------------------- |
|
|
286
|
+
| `ModelMutation` | `Model` | Traverses base models, properties, and indexers. |
|
|
287
|
+
| `ModelPropertyMutation` | `ModelProperty` | Mutates referenced types, exposes `replaceReferencedType`. |
|
|
288
|
+
| `UnionMutation` | `Union` | Iterates over variants and lazy-loads their mutations. |
|
|
289
|
+
| `UnionVariantMutation` | `UnionVariant` | Handles referenced variant types. |
|
|
290
|
+
| `ScalarMutation` | `Scalar` | Provides access to scalar definitions and projections. |
|
|
291
|
+
| `LiteralMutation` | string/number/boolean literals | Provides literal values and traversal control. |
|
|
292
|
+
| `OperationMutation` | `Operation` | Mutates parameters, return types, and decorators. |
|
|
293
|
+
| `InterfaceMutation` | `Interface` | Walks operations declared on the interface. |
|
|
294
|
+
| `IntrinsicMutation` | `Intrinsic` | Surfaces intrinsic TypeSpec types. |
|
|
295
|
+
|
|
296
|
+
Each class inherits from the foundational `Mutation` class, which provides
|
|
297
|
+
shared helpers for mutated types (`getMutatedType`) and nodes
|
|
298
|
+
(`getMutationNode`). Override them or add convenience getters/setters to tailor
|
|
299
|
+
the experience for your consumers.
|
|
300
|
+
|
|
301
|
+
## Working with Mutation Subgraphs
|
|
302
|
+
|
|
303
|
+
The engine builds mutation nodes inside a `MutationSubgraph`. Each subgraph
|
|
304
|
+
captures a set of mutations that share the same options and transformation
|
|
305
|
+
logic.
|
|
306
|
+
|
|
307
|
+
```ts
|
|
308
|
+
const prefixGraph = engine.getPrefix(renameOptions);
|
|
309
|
+
const prefixFoo = engine.getMutatedType(prefixGraph, Foo);
|
|
310
|
+
|
|
311
|
+
const suffixGraph = engine.getSuffix(renameOptions);
|
|
312
|
+
const suffixFoo = engine.getMutatedType(suffixGraph, Foo);
|
|
313
|
+
|
|
314
|
+
console.log(prefixFoo.name, suffixFoo.name);
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
When you call `engine.mutate(type, options)` the engine automatically creates mutation nodes in all
|
|
318
|
+
registered subgraphs for the provided options. Subsequent calls reuse the cached nodes, so you can
|
|
319
|
+
freely navigate the mutation graph without re-running your transformation logic.
|
|
320
|
+
|
|
321
|
+
## Tips for Building Mutations
|
|
322
|
+
|
|
323
|
+
- **Always call `super.mutate()`** when you want the default traversal logic after your custom
|
|
324
|
+
changes. Skipping it gives you full control, but you must handle traversal yourself.
|
|
325
|
+
- **Use `MutationOptions` subclasses** whenever your mutation behavior depends on input
|
|
326
|
+
configuration. Return a stable `cacheKey()` to reuse work.
|
|
327
|
+
- **Inspect `referenceTypes`** to learn which `ModelProperty` or `UnionVariant` led to the current
|
|
328
|
+
mutation node. This helps you emit diagnostics or perform context-sensitive logic.
|
|
329
|
+
- **Mutate lazily**. Mutations only run once per `(type, options)` pair. If you expose getters that
|
|
330
|
+
trigger work, they should go through `engine.mutate(...)` so the cache stays consistent.
|
|
331
|
+
- **Prefer `SimpleMutationEngine`** unless you need named subgraphs. You can graduate to a custom
|
|
332
|
+
engine later.
|
|
333
|
+
|
|
334
|
+
## Additional Resources
|
|
335
|
+
|
|
336
|
+
- Browse the rest of the files under `packages/mutator-framework/src/mutation` to see the built-in
|
|
337
|
+
mutation implementations.
|
|
338
|
+
- The unit tests in `mutation-engine.test.ts` demonstrate more end-to-end usage patterns, including
|
|
339
|
+
multi-subgraph mutations and reference replacements.
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from "./interface.js";
|
|
2
|
+
export * from "./intrinsic.js";
|
|
3
|
+
export * from "./literal.js";
|
|
4
|
+
export * from "./model-property.js";
|
|
5
|
+
export * from "./model.js";
|
|
6
|
+
export * from "./mutation-engine.js";
|
|
7
|
+
export * from "./mutation.js";
|
|
8
|
+
export * from "./operation.js";
|
|
9
|
+
export * from "./scalar.js";
|
|
10
|
+
export * from "./simple-mutation-engine.js";
|
|
11
|
+
export * from "./union-variant.js";
|
|
12
|
+
export * from "./union.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Interface, MemberType } 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 InterfaceMutation<
|
|
11
|
+
TOptions extends MutationOptions,
|
|
12
|
+
TCustomMutations extends CustomMutationClasses,
|
|
13
|
+
> extends Mutation<Interface, TCustomMutations, TOptions> {
|
|
14
|
+
readonly kind = "Interface";
|
|
15
|
+
operations: Map<string, MutationFor<TCustomMutations, "Operation">> = new Map();
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
engine: MutationEngine<TCustomMutations>,
|
|
19
|
+
sourceType: Interface,
|
|
20
|
+
referenceTypes: MemberType[] = [],
|
|
21
|
+
options: TOptions,
|
|
22
|
+
) {
|
|
23
|
+
super(engine, sourceType, referenceTypes, options);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
protected mutateOperations() {
|
|
27
|
+
for (const op of this.sourceType.operations.values()) {
|
|
28
|
+
this.operations.set(
|
|
29
|
+
op.name,
|
|
30
|
+
this.engine.mutate(op, this.options) as MutationFor<TCustomMutations, "Operation">,
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
mutate() {
|
|
36
|
+
this.mutateOperations();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IntrinsicType, MemberType } from "@typespec/compiler";
|
|
2
|
+
import type { CustomMutationClasses, MutationEngine, MutationOptions } from "./mutation-engine.js";
|
|
3
|
+
import { Mutation } from "./mutation.js";
|
|
4
|
+
|
|
5
|
+
export class IntrinsicMutation<
|
|
6
|
+
TOptions extends MutationOptions,
|
|
7
|
+
TCustomMutations extends CustomMutationClasses,
|
|
8
|
+
TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
|
|
9
|
+
> extends Mutation<IntrinsicType, TCustomMutations, TOptions, TEngine> {
|
|
10
|
+
readonly kind = "Intrinsic";
|
|
11
|
+
constructor(
|
|
12
|
+
engine: TEngine,
|
|
13
|
+
sourceType: IntrinsicType,
|
|
14
|
+
referenceTypes: MemberType[] = [],
|
|
15
|
+
options: TOptions,
|
|
16
|
+
) {
|
|
17
|
+
super(engine, sourceType, referenceTypes, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
mutate() {
|
|
21
|
+
// No mutations needed for intrinsic types
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { BooleanLiteral, MemberType, NumericLiteral, StringLiteral } from "@typespec/compiler";
|
|
2
|
+
import type { CustomMutationClasses, MutationEngine, MutationOptions } from "./mutation-engine.js";
|
|
3
|
+
import { Mutation } from "./mutation.js";
|
|
4
|
+
|
|
5
|
+
export class LiteralMutation<
|
|
6
|
+
TOptions extends MutationOptions,
|
|
7
|
+
TCustomMutations extends CustomMutationClasses,
|
|
8
|
+
TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
|
|
9
|
+
> extends Mutation<
|
|
10
|
+
StringLiteral | NumericLiteral | BooleanLiteral,
|
|
11
|
+
TCustomMutations,
|
|
12
|
+
TOptions,
|
|
13
|
+
TEngine
|
|
14
|
+
> {
|
|
15
|
+
readonly kind = "Literal";
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
engine: TEngine,
|
|
19
|
+
sourceType: StringLiteral | NumericLiteral | BooleanLiteral,
|
|
20
|
+
referenceTypes: MemberType[] = [],
|
|
21
|
+
options: TOptions,
|
|
22
|
+
) {
|
|
23
|
+
super(engine, sourceType, referenceTypes, options);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
mutate() {
|
|
27
|
+
// No mutations needed for literal types
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ModelProperty, Type } from "@typespec/compiler";
|
|
2
|
+
import type { MutationSubgraph } from "../mutation-node/mutation-subgraph.js";
|
|
3
|
+
import type {
|
|
4
|
+
CustomMutationClasses,
|
|
5
|
+
MutationEngine,
|
|
6
|
+
MutationFor,
|
|
7
|
+
MutationOptions,
|
|
8
|
+
} from "./mutation-engine.js";
|
|
9
|
+
import { Mutation } from "./mutation.js";
|
|
10
|
+
|
|
11
|
+
export class ModelPropertyMutation<
|
|
12
|
+
TOptions extends MutationOptions,
|
|
13
|
+
TCustomMutations extends CustomMutationClasses,
|
|
14
|
+
TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
|
|
15
|
+
> extends Mutation<ModelProperty, TCustomMutations, TOptions, TEngine> {
|
|
16
|
+
readonly kind = "ModelProperty";
|
|
17
|
+
type!: MutationFor<TCustomMutations, Type["kind"]>;
|
|
18
|
+
|
|
19
|
+
mutate() {
|
|
20
|
+
this.type = this.engine.mutateReference(this.sourceType, this.options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
getReferenceMutationNode(
|
|
24
|
+
subgraph: MutationSubgraph = this.engine.getDefaultMutationSubgraph(this.options),
|
|
25
|
+
) {
|
|
26
|
+
return subgraph.getReferenceNode(this.sourceType);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
replaceReferencedType(subgraph: MutationSubgraph, newType: Type) {
|
|
30
|
+
// First, update the mutation node
|
|
31
|
+
subgraph.getReferenceNode(this.sourceType).replace(newType);
|
|
32
|
+
// then return a new reference mutation for the new type
|
|
33
|
+
return this.engine.mutateReference(this.sourceType, newType, this.options);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { MemberType, Model, Type } 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 ModelMutation<
|
|
11
|
+
TOptions extends MutationOptions,
|
|
12
|
+
TCustomMutations extends CustomMutationClasses,
|
|
13
|
+
TEngine extends MutationEngine<TCustomMutations> = MutationEngine<TCustomMutations>,
|
|
14
|
+
> extends Mutation<Model, TCustomMutations, TOptions, TEngine> {
|
|
15
|
+
readonly kind = "Model";
|
|
16
|
+
baseModel?: MutationFor<TCustomMutations, "Model">;
|
|
17
|
+
properties: Map<string, MutationFor<TCustomMutations, "ModelProperty">> = new Map();
|
|
18
|
+
indexer?: {
|
|
19
|
+
key: MutationFor<TCustomMutations, "Scalar">;
|
|
20
|
+
value: MutationFor<TCustomMutations, Type["kind"]>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
constructor(
|
|
24
|
+
engine: TEngine,
|
|
25
|
+
sourceType: Model,
|
|
26
|
+
referenceTypes: MemberType[] = [],
|
|
27
|
+
options: TOptions,
|
|
28
|
+
) {
|
|
29
|
+
super(engine, sourceType, referenceTypes, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
protected mutateBaseModel() {
|
|
33
|
+
if (this.sourceType.baseModel) {
|
|
34
|
+
this.baseModel = this.engine.mutate(this.sourceType.baseModel, this.options);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
protected mutateProperties() {
|
|
39
|
+
for (const prop of this.sourceType.properties.values()) {
|
|
40
|
+
this.properties.set(prop.name, this.engine.mutate(prop, this.options));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected mutateIndexer() {
|
|
45
|
+
if (this.sourceType.indexer) {
|
|
46
|
+
this.indexer = {
|
|
47
|
+
key: this.engine.mutate(this.sourceType.indexer.key, this.options),
|
|
48
|
+
value: this.engine.mutate(this.sourceType.indexer.value, this.options),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
mutate() {
|
|
54
|
+
this.mutateBaseModel();
|
|
55
|
+
this.mutateProperties();
|
|
56
|
+
this.mutateIndexer();
|
|
57
|
+
}
|
|
58
|
+
}
|