From b7e2334de5f4e8ef78c1b7da825a7d8127a1af13 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Thu, 31 Oct 2024 21:36:08 -0600 Subject: [PATCH 1/4] Propagate Namespace mutation to children --- packages/compiler/src/experimental/index.ts | 2 ++ .../compiler/src/experimental/mutators.ts | 18 +++++++++- .../src/experimental/typekit/kits/type.ts | 36 +++++++++++++------ .../test/experimental/mutator.test.ts | 14 +++++--- 4 files changed, 54 insertions(+), 16 deletions(-) diff --git a/packages/compiler/src/experimental/index.ts b/packages/compiler/src/experimental/index.ts index ab5fa2549d..db540a5a95 100644 --- a/packages/compiler/src/experimental/index.ts +++ b/packages/compiler/src/experimental/index.ts @@ -7,6 +7,8 @@ export { MutatorRecord as unsafe_MutatorRecord, MutatorReplaceFn as unsafe_MutatorReplaceFn, mutateSubgraph as unsafe_mutateSubgraph, + mutateSubgraphWithNamespace as unsafe_mutateSubgraphWithNamespace, + MutatorWithNamespace as unsafe_MutatorWithNamespace, } from "./mutators.js"; export { Realm as unsafe_Realm } from "./realm.js"; export { unsafe_useStateMap, unsafe_useStateSet } from "./state-accessor.js"; diff --git a/packages/compiler/src/experimental/mutators.ts b/packages/compiler/src/experimental/mutators.ts index bd948aea66..f72b1db372 100644 --- a/packages/compiler/src/experimental/mutators.ts +++ b/packages/compiler/src/experimental/mutators.ts @@ -73,9 +73,15 @@ export interface Mutator { ScalarConstructor?: MutatorRecord; StringTemplate?: MutatorRecord; StringTemplateSpan?: MutatorRecord; - Namespace?: MutatorRecord; } +/** + * @experimental - This is a type that extends Mutator with a Namespace property. + */ +export type MutatorWithNamespace = Mutator & { + Namespace?: MutatorRecord; +} + /** @experimental */ export enum MutatorFlow { MutateAndRecurse = 0, @@ -93,7 +99,10 @@ export type MutableType = Exclude< | FunctionParameter | ObjectType | Projection + | Namespace >; +/** @experimental */ +export type MutableTypeWithNamespace = MutableType | Namespace; const typeId = CustomKeyMap.objectKeyer(); const mutatorId = CustomKeyMap.objectKeyer(); const seen = new CustomKeyMap<[MutableType, Set | Mutator[]], Type>(([type, mutators]) => { @@ -103,6 +112,13 @@ const seen = new CustomKeyMap<[MutableType, Set | Mutator[]], Type>(([t return key; }); +/** @experimental */ +export function mutateSubgraphWithNamespace( program: Program, + mutators: (MutatorWithNamespace)[], + type: T,): { realm: Realm | null; type: MutableTypeWithNamespace } { + return mutateSubgraph(program, mutators, type as any); +} + /** @experimental */ export function mutateSubgraph( program: Program, diff --git a/packages/compiler/src/experimental/typekit/kits/type.ts b/packages/compiler/src/experimental/typekit/kits/type.ts index 277b597ea1..0766e4b3b6 100644 --- a/packages/compiler/src/experimental/typekit/kits/type.ts +++ b/packages/compiler/src/experimental/typekit/kits/type.ts @@ -1,5 +1,5 @@ -import type { Enum, Model, Type } from "../../../core/types.js"; -import { defineKit } from "../define-kit.js"; +import { type Namespace, type Type } from "../../../core/types.js"; +import { $, defineKit } from "../define-kit.js"; import { copyMap } from "../utils.js"; /** @experimental */ @@ -70,20 +70,21 @@ defineKit({ clone = this.program.checker.createType({ ...type, decorators: [...type.decorators], - decoratorDeclarations: new Map(type.decoratorDeclarations), - models: new Map(type.models), - enums: new Map(type.enums), - functionDeclarations: new Map(type.functionDeclarations), instantiationParameters: type.instantiationParameters ? [...type.instantiationParameters] : undefined, - interfaces: new Map(type.interfaces), - namespaces: new Map(type.namespaces), - operations: new Map(type.operations), projections: [...type.projections], - scalars: new Map(type.scalars), - unions: new Map(type.unions), }); + const clonedNamespace = clone as Namespace; + clonedNamespace.decoratorDeclarations = cloneTypeCollection(type.decoratorDeclarations, {namespace: clonedNamespace}); + clonedNamespace.models = cloneTypeCollection(type.models, {namespace: clonedNamespace}); + clonedNamespace.enums = cloneTypeCollection(type.enums, {namespace: clonedNamespace}); + clonedNamespace.functionDeclarations = cloneTypeCollection(type.functionDeclarations, {namespace: clonedNamespace}); + clonedNamespace.interfaces = cloneTypeCollection(type.interfaces, {namespace: clonedNamespace}); + clonedNamespace.namespaces = cloneTypeCollection(type.namespaces, {namespace: clonedNamespace}); + clonedNamespace.operations = cloneTypeCollection(type.operations, {namespace: clonedNamespace}); + clonedNamespace.scalars = cloneTypeCollection(type.scalars, {namespace: clonedNamespace}); + clonedNamespace.unions = cloneTypeCollection(type.unions, {namespace: clonedNamespace}); break; default: clone = this.program.checker.createType({ @@ -97,3 +98,16 @@ defineKit({ }, }, }); + + +function cloneTypeCollection(collection: Map, options: {namespace?: Namespace} = {}): Map { + const cloneCollection = new Map(); + for (const [key, type] of collection) { + const clone = $.type.clone(type); + if("namespace" in clone && options.namespace) { + clone.namespace = options.namespace; + } + cloneCollection.set(key, clone); + } + return cloneCollection; +} diff --git a/packages/compiler/test/experimental/mutator.test.ts b/packages/compiler/test/experimental/mutator.test.ts index 716a794e48..4603571f0e 100644 --- a/packages/compiler/test/experimental/mutator.test.ts +++ b/packages/compiler/test/experimental/mutator.test.ts @@ -1,5 +1,5 @@ import { beforeEach, expect, it } from "vitest"; -import { mutateSubgraph, Mutator, MutatorFlow } from "../../src/experimental/mutators.js"; +import { mutateSubgraph, Mutator, MutatorFlow, mutateSubgraphWithNamespace, MutatorWithNamespace } from "../../src/experimental/mutators.js"; import { Model, Namespace } from "../../src/index.js"; import { createTestHost } from "../../src/testing/test-host.js"; import { createTestWrapper } from "../../src/testing/test-utils.js"; @@ -85,16 +85,16 @@ it("removes model reference from namespace", async () => { `; const { Foo } = (await runner.compile(code)) as { Foo: Namespace; Bar: Model; Baz: Model }; - const mutator: Mutator = { + const mutator: MutatorWithNamespace = { name: "test", Namespace: { - mutate: (ns, clone, p, realm) => { + mutate: (_ns, clone) => { clone.models.delete("Bar"); }, }, }; - const { type } = mutateSubgraph(runner.program, [mutator], Foo); + const { type } = mutateSubgraphWithNamespace(runner.program, [mutator], Foo); const mutatedNs = type as Namespace; @@ -102,6 +102,12 @@ it("removes model reference from namespace", async () => { expect(Foo.models.has("Bar")).toBeTruthy(); // Mutated namespace should not have Bar model expect(mutatedNs.models.has("Bar")).toBeFalsy(); + // Mutated namespace is propagated to the models + expect(mutatedNs.models.get("Baz")!.namespace?.models.get("Bar")).toBeUndefined(); + // Original should be unchanged + expect(Foo.models.get("Baz")!.namespace?.models.get("Bar")).toBeDefined(); + expect(Foo.models.get("Baz")!.namespace).toBe(Foo); + }); it("do not recurse the model", async () => { From a019b475fd5a771775eef034e210561d9f7360a5 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Thu, 31 Oct 2024 21:42:37 -0600 Subject: [PATCH 2/4] Format --- ...te-namespaces-cascade-2024-9-31-21-41-8.md | 7 ++++ packages/compiler/src/experimental/index.ts | 2 +- .../compiler/src/experimental/mutators.ts | 12 +++--- .../src/experimental/typekit/kits/type.ts | 38 +++++++++++++------ .../test/experimental/mutator.test.ts | 9 ++++- 5 files changed, 48 insertions(+), 20 deletions(-) create mode 100644 .chronus/changes/feature-mutate-namespaces-cascade-2024-9-31-21-41-8.md diff --git a/.chronus/changes/feature-mutate-namespaces-cascade-2024-9-31-21-41-8.md b/.chronus/changes/feature-mutate-namespaces-cascade-2024-9-31-21-41-8.md new file mode 100644 index 0000000000..8333e554f5 --- /dev/null +++ b/.chronus/changes/feature-mutate-namespaces-cascade-2024-9-31-21-41-8.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/compiler" +--- + +Add mutateSubgraphWithNamespace as a separate API diff --git a/packages/compiler/src/experimental/index.ts b/packages/compiler/src/experimental/index.ts index db540a5a95..a7b3a243e6 100644 --- a/packages/compiler/src/experimental/index.ts +++ b/packages/compiler/src/experimental/index.ts @@ -6,9 +6,9 @@ export { MutatorFn as unsafe_MutatorFn, MutatorRecord as unsafe_MutatorRecord, MutatorReplaceFn as unsafe_MutatorReplaceFn, + MutatorWithNamespace as unsafe_MutatorWithNamespace, mutateSubgraph as unsafe_mutateSubgraph, mutateSubgraphWithNamespace as unsafe_mutateSubgraphWithNamespace, - MutatorWithNamespace as unsafe_MutatorWithNamespace, } from "./mutators.js"; export { Realm as unsafe_Realm } from "./realm.js"; export { unsafe_useStateMap, unsafe_useStateSet } from "./state-accessor.js"; diff --git a/packages/compiler/src/experimental/mutators.ts b/packages/compiler/src/experimental/mutators.ts index f72b1db372..a49a155d29 100644 --- a/packages/compiler/src/experimental/mutators.ts +++ b/packages/compiler/src/experimental/mutators.ts @@ -80,7 +80,7 @@ export interface Mutator { */ export type MutatorWithNamespace = Mutator & { Namespace?: MutatorRecord; -} +}; /** @experimental */ export enum MutatorFlow { @@ -113,10 +113,12 @@ const seen = new CustomKeyMap<[MutableType, Set | Mutator[]], Type>(([t }); /** @experimental */ -export function mutateSubgraphWithNamespace( program: Program, - mutators: (MutatorWithNamespace)[], - type: T,): { realm: Realm | null; type: MutableTypeWithNamespace } { - return mutateSubgraph(program, mutators, type as any); +export function mutateSubgraphWithNamespace( + program: Program, + mutators: MutatorWithNamespace[], + type: T, +): { realm: Realm | null; type: MutableTypeWithNamespace } { + return mutateSubgraph(program, mutators, type as any); } /** @experimental */ diff --git a/packages/compiler/src/experimental/typekit/kits/type.ts b/packages/compiler/src/experimental/typekit/kits/type.ts index 0766e4b3b6..c7dfcf3dc3 100644 --- a/packages/compiler/src/experimental/typekit/kits/type.ts +++ b/packages/compiler/src/experimental/typekit/kits/type.ts @@ -76,15 +76,27 @@ defineKit({ projections: [...type.projections], }); const clonedNamespace = clone as Namespace; - clonedNamespace.decoratorDeclarations = cloneTypeCollection(type.decoratorDeclarations, {namespace: clonedNamespace}); - clonedNamespace.models = cloneTypeCollection(type.models, {namespace: clonedNamespace}); - clonedNamespace.enums = cloneTypeCollection(type.enums, {namespace: clonedNamespace}); - clonedNamespace.functionDeclarations = cloneTypeCollection(type.functionDeclarations, {namespace: clonedNamespace}); - clonedNamespace.interfaces = cloneTypeCollection(type.interfaces, {namespace: clonedNamespace}); - clonedNamespace.namespaces = cloneTypeCollection(type.namespaces, {namespace: clonedNamespace}); - clonedNamespace.operations = cloneTypeCollection(type.operations, {namespace: clonedNamespace}); - clonedNamespace.scalars = cloneTypeCollection(type.scalars, {namespace: clonedNamespace}); - clonedNamespace.unions = cloneTypeCollection(type.unions, {namespace: clonedNamespace}); + clonedNamespace.decoratorDeclarations = cloneTypeCollection(type.decoratorDeclarations, { + namespace: clonedNamespace, + }); + clonedNamespace.models = cloneTypeCollection(type.models, { namespace: clonedNamespace }); + clonedNamespace.enums = cloneTypeCollection(type.enums, { namespace: clonedNamespace }); + clonedNamespace.functionDeclarations = cloneTypeCollection(type.functionDeclarations, { + namespace: clonedNamespace, + }); + clonedNamespace.interfaces = cloneTypeCollection(type.interfaces, { + namespace: clonedNamespace, + }); + clonedNamespace.namespaces = cloneTypeCollection(type.namespaces, { + namespace: clonedNamespace, + }); + clonedNamespace.operations = cloneTypeCollection(type.operations, { + namespace: clonedNamespace, + }); + clonedNamespace.scalars = cloneTypeCollection(type.scalars, { + namespace: clonedNamespace, + }); + clonedNamespace.unions = cloneTypeCollection(type.unions, { namespace: clonedNamespace }); break; default: clone = this.program.checker.createType({ @@ -99,12 +111,14 @@ defineKit({ }, }); - -function cloneTypeCollection(collection: Map, options: {namespace?: Namespace} = {}): Map { +function cloneTypeCollection( + collection: Map, + options: { namespace?: Namespace } = {}, +): Map { const cloneCollection = new Map(); for (const [key, type] of collection) { const clone = $.type.clone(type); - if("namespace" in clone && options.namespace) { + if ("namespace" in clone && options.namespace) { clone.namespace = options.namespace; } cloneCollection.set(key, clone); diff --git a/packages/compiler/test/experimental/mutator.test.ts b/packages/compiler/test/experimental/mutator.test.ts index 4603571f0e..d559c03854 100644 --- a/packages/compiler/test/experimental/mutator.test.ts +++ b/packages/compiler/test/experimental/mutator.test.ts @@ -1,5 +1,11 @@ import { beforeEach, expect, it } from "vitest"; -import { mutateSubgraph, Mutator, MutatorFlow, mutateSubgraphWithNamespace, MutatorWithNamespace } from "../../src/experimental/mutators.js"; +import { + mutateSubgraph, + mutateSubgraphWithNamespace, + Mutator, + MutatorFlow, + MutatorWithNamespace, +} from "../../src/experimental/mutators.js"; import { Model, Namespace } from "../../src/index.js"; import { createTestHost } from "../../src/testing/test-host.js"; import { createTestWrapper } from "../../src/testing/test-utils.js"; @@ -107,7 +113,6 @@ it("removes model reference from namespace", async () => { // Original should be unchanged expect(Foo.models.get("Baz")!.namespace?.models.get("Bar")).toBeDefined(); expect(Foo.models.get("Baz")!.namespace).toBe(Foo); - }); it("do not recurse the model", async () => { From 25456fe2ba4ab8520d05d7d46e5b9b6a50b63cdb Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Tue, 5 Nov 2024 19:50:57 -0600 Subject: [PATCH 3/4] Address feedback --- packages/compiler/src/experimental/mutators.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/compiler/src/experimental/mutators.ts b/packages/compiler/src/experimental/mutators.ts index a49a155d29..15c16e976b 100644 --- a/packages/compiler/src/experimental/mutators.ts +++ b/packages/compiler/src/experimental/mutators.ts @@ -79,7 +79,7 @@ export interface Mutator { * @experimental - This is a type that extends Mutator with a Namespace property. */ export type MutatorWithNamespace = Mutator & { - Namespace?: MutatorRecord; + Namespace: MutatorRecord; }; /** @experimental */ @@ -112,7 +112,11 @@ const seen = new CustomKeyMap<[MutableType, Set | Mutator[]], Type>(([t return key; }); -/** @experimental */ +/** + * Mutate the type graph with some namespace mutation. + * **Warning** this will most likely endup mutating the entire TypeGraph as every type relate to namespace in some way or another causing parent navigation which in turn would mutate everything in that namespace. + * @experimental + */ export function mutateSubgraphWithNamespace( program: Program, mutators: MutatorWithNamespace[], From ccd04c5d67db7304d1944a353919c2633ee037a7 Mon Sep 17 00:00:00 2001 From: Jose Manuel Heredia Hidalgo Date: Tue, 5 Nov 2024 19:58:20 -0600 Subject: [PATCH 4/4] Fix spelling --- packages/compiler/src/experimental/mutators.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/compiler/src/experimental/mutators.ts b/packages/compiler/src/experimental/mutators.ts index 15c16e976b..e668004914 100644 --- a/packages/compiler/src/experimental/mutators.ts +++ b/packages/compiler/src/experimental/mutators.ts @@ -114,7 +114,9 @@ const seen = new CustomKeyMap<[MutableType, Set | Mutator[]], Type>(([t /** * Mutate the type graph with some namespace mutation. - * **Warning** this will most likely endup mutating the entire TypeGraph as every type relate to namespace in some way or another causing parent navigation which in turn would mutate everything in that namespace. + * **Warning** this will most likely end up mutating the entire TypeGraph + * as every type relate to namespace in some way or another + * causing parent navigation which in turn would mutate everything in that namespace. * @experimental */ export function mutateSubgraphWithNamespace(