From cba85ebe113f7e09be96ccab819958de583f9913 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 May 2025 17:29:02 +0000 Subject: [PATCH 1/6] Initial plan for issue From 9db8493f0b11fb25e1a43a46bbaf6ac1c645ebcb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 May 2025 17:40:00 +0000 Subject: [PATCH 2/6] Add type.listUnder utility function to list types under a container Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com> --- .changeset/yellow-bees-float.md | 5 + .../src/core/helpers/type-utils.test.ts | 104 ++++++++++++++ .../compiler/src/core/helpers/type-utils.ts | 131 ++++++++++++++++++ .../compiler/src/typekit/kits/type.test.ts | 121 ++++++++++++++++ packages/compiler/src/typekit/kits/type.ts | 31 +++++ 5 files changed, 392 insertions(+) create mode 100644 .changeset/yellow-bees-float.md create mode 100644 packages/compiler/src/core/helpers/type-utils.test.ts create mode 100644 packages/compiler/src/core/helpers/type-utils.ts create mode 100644 packages/compiler/src/typekit/kits/type.test.ts diff --git a/.changeset/yellow-bees-float.md b/.changeset/yellow-bees-float.md new file mode 100644 index 00000000000..736f6a1ea23 --- /dev/null +++ b/.changeset/yellow-bees-float.md @@ -0,0 +1,5 @@ +--- +"@typespec/compiler": minor +--- + +Added `$.type.listUnder()` utility function to allow listing all types under a container (namespace/interface) that match a filter criteria. \ No newline at end of file diff --git a/packages/compiler/src/core/helpers/type-utils.test.ts b/packages/compiler/src/core/helpers/type-utils.test.ts new file mode 100644 index 00000000000..a4ca800ad9a --- /dev/null +++ b/packages/compiler/src/core/helpers/type-utils.test.ts @@ -0,0 +1,104 @@ +import { Interface, Model, Namespace, Operation } from "../types.js"; +import { createTestHost, TestHost } from "../../testing/index.js"; +import { listTypesUnder } from "./type-utils.js"; + +describe("type-utils: listTypesUnder", () => { + let host: TestHost; + + beforeEach(async () => { + host = await createTestHost(); + }); + + it("lists all types in a namespace", async () => { + const testCode = ` + namespace A { + model M1 {} + model M2 {} + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const types = listTypesUnder(A, (t) => true); + + // Should include 2 models plus namespace A + expect(types.length).toBe(3); + }); + + it("filters models", async () => { + const testCode = ` + namespace A { + model M1 {} + model M2 {} + scalar S {} + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const models = listTypesUnder(A, (t): t is Model => t.kind === "Model"); + + expect(models.length).toBe(2); + expect(models.every(m => m.kind === "Model")).toBe(true); + }); + + it("handles nested namespaces", async () => { + const testCode = ` + namespace A { + model M1 {} + + namespace B { + model M2 {} + } + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const models = listTypesUnder(A, (t): t is Model => t.kind === "Model"); + + expect(models.length).toBe(2); + }); + + it("handles interfaces and operations", async () => { + const testCode = ` + namespace A { + interface I1 { + op1(): void; + op2(): string; + } + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const operations = listTypesUnder(A, (t): t is Operation => t.kind === "Operation"); + const interfaces = listTypesUnder(A, (t): t is Interface => t.kind === "Interface"); + + expect(operations.length).toBe(2); + expect(interfaces.length).toBe(1); + }); + + it("respects recursive option", async () => { + const testCode = ` + namespace A { + model M1 {} + + namespace B { + model M2 {} + + namespace C { + model M3 {} + } + } + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // With recursive = true (default) + const allModels = listTypesUnder(A, (t): t is Model => t.kind === "Model"); + expect(allModels.length).toBe(3); + + // With recursive = false + const topLevelModels = listTypesUnder(A, (t): t is Model => t.kind === "Model", { recursive: false }); + expect(topLevelModels.length).toBe(1); + expect(topLevelModels[0].name).toBe("M1"); + }); +}); \ No newline at end of file diff --git a/packages/compiler/src/core/helpers/type-utils.ts b/packages/compiler/src/core/helpers/type-utils.ts new file mode 100644 index 00000000000..c3cf7638b70 --- /dev/null +++ b/packages/compiler/src/core/helpers/type-utils.ts @@ -0,0 +1,131 @@ +import { Interface, Namespace, Type } from "../types.js"; +import { isTemplateDeclaration } from "../type-utils.js"; + +export interface ListUnderOptions { + /** + * If the container is a namespace look for types in sub namespaces. + * @default true + */ + recursive?: boolean; +} + +/** + * List types under the given container. Will list types recursively by default. + * @param container Container. + * @param filter Function to filter types. + * @param options Options. + */ +export function listTypesUnder( + container: Namespace | Interface, + filter: (type: Type) => type is T, + options?: ListUnderOptions +): T[]; + +/** + * List types under the given container. Will list types recursively by default. + * @param container Container. + * @param filter Function to filter types. + * @param options Options. + */ +export function listTypesUnder( + container: Namespace | Interface, + filter: (type: Type) => boolean, + options?: ListUnderOptions +): Type[]; + +export function listTypesUnder( + container: Namespace | Interface, + filter: (type: Type) => boolean, + options: ListUnderOptions = {} +): Type[] { + const types: Type[] = []; + + function addTypes(current: Namespace | Interface) { + if (current.kind === "Interface" && isTemplateDeclaration(current)) { + // Skip template interface types + return; + } + + // For interfaces, we only have operations + if (current.kind === "Interface") { + for (const op of current.operations.values()) { + if (filter(op)) { + types.push(op); + } + } + return; + } + + // For namespaces, check all contained type collections + const namespace = current as Namespace; + + // Check models + for (const model of namespace.models.values()) { + if (filter(model)) { + types.push(model); + } + } + + // Check operations + for (const op of namespace.operations.values()) { + if (filter(op)) { + types.push(op); + } + } + + // Check scalars + for (const scalar of namespace.scalars.values()) { + if (filter(scalar)) { + types.push(scalar); + } + } + + // Check enums + for (const enumType of namespace.enums.values()) { + if (filter(enumType)) { + types.push(enumType); + } + } + + // Check unions + for (const union of namespace.unions.values()) { + if (filter(union)) { + types.push(union); + } + } + + // Check interfaces + for (const iface of namespace.interfaces.values()) { + if (filter(iface)) { + types.push(iface); + } + } + + // Recursively check sub-namespaces + const recursive = options.recursive ?? true; + if (recursive) { + for (const subNamespace of namespace.namespaces.values()) { + if ( + !( + subNamespace.name === "Prototypes" && + subNamespace.namespace?.name === "TypeSpec" && + subNamespace.namespace.namespace?.name === "" + ) + ) { + if (filter(subNamespace)) { + types.push(subNamespace); + } + addTypes(subNamespace); + } + } + } + + // Recursively check operations in interfaces + for (const iface of namespace.interfaces.values()) { + addTypes(iface); + } + } + + addTypes(container); + return types; +} \ No newline at end of file diff --git a/packages/compiler/src/typekit/kits/type.test.ts b/packages/compiler/src/typekit/kits/type.test.ts new file mode 100644 index 00000000000..6cec5e4beed --- /dev/null +++ b/packages/compiler/src/typekit/kits/type.test.ts @@ -0,0 +1,121 @@ +import { Test, TestHost, createTestHost } from "../testing/index.js"; +import { Model, Namespace } from "../core/types.js"; + +describe("listTypesUnder", () => { + let host: TestHost; + + beforeEach(async () => { + host = await createTestHost(); + }); + + it("lists all types under a namespace", async () => { + const testCode = ` + namespace A { + model M1 {} + model M2 {} + + @doc("Some doc") + model M3 {} + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const types = host.program.typespecType.listUnder(A, t => true); + + // Should include all 3 models plus the namespace itself when filter is 'true' + expect(types.length).toBe(4); + }); + + it("filters types with the provided filter function", async () => { + const testCode = ` + namespace A { + model M1 {} + + @doc("Some doc") + model M2 {} + + @deprecated + model M3 {} + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // Filter only models with @doc decorator + const types = host.program.typespecType.listUnder(A, t => { + return t.kind === "Model" && !!t.decorators.find(d => d.decorator.name === "@doc"); + }); + + expect(types.length).toBe(1); + expect((types[0] as Model).name).toBe("M2"); + }); + + it("recursively searches sub-namespaces by default", async () => { + const testCode = ` + namespace A { + model M1 {} + + namespace B { + model M2 {} + + namespace C { + model M3 {} + } + } + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // Should find all models in all namespaces + const types = host.program.typespecType.listUnder(A, t => t.kind === "Model"); + + expect(types.length).toBe(3); + }); + + it("only searches the current namespace when recursive is false", async () => { + const testCode = ` + namespace A { + model M1 {} + + namespace B { + model M2 {} + + namespace C { + model M3 {} + } + } + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // Should only find models in the A namespace + const types = host.program.typespecType.listUnder( + A, + t => t.kind === "Model", + { recursive: false } + ); + + expect(types.length).toBe(1); + expect((types[0] as Model).name).toBe("M1"); + }); + + it("finds operations in interfaces", async () => { + const testCode = ` + namespace A { + interface I1 { + op1(): void; + op2(): string; + } + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // Should find both operations + const types = host.program.typespecType.listUnder(A, t => t.kind === "Operation"); + + expect(types.length).toBe(2); + }); +}); \ No newline at end of file diff --git a/packages/compiler/src/typekit/kits/type.ts b/packages/compiler/src/typekit/kits/type.ts index 14ec7d759c9..e8088d8ff4b 100644 --- a/packages/compiler/src/typekit/kits/type.ts +++ b/packages/compiler/src/typekit/kits/type.ts @@ -1,4 +1,5 @@ import { getLocationContext } from "../../core/helpers/location-context.js"; +import { listTypesUnder } from "../../core/helpers/type-utils.js"; import { getMaxItems, getMaxLength, @@ -13,6 +14,7 @@ import { isNeverType } from "../../core/type-utils.js"; import { Entity, Enum, + Interface, Model, Namespace, Node, @@ -164,6 +166,32 @@ export interface TypeTypekit { kind?: K, ) => K extends Type["kind"] ? Extract : undefined >; + + /** + * List all types under the given container that satisfy the filter criteria. + * @param container The container to inspect. + * @param filter A filter function to select specific types. + * @param options Optional settings for the search. + * @returns An array of types that match the filter criteria. + */ + listUnder( + container: Namespace | Interface, + filter: (type: Type) => type is T, + options?: { recursive?: boolean } + ): T[]; + + /** + * List all types under the given container that satisfy the filter criteria. + * @param container The container to inspect. + * @param filter A filter function to select specific types. + * @param options Optional settings for the search. + * @returns An array of types that match the filter criteria. + */ + listUnder( + container: Namespace | Interface, + filter: (type: Type) => boolean, + options?: { recursive?: boolean } + ): Type[]; } interface TypekitExtension { @@ -345,5 +373,8 @@ defineKit({ } return [type, diagnostics]; }), + listUnder(container, filter, options = {}) { + return listTypesUnder(container, filter, options); + }, }, }); From 78bf8bf6edf45076cdf0949078f00a9763ffce51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 May 2025 17:52:52 +0000 Subject: [PATCH 3/6] Remove top-level describe from type-utils.test.ts Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com> --- .../src/core/helpers/type-utils.test.ts | 164 +++++++++--------- 1 file changed, 81 insertions(+), 83 deletions(-) diff --git a/packages/compiler/src/core/helpers/type-utils.test.ts b/packages/compiler/src/core/helpers/type-utils.test.ts index a4ca800ad9a..b61f571bb6a 100644 --- a/packages/compiler/src/core/helpers/type-utils.test.ts +++ b/packages/compiler/src/core/helpers/type-utils.test.ts @@ -2,103 +2,101 @@ import { Interface, Model, Namespace, Operation } from "../types.js"; import { createTestHost, TestHost } from "../../testing/index.js"; import { listTypesUnder } from "./type-utils.js"; -describe("type-utils: listTypesUnder", () => { - let host: TestHost; +let host: TestHost; - beforeEach(async () => { - host = await createTestHost(); - }); +beforeEach(async () => { + host = await createTestHost(); +}); - it("lists all types in a namespace", async () => { - const testCode = ` - namespace A { - model M1 {} - model M2 {} - } - `; +it("lists all types in a namespace", async () => { + const testCode = ` + namespace A { + model M1 {} + model M2 {} + } + `; - const { A } = (await host.compile(testCode)) as { A: Namespace }; - const types = listTypesUnder(A, (t) => true); - - // Should include 2 models plus namespace A - expect(types.length).toBe(3); - }); + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const types = listTypesUnder(A, (t) => true); + + // Should include 2 models plus namespace A + expect(types.length).toBe(3); +}); - it("filters models", async () => { - const testCode = ` - namespace A { - model M1 {} - model M2 {} - scalar S {} - } - `; +it("filters models", async () => { + const testCode = ` + namespace A { + model M1 {} + model M2 {} + scalar S {} + } + `; - const { A } = (await host.compile(testCode)) as { A: Namespace }; - const models = listTypesUnder(A, (t): t is Model => t.kind === "Model"); - - expect(models.length).toBe(2); - expect(models.every(m => m.kind === "Model")).toBe(true); - }); + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const models = listTypesUnder(A, (t): t is Model => t.kind === "Model"); + + expect(models.length).toBe(2); + expect(models.every(m => m.kind === "Model")).toBe(true); +}); - it("handles nested namespaces", async () => { - const testCode = ` - namespace A { - model M1 {} - - namespace B { - model M2 {} - } +it("handles nested namespaces", async () => { + const testCode = ` + namespace A { + model M1 {} + + namespace B { + model M2 {} } - `; + } + `; - const { A } = (await host.compile(testCode)) as { A: Namespace }; - const models = listTypesUnder(A, (t): t is Model => t.kind === "Model"); - - expect(models.length).toBe(2); - }); + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const models = listTypesUnder(A, (t): t is Model => t.kind === "Model"); + + expect(models.length).toBe(2); +}); - it("handles interfaces and operations", async () => { - const testCode = ` - namespace A { - interface I1 { - op1(): void; - op2(): string; - } +it("handles interfaces and operations", async () => { + const testCode = ` + namespace A { + interface I1 { + op1(): void; + op2(): string; } - `; + } + `; - const { A } = (await host.compile(testCode)) as { A: Namespace }; - const operations = listTypesUnder(A, (t): t is Operation => t.kind === "Operation"); - const interfaces = listTypesUnder(A, (t): t is Interface => t.kind === "Interface"); - - expect(operations.length).toBe(2); - expect(interfaces.length).toBe(1); - }); + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const operations = listTypesUnder(A, (t): t is Operation => t.kind === "Operation"); + const interfaces = listTypesUnder(A, (t): t is Interface => t.kind === "Interface"); + + expect(operations.length).toBe(2); + expect(interfaces.length).toBe(1); +}); - it("respects recursive option", async () => { - const testCode = ` - namespace A { - model M1 {} +it("respects recursive option", async () => { + const testCode = ` + namespace A { + model M1 {} + + namespace B { + model M2 {} - namespace B { - model M2 {} - - namespace C { - model M3 {} - } + namespace C { + model M3 {} } } - `; + } + `; - const { A } = (await host.compile(testCode)) as { A: Namespace }; - - // With recursive = true (default) - const allModels = listTypesUnder(A, (t): t is Model => t.kind === "Model"); - expect(allModels.length).toBe(3); - - // With recursive = false - const topLevelModels = listTypesUnder(A, (t): t is Model => t.kind === "Model", { recursive: false }); - expect(topLevelModels.length).toBe(1); - expect(topLevelModels[0].name).toBe("M1"); - }); + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // With recursive = true (default) + const allModels = listTypesUnder(A, (t): t is Model => t.kind === "Model"); + expect(allModels.length).toBe(3); + + // With recursive = false + const topLevelModels = listTypesUnder(A, (t): t is Model => t.kind === "Model", { recursive: false }); + expect(topLevelModels.length).toBe(1); + expect(topLevelModels[0].name).toBe("M1"); }); \ No newline at end of file From 747a22a033d412029e091ada560ce8aa0fa14f09 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 May 2025 18:00:51 +0000 Subject: [PATCH 4/6] Address review feedback Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com> --- .changeset/yellow-bees-float.md | 1 + .../compiler/src/core/helpers/type-utils.ts | 48 +++++++------------ packages/compiler/src/typekit/kits/type.ts | 8 +--- .../{src => test}/typekit/kits/type.test.ts | 36 ++------------ 4 files changed, 23 insertions(+), 70 deletions(-) rename packages/compiler/{src => test}/typekit/kits/type.test.ts (70%) diff --git a/.changeset/yellow-bees-float.md b/.changeset/yellow-bees-float.md index 736f6a1ea23..dd7884e999b 100644 --- a/.changeset/yellow-bees-float.md +++ b/.changeset/yellow-bees-float.md @@ -2,4 +2,5 @@ "@typespec/compiler": minor --- +@chronus/chronus Added `$.type.listUnder()` utility function to allow listing all types under a container (namespace/interface) that match a filter criteria. \ No newline at end of file diff --git a/packages/compiler/src/core/helpers/type-utils.ts b/packages/compiler/src/core/helpers/type-utils.ts index c3cf7638b70..6db664260a4 100644 --- a/packages/compiler/src/core/helpers/type-utils.ts +++ b/packages/compiler/src/core/helpers/type-utils.ts @@ -1,48 +1,35 @@ import { Interface, Namespace, Type } from "../types.js"; import { isTemplateDeclaration } from "../type-utils.js"; -export interface ListUnderOptions { - /** - * If the container is a namespace look for types in sub namespaces. - * @default true - */ - recursive?: boolean; -} - /** - * List types under the given container. Will list types recursively by default. + * List types under the given container. Will list types recursively. * @param container Container. * @param filter Function to filter types. - * @param options Options. */ export function listTypesUnder( container: Namespace | Interface, filter: (type: Type) => type is T, - options?: ListUnderOptions ): T[]; /** - * List types under the given container. Will list types recursively by default. + * List types under the given container. Will list types recursively. * @param container Container. * @param filter Function to filter types. - * @param options Options. */ export function listTypesUnder( container: Namespace | Interface, filter: (type: Type) => boolean, - options?: ListUnderOptions ): Type[]; export function listTypesUnder( container: Namespace | Interface, filter: (type: Type) => boolean, - options: ListUnderOptions = {} ): Type[] { const types: Type[] = []; function addTypes(current: Namespace | Interface) { - if (current.kind === "Interface" && isTemplateDeclaration(current)) { - // Skip template interface types + if (isTemplateDeclaration(current)) { + // Skip template types return; } @@ -57,7 +44,7 @@ export function listTypesUnder( } // For namespaces, check all contained type collections - const namespace = current as Namespace; + const namespace = current; // Check models for (const model of namespace.models.values()) { @@ -102,21 +89,18 @@ export function listTypesUnder( } // Recursively check sub-namespaces - const recursive = options.recursive ?? true; - if (recursive) { - for (const subNamespace of namespace.namespaces.values()) { - if ( - !( - subNamespace.name === "Prototypes" && - subNamespace.namespace?.name === "TypeSpec" && - subNamespace.namespace.namespace?.name === "" - ) - ) { - if (filter(subNamespace)) { - types.push(subNamespace); - } - addTypes(subNamespace); + for (const subNamespace of namespace.namespaces.values()) { + if ( + !( + subNamespace.name === "Prototypes" && + subNamespace.namespace?.name === "TypeSpec" && + subNamespace.namespace.namespace?.name === "" + ) + ) { + if (filter(subNamespace)) { + types.push(subNamespace); } + addTypes(subNamespace); } } diff --git a/packages/compiler/src/typekit/kits/type.ts b/packages/compiler/src/typekit/kits/type.ts index e8088d8ff4b..b2e1135f082 100644 --- a/packages/compiler/src/typekit/kits/type.ts +++ b/packages/compiler/src/typekit/kits/type.ts @@ -171,26 +171,22 @@ export interface TypeTypekit { * List all types under the given container that satisfy the filter criteria. * @param container The container to inspect. * @param filter A filter function to select specific types. - * @param options Optional settings for the search. * @returns An array of types that match the filter criteria. */ listUnder( container: Namespace | Interface, filter: (type: Type) => type is T, - options?: { recursive?: boolean } ): T[]; /** * List all types under the given container that satisfy the filter criteria. * @param container The container to inspect. * @param filter A filter function to select specific types. - * @param options Optional settings for the search. * @returns An array of types that match the filter criteria. */ listUnder( container: Namespace | Interface, filter: (type: Type) => boolean, - options?: { recursive?: boolean } ): Type[]; } @@ -373,8 +369,8 @@ defineKit({ } return [type, diagnostics]; }), - listUnder(container, filter, options = {}) { - return listTypesUnder(container, filter, options); + listUnder(container, filter) { + return listTypesUnder(container, filter); }, }, }); diff --git a/packages/compiler/src/typekit/kits/type.test.ts b/packages/compiler/test/typekit/kits/type.test.ts similarity index 70% rename from packages/compiler/src/typekit/kits/type.test.ts rename to packages/compiler/test/typekit/kits/type.test.ts index 6cec5e4beed..01f8c4c2a00 100644 --- a/packages/compiler/src/typekit/kits/type.test.ts +++ b/packages/compiler/test/typekit/kits/type.test.ts @@ -1,7 +1,7 @@ -import { Test, TestHost, createTestHost } from "../testing/index.js"; -import { Model, Namespace } from "../core/types.js"; +import { Test, TestHost, createTestHost } from "../../../src/testing/index.js"; +import { Model, Namespace } from "../../../src/core/types.js"; -describe("listTypesUnder", () => { +describe("listUnder", () => { let host: TestHost; beforeEach(async () => { @@ -50,7 +50,7 @@ describe("listTypesUnder", () => { expect((types[0] as Model).name).toBe("M2"); }); - it("recursively searches sub-namespaces by default", async () => { + it("recursively searches sub-namespaces", async () => { const testCode = ` namespace A { model M1 {} @@ -73,34 +73,6 @@ describe("listTypesUnder", () => { expect(types.length).toBe(3); }); - it("only searches the current namespace when recursive is false", async () => { - const testCode = ` - namespace A { - model M1 {} - - namespace B { - model M2 {} - - namespace C { - model M3 {} - } - } - } - `; - - const { A } = (await host.compile(testCode)) as { A: Namespace }; - - // Should only find models in the A namespace - const types = host.program.typespecType.listUnder( - A, - t => t.kind === "Model", - { recursive: false } - ); - - expect(types.length).toBe(1); - expect((types[0] as Model).name).toBe("M1"); - }); - it("finds operations in interfaces", async () => { const testCode = ` namespace A { From 9a633f8b3c34c7cd4acd87cc8ccd5758942be76b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 May 2025 18:43:47 +0000 Subject: [PATCH 5/6] Move type-utils.test.ts to correct location Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com> --- .../core/helpers/type-utils.test.ts | 33 ++----------------- 1 file changed, 3 insertions(+), 30 deletions(-) rename packages/compiler/{src => test}/core/helpers/type-utils.test.ts (65%) diff --git a/packages/compiler/src/core/helpers/type-utils.test.ts b/packages/compiler/test/core/helpers/type-utils.test.ts similarity index 65% rename from packages/compiler/src/core/helpers/type-utils.test.ts rename to packages/compiler/test/core/helpers/type-utils.test.ts index b61f571bb6a..69d8e96f679 100644 --- a/packages/compiler/src/core/helpers/type-utils.test.ts +++ b/packages/compiler/test/core/helpers/type-utils.test.ts @@ -1,6 +1,6 @@ -import { Interface, Model, Namespace, Operation } from "../types.js"; -import { createTestHost, TestHost } from "../../testing/index.js"; -import { listTypesUnder } from "./type-utils.js"; +import { Interface, Model, Namespace, Operation } from "../../../src/core/types.js"; +import { createTestHost, TestHost } from "../../../src/testing/index.js"; +import { listTypesUnder } from "../../../src/core/helpers/type-utils.js"; let host: TestHost; @@ -72,31 +72,4 @@ it("handles interfaces and operations", async () => { expect(operations.length).toBe(2); expect(interfaces.length).toBe(1); -}); - -it("respects recursive option", async () => { - const testCode = ` - namespace A { - model M1 {} - - namespace B { - model M2 {} - - namespace C { - model M3 {} - } - } - } - `; - - const { A } = (await host.compile(testCode)) as { A: Namespace }; - - // With recursive = true (default) - const allModels = listTypesUnder(A, (t): t is Model => t.kind === "Model"); - expect(allModels.length).toBe(3); - - // With recursive = false - const topLevelModels = listTypesUnder(A, (t): t is Model => t.kind === "Model", { recursive: false }); - expect(topLevelModels.length).toBe(1); - expect(topLevelModels[0].name).toBe("M1"); }); \ No newline at end of file From b4d3801f881753173c9b10c07e5f66ff40342456 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 19 May 2025 18:51:51 +0000 Subject: [PATCH 6/6] Move type.test.ts from typekit/kits to typekit directory Co-authored-by: timotheeguerin <1031227+timotheeguerin@users.noreply.github.com> --- .../compiler/test/typekit/kits/type.test.ts | 93 ------------------- packages/compiler/test/typekit/type.test.ts | 92 ++++++++++++++++++ 2 files changed, 92 insertions(+), 93 deletions(-) delete mode 100644 packages/compiler/test/typekit/kits/type.test.ts diff --git a/packages/compiler/test/typekit/kits/type.test.ts b/packages/compiler/test/typekit/kits/type.test.ts deleted file mode 100644 index 01f8c4c2a00..00000000000 --- a/packages/compiler/test/typekit/kits/type.test.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Test, TestHost, createTestHost } from "../../../src/testing/index.js"; -import { Model, Namespace } from "../../../src/core/types.js"; - -describe("listUnder", () => { - let host: TestHost; - - beforeEach(async () => { - host = await createTestHost(); - }); - - it("lists all types under a namespace", async () => { - const testCode = ` - namespace A { - model M1 {} - model M2 {} - - @doc("Some doc") - model M3 {} - } - `; - - const { A } = (await host.compile(testCode)) as { A: Namespace }; - const types = host.program.typespecType.listUnder(A, t => true); - - // Should include all 3 models plus the namespace itself when filter is 'true' - expect(types.length).toBe(4); - }); - - it("filters types with the provided filter function", async () => { - const testCode = ` - namespace A { - model M1 {} - - @doc("Some doc") - model M2 {} - - @deprecated - model M3 {} - } - `; - - const { A } = (await host.compile(testCode)) as { A: Namespace }; - - // Filter only models with @doc decorator - const types = host.program.typespecType.listUnder(A, t => { - return t.kind === "Model" && !!t.decorators.find(d => d.decorator.name === "@doc"); - }); - - expect(types.length).toBe(1); - expect((types[0] as Model).name).toBe("M2"); - }); - - it("recursively searches sub-namespaces", async () => { - const testCode = ` - namespace A { - model M1 {} - - namespace B { - model M2 {} - - namespace C { - model M3 {} - } - } - } - `; - - const { A } = (await host.compile(testCode)) as { A: Namespace }; - - // Should find all models in all namespaces - const types = host.program.typespecType.listUnder(A, t => t.kind === "Model"); - - expect(types.length).toBe(3); - }); - - it("finds operations in interfaces", async () => { - const testCode = ` - namespace A { - interface I1 { - op1(): void; - op2(): string; - } - } - `; - - const { A } = (await host.compile(testCode)) as { A: Namespace }; - - // Should find both operations - const types = host.program.typespecType.listUnder(A, t => t.kind === "Operation"); - - expect(types.length).toBe(2); - }); -}); \ No newline at end of file diff --git a/packages/compiler/test/typekit/type.test.ts b/packages/compiler/test/typekit/type.test.ts index 46c422de864..bf3a57e1fda 100644 --- a/packages/compiler/test/typekit/type.test.ts +++ b/packages/compiler/test/typekit/type.test.ts @@ -4,6 +4,7 @@ import { isTemplateInstance } from "../../src/index.js"; import { expectDiagnosticEmpty, expectDiagnostics } from "../../src/testing/expect.js"; import { $ } from "../../src/typekit/index.js"; import { getAssignables, getTypes } from "./utils.js"; +import { TestHost, createTestHost } from "../../src/testing/index.js"; describe("is", () => { it("checks if an entity is a type", async () => { @@ -667,3 +668,94 @@ describe("resolve", () => { }); }); }); + +describe("listUnder", () => { + let host: TestHost; + + beforeEach(async () => { + host = await createTestHost(); + }); + + it("lists all types under a namespace", async () => { + const testCode = ` + namespace A { + model M1 {} + model M2 {} + + @doc("Some doc") + model M3 {} + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + const types = host.program.typespecType.listUnder(A, t => true); + + // Should include all 3 models plus the namespace itself when filter is 'true' + expect(types.length).toBe(4); + }); + + it("filters types with the provided filter function", async () => { + const testCode = ` + namespace A { + model M1 {} + + @doc("Some doc") + model M2 {} + + @deprecated + model M3 {} + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // Filter only models with @doc decorator + const types = host.program.typespecType.listUnder(A, t => { + return t.kind === "Model" && !!t.decorators.find(d => d.decorator.name === "@doc"); + }); + + expect(types.length).toBe(1); + expect((types[0] as Model).name).toBe("M2"); + }); + + it("recursively searches sub-namespaces", async () => { + const testCode = ` + namespace A { + model M1 {} + + namespace B { + model M2 {} + + namespace C { + model M3 {} + } + } + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // Should find all models in all namespaces + const types = host.program.typespecType.listUnder(A, t => t.kind === "Model"); + + expect(types.length).toBe(3); + }); + + it("finds operations in interfaces", async () => { + const testCode = ` + namespace A { + interface I1 { + op1(): void; + op2(): string; + } + } + `; + + const { A } = (await host.compile(testCode)) as { A: Namespace }; + + // Should find both operations + const types = host.program.typespecType.listUnder(A, t => t.kind === "Operation"); + + expect(types.length).toBe(2); + }); +});