Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/mutator-framework"
---

Clarify mutation strategy identity, caching, and single-node engine contracts.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/mutator-framework"
---

Document mutation node identity, lifecycle propagation, and edge callback ownership.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/mutator-framework"
---

Clarify model and operation traversal, ownership edges, and output reference updates.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: internal
packages:
- "@typespec/mutator-framework"
---

Document scalar, enum, union, literal, intrinsic, and tuple mutation contracts.
8 changes: 8 additions & 0 deletions packages/mutator-framework/src/mutation-node/enum-member.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import type { EnumMutationNode } from "./enum.js";
import { HalfEdge } from "./mutation-edge.js";
import { MutationNode } from "./mutation-node.js";

/** Tracks an enum member's output and its explicitly connected containing enum. */
export class EnumMemberMutationNode extends MutationNode<EnumMember> {
/** Selects enum-member ownership connections. */
readonly kind = "EnumMember";

/**
* Starts the containing-enum back-reference.
* Enum mutation updates the output's owner; enum deletion or replacement
* deletes this member rather than moving it to a different enum.
*/
startEnumEdge() {
return new HalfEdge<EnumMember, Enum>(this, {
onTailMutation: ({ tail }) => {
Expand All @@ -21,6 +28,7 @@ export class EnumMemberMutationNode extends MutationNode<EnumMember> {
});
}

/** Completes {@link startEnumEdge}; normally established by an enum's member connection. */
connectEnum(enumNode: EnumMutationNode) {
this.startEnumEdge().setTail(enumNode);
}
Expand Down
8 changes: 8 additions & 0 deletions packages/mutator-framework/src/mutation-node/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ import type { Enum, EnumMember } from "@typespec/compiler";
import { HalfEdge } from "./mutation-edge.js";
import { MutationNode } from "./mutation-node.js";

/** Maintains an enum's member map through explicit member connections. */
export class EnumMutationNode extends MutationNode<Enum> {
/** Selects enum-specific member connections. */
readonly kind = "Enum";

/**
* Starts a member reference and establishes the member's enum back-reference.
* Renames replace source-name entries, deletion removes them, and replacements
* must remain enum members. The enum is cloned when its map needs updating.
*/
startMemberEdge() {
return new HalfEdge<Enum, EnumMember>(this, {
onTailCreation: ({ tail }) => {
Expand Down Expand Up @@ -33,6 +40,7 @@ export class EnumMutationNode extends MutationNode<Enum> {
});
}

/** Connects a member and its enum back-reference via {@link startMemberEdge}. */
connectMember(memberNode: MutationNode<EnumMember>) {
this.startMemberEdge().setTail(memberNode);
}
Expand Down
6 changes: 6 additions & 0 deletions packages/mutator-framework/src/mutation-node/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ import { TupleMutationNode } from "./tuple.js";
import { UnionVariantMutationNode } from "./union-variant.js";
import { UnionMutationNode } from "./union.js";

/**
* Constructs a fresh node selected by the source type's kind, without caching.
* Use {@link MutationEngine.getMutationNode} to share nodes within an engine.
* Throws for unsupported kinds; creating a node does not traverse its references.
*/
export function mutationNodeFor<T extends Type>(
engine: MutationEngine<any>,
sourceType: T,
Expand Down Expand Up @@ -72,6 +77,7 @@ export function mutationNodeFor<T extends Type>(
}
}

/** Maps supported compiler type shapes to their corresponding concrete node classes. */
export type MutationNodeForType<T extends Type> = T extends Model
? ModelMutationNode
: T extends Interface
Expand Down
15 changes: 14 additions & 1 deletion packages/mutator-framework/src/mutation-node/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@ import type { Interface, Operation } from "@typespec/compiler";
import { HalfEdge } from "./mutation-edge.js";
import { MutationNode } from "./mutation-node.js";

/** Connection-key configuration shape; current node connection methods do not consume it. */
export interface InterfaceConnectOptions {
/** Mutation keys for operation nodes, keyed by operation name. Defaults to this node's key for all. */
/** Keys designated by source operation name; callers must select those nodes explicitly. */
operations?: Record<string, string>;
}

/**
* Maintains an interface's operation map through explicit connections.
* Operation callbacks write the current output map directly; call `mutate()`
* first when the map must belong to a clone rather than the source interface.
*/
export class InterfaceMutationNode extends MutationNode<Interface> {
/** Selects interface-specific operation connections. */
readonly kind = "Interface";

/**
* Starts an operation reference and establishes its interface back-reference.
* Renames replace source-name entries, deletion removes them, and replacement
* requires an operation. Map updates do not themselves clone this interface.
*/
startOperationEdge() {
return new HalfEdge<Interface, Operation>(this, {
onTailCreation: ({ tail }) => {
Expand All @@ -35,6 +47,7 @@ export class InterfaceMutationNode extends MutationNode<Interface> {
});
}

/** Connects an operation and its interface back-reference via {@link startOperationEdge}. */
connectOperation(opNode: MutationNode<Operation>) {
this.startOperationEdge().setTail(opNode);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/mutator-framework/src/mutation-node/intrinsic.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import type { IntrinsicType } from "@typespec/compiler";
import { MutationNode } from "./mutation-node.js";

/** Leaf node for compiler intrinsic outputs, with no child reference edges to establish. */
export class IntrinsicMutationNode extends MutationNode<IntrinsicType> {
/** Identifies intrinsic nodes independently of the particular intrinsic's name. */
readonly kind = "Intrinsic";

/** Completes the leaf's connection phase without creating edges; repeated calls do nothing. */
connect() {
if (this.connected) return;
this.connected = true;
Expand Down
3 changes: 3 additions & 0 deletions packages/mutator-framework/src/mutation-node/literal.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import type { BooleanLiteral, NumericLiteral, StringLiteral } from "@typespec/compiler";
import { MutationNode } from "./mutation-node.js";

/** Leaf node for literal outputs, with no child reference edges to establish. */
export class LiteralMutationNode extends MutationNode<
StringLiteral | NumericLiteral | BooleanLiteral
> {
/** Groups string, numeric, and boolean literal nodes under one connection kind. */
readonly kind = "Literal";

/** Completes the leaf's connection phase without creating edges; repeated calls do nothing. */
connect() {
if (this.connected) return;
this.connected = true;
Expand Down
17 changes: 16 additions & 1 deletion packages/mutator-framework/src/mutation-node/model-property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,22 @@ import { HalfEdge } from "./mutation-edge.js";
import { MutationNode } from "./mutation-node.js";
import { traceNode } from "./tracer.js";

/** Connection-key configuration shape; current node connection methods do not consume it. */
export interface ModelPropertyConnectOptions {
/** Mutation key for the property's type node. Defaults to this node's key. */
/** Key designated for the underlying type; callers must select that node explicitly. */
typeMutationKey?: string;
}

/** Maintains a property's underlying type and containing-model references through explicit edges. */
export class ModelPropertyMutationNode extends MutationNode<ModelProperty> {
/** Selects property-specific connections independently of a replacement node's kind. */
readonly kind = "ModelProperty";

/**
* Starts a type reference that follows mutations and replacements of any kind.
* Deletion substitutes `any`. Replacing this property with another property
* transfers the type connection to the replacement.
*/
startTypeEdge() {
return new HalfEdge<ModelProperty, Type>(this, {
onTailMutation: ({ tail }) => {
Expand Down Expand Up @@ -40,10 +48,16 @@ export class ModelPropertyMutationNode extends MutationNode<ModelProperty> {
});
}

/** Completes {@link startTypeEdge} with the chosen underlying type node. */
connectType(typeNode: MutationNode<Type>) {
this.startTypeEdge().setTail(typeNode);
}

/**
* Starts the containing-model back-reference.
* Model changes update the output's owner; model deletion or replacement
* deletes this property rather than moving it to a new owner.
*/
startModelEdge() {
return new HalfEdge<ModelProperty, Model>(this, {
onTailMutation: ({ tail }) => {
Expand All @@ -60,6 +74,7 @@ export class ModelPropertyMutationNode extends MutationNode<ModelProperty> {
});
}

/** Completes {@link startModelEdge}; normally established by a model's property connection. */
connectModel(modelNode: MutationNode<Model>) {
this.startModelEdge().setTail(modelNode);
}
Expand Down
32 changes: 30 additions & 2 deletions packages/mutator-framework/src/mutation-node/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,25 @@ import { HalfEdge } from "./mutation-edge.js";
import { MutationNode } from "./mutation-node.js";
import { traceNode } from "./tracer.js";

/** Connection-key configuration shape; current node connection methods do not consume it. */
export interface ModelConnectOptions {
/** Mutation key for the base model node. Defaults to this node's key. */
/** Key designated for a base model node; callers must select that node explicitly. */
baseModel?: string;
/** Mutation key for the indexer value node. Defaults to this node's key. */
/** Key designated for an indexer value node; callers must select that node explicitly. */
indexerValue?: string;
}

/**
* Updates model references as connected base, property, and indexer nodes change.
* Connections are explicit: construction alone does not traverse the model.
*/
export class ModelMutationNode extends MutationNode<Model> {
/** Selects model-specific connection behavior, even if this node is later replaced. */
readonly kind = "Model";
/**
* Starts a base reference that follows model mutations, clears the base on
* deletion, and rejects replacements that are not models.
*/
startBaseModelEdge() {
return new HalfEdge<Model, Model>(this, {
onTailMutation: ({ tail }) => {
Expand All @@ -35,10 +45,16 @@ export class ModelMutationNode extends MutationNode<Model> {
},
});
}
/** Completes {@link startBaseModelEdge} with the chosen base model node. */
connectBase(baseNode: MutationNode<Model>) {
this.startBaseModelEdge().setTail(baseNode);
}

/**
* Starts a property reference and connects the property's containing-model
* back-reference. Renames replace the source-name entry in the output map;
* deletion removes it. Replacements must remain model properties.
*/
startPropertyEdge() {
return new HalfEdge<Model, ModelProperty>(this, {
onTailCreation: ({ tail }) => {
Expand Down Expand Up @@ -75,10 +91,15 @@ export class ModelMutationNode extends MutationNode<Model> {
});
}

/** Connects a property and its containing-model back-reference via {@link startPropertyEdge}. */
connectProperty(propNode: ModelPropertyMutationNode) {
this.startPropertyEdge().setTail(propNode);
}

/**
* Starts a reference to an existing indexer's value type.
* Deletion substitutes `any`; no indexer is created if the output has none.
*/
startIndexerValueEdge() {
return new HalfEdge<Model, Type>(this, {
onTailMutation: ({ tail }) => {
Expand Down Expand Up @@ -114,6 +135,11 @@ export class ModelMutationNode extends MutationNode<Model> {
});
}

/**
* Starts a reference to an existing indexer's key scalar.
* Deletion substitutes `integer`; replacements must be scalars. No indexer is
* created if the output has none.
*/
startIndexerKeyEdge() {
return new HalfEdge<Model, Scalar>(this, {
onTailMutation: ({ tail }) => {
Expand Down Expand Up @@ -152,10 +178,12 @@ export class ModelMutationNode extends MutationNode<Model> {
});
}

/** Completes {@link startIndexerKeyEdge} with an explicitly selected key node. */
connectIndexerKey(indexerNode: MutationNode<Scalar>) {
this.startIndexerKeyEdge().setTail(indexerNode);
}

/** Completes {@link startIndexerValueEdge} with an explicitly selected value node. */
connectIndexerValue(indexerNode: MutationNode<Type>) {
this.startIndexerValueEdge().setTail(indexerNode);
}
Expand Down
Loading
Loading