diff --git a/.chronus/changes/SemanticWalkerExitFixes-2024-9-9-10-27-23.md b/.chronus/changes/SemanticWalkerExitFixes-2024-9-9-10-27-23.md new file mode 100644 index 0000000000..b380c8573e --- /dev/null +++ b/.chronus/changes/SemanticWalkerExitFixes-2024-9-9-10-27-23.md @@ -0,0 +1,7 @@ +--- +changeKind: fix +packages: + - "@typespec/compiler" +--- + +Fix #4588 \ No newline at end of file diff --git a/packages/compiler/src/core/semantic-walker.ts b/packages/compiler/src/core/semantic-walker.ts index e32ee72630..a9e689138b 100644 --- a/packages/compiler/src/core/semantic-walker.ts +++ b/packages/compiler/src/core/semantic-walker.ts @@ -194,6 +194,8 @@ function navigateNamespaceType(namespace: Namespace, context: NavigationContext) for (const decorator of namespace.decoratorDeclarations.values()) { navigateDecoratorDeclaration(decorator, context); } + + context.emit("exitNamespace", namespace); } function checkVisited(visited: Set, item: Type) { @@ -288,6 +290,8 @@ function navigateInterfaceType(type: Interface, context: NavigationContext) { for (const op of type.operations.values()) { navigateOperationType(op, context); } + + context.emit("exitInterface", type); } function navigateEnumType(type: Enum, context: NavigationContext) { @@ -296,6 +300,11 @@ function navigateEnumType(type: Enum, context: NavigationContext) { } context.emit("enum", type); + for (const member of type.members.values()) { + navigateTypeInternal(member, context); + } + + context.emit("exitEnum", type); } function navigateUnionType(type: Union, context: NavigationContext) { @@ -309,6 +318,8 @@ function navigateUnionType(type: Union, context: NavigationContext) { for (const variant of type.variants.values()) { navigateUnionTypeVariant(variant, context); } + + context.emit("exitUnion", type); } function navigateUnionTypeVariant(type: UnionVariant, context: NavigationContext) { @@ -317,6 +328,8 @@ function navigateUnionTypeVariant(type: UnionVariant, context: NavigationContext } if (context.emit("unionVariant", type) === ListenerFlow.NoRecursion) return; navigateTypeInternal(type.type, context); + + context.emit("exitUnionVariant", type); } function navigateTupleType(type: Tuple, context: NavigationContext) { @@ -327,6 +340,7 @@ function navigateTupleType(type: Tuple, context: NavigationContext) { for (const value of type.values) { navigateTypeInternal(value, context); } + context.emit("exitTuple", type); } function navigateStringTemplate(type: StringTemplate, context: NavigationContext) { diff --git a/packages/compiler/test/semantic-walker.test.ts b/packages/compiler/test/semantic-walker.test.ts index 5240e001db..8d35b1c883 100644 --- a/packages/compiler/test/semantic-walker.test.ts +++ b/packages/compiler/test/semantic-walker.test.ts @@ -1,6 +1,7 @@ import { deepStrictEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; import { + Enum, Interface, ListenerFlow, Model, @@ -29,18 +30,24 @@ describe("compiler: semantic walker", () => { function createCollector(customListener?: SemanticNodeListener) { const result = { + enums: [] as Enum[], + exitEnums: [] as Enum[], + interfaces: [] as Interface[], + exitInterfaces: [] as Interface[], models: [] as Model[], exitModels: [] as Model[], modelProperties: [] as ModelProperty[], exitModelProperties: [] as ModelProperty[], namespaces: [] as Namespace[], + exitNamespaces: [] as Namespace[], operations: [] as Operation[], exitOperations: [] as Operation[], - interfaces: [] as Interface[], - unions: [] as Union[], - unionVariants: [] as UnionVariant[], tuples: [] as Tuple[], exitTuples: [] as Tuple[], + unions: [] as Union[], + exitUnions: [] as Union[], + unionVariants: [] as UnionVariant[], + exitUnionVariants: [] as UnionVariant[], }; const listener: SemanticNodeListener = { @@ -48,6 +55,10 @@ describe("compiler: semantic walker", () => { result.namespaces.push(x); return customListener?.namespace?.(x); }, + exitNamespace: (x) => { + result.exitNamespaces.push(x); + return customListener?.exitNamespace?.(x); + }, operation: (x) => { result.operations.push(x); return customListener?.operation?.(x); @@ -72,17 +83,29 @@ describe("compiler: semantic walker", () => { result.exitModelProperties.push(x); return customListener?.exitModelProperty?.(x); }, + enum: (x) => { + result.enums.push(x); + return customListener?.enum?.(x); + }, + exitEnum: (x) => { + result.exitEnums.push(x); + return customListener?.exitEnum?.(x); + }, union: (x) => { result.unions.push(x); return customListener?.union?.(x); }, + exitUnion: (x) => { + result.exitUnions.push(x); + return customListener?.exitUnion?.(x); + }, interface: (x) => { result.interfaces.push(x); return customListener?.interface?.(x); }, - unionVariant: (x) => { - result.unionVariants.push(x); - return customListener?.unionVariant?.(x); + exitInterface: (x) => { + result.exitInterfaces.push(x); + return customListener?.exitInterface?.(x); }, tuple: (x) => { result.tuples.push(x); @@ -92,6 +115,14 @@ describe("compiler: semantic walker", () => { result.exitTuples.push(x); return customListener?.exitTuple?.(x); }, + unionVariant: (x) => { + result.unionVariants.push(x); + return customListener?.unionVariant?.(x); + }, + exitUnionVariant: (x) => { + result.exitUnionVariants.push(x); + return customListener?.exitUnionVariant?.(x); + }, }; return [result, listener] as const; } @@ -198,6 +229,31 @@ describe("compiler: semantic walker", () => { ); }); + it("finds exit namespaces", async () => { + const result = await runNavigator(` + namespace Global.My; + namespace Simple { + } + namespace Parent { + namespace Child { + } + } + `); + + deepStrictEqual( + result.exitNamespaces.map((x) => getNamespaceFullName(x)), + [ + "TypeSpec", + "Global.My.Simple", + "Global.My.Parent.Child", + "Global.My.Parent", + "Global.My", + "Global", + "", + ], + ); + }); + it("finds model properties", async () => { const result = await runNavigator(` model Foo { @@ -236,6 +292,94 @@ describe("compiler: semantic walker", () => { strictEqual(result.exitModelProperties[2].name, "name"); }); + it("finds enums", async () => { + const result = await runNavigator(` + enum Direction { + North: "north", + East: "east", + South: "south", + West: "west", + } + + enum Metric { + One: 1, + Ten: 10, + Hundred: 100, + } + `); + + strictEqual(result.enums.length, 2); + strictEqual(result.enums[0].name, "Direction"); + strictEqual(result.enums[1].name, "Metric"); + }); + + it("finds exit enums", async () => { + const result = await runNavigator(` + enum Direction { + North: "north", + East: "east", + South: "south", + West: "west", + } + + enum Metric { + One: 1, + Ten: 10, + Hundred: 100, + } + `); + + strictEqual(result.exitEnums.length, 2); + strictEqual(result.exitEnums[0].name, "Direction"); + strictEqual(result.exitEnums[1].name, "Metric"); + }); + + it("finds tuples with model", async () => { + const result = await runNavigator(` + model Foo { + bar: [Direction, Color] + } + + enum Direction { + North, + East, + South, + West, + } + + model Color { + value: string; + } + `); + + strictEqual(result.tuples.length, 1); + strictEqual(result.enums[0].name, "Direction"); + strictEqual(result.models[1].name, "Color"); + }); + + it("finds exit tuples with model", async () => { + const result = await runNavigator(` + model Foo { + bar: [Direction, Color] + } + + enum Direction { + North, + East, + South, + West, + } + + model Color { + value: string; + } + `); + + strictEqual(result.exitTuples.length, 1); + strictEqual(result.exitEnums[0].name, "Direction"); + strictEqual(result.exitModels[0].name, "Color"); + }); + it("finds unions", async () => { const result = await runNavigator(` union A { @@ -271,6 +415,19 @@ describe("compiler: semantic walker", () => { strictEqual(result.exitTuples[0].values.length, 1); }); + it("finds exit unions", async () => { + const result = await runNavigator(` + union A { + x: true; + } + `); + + strictEqual(result.exitUnions.length, 1); + strictEqual(result.exitUnions[0].name!, "A"); + strictEqual(result.exitUnionVariants.length, 1); + strictEqual(result.exitUnionVariants[0].name!, "x"); + }); + it("finds interfaces", async () => { const result = await runNavigator(` model B { }; @@ -285,6 +442,20 @@ describe("compiler: semantic walker", () => { strictEqual(result.operations[0].name, "a"); }); + it("finds exit interfaces", async () => { + const result = await runNavigator(` + model B { }; + interface A { + a(): true; + } + `); + + strictEqual(result.exitInterfaces.length, 1, "finds interfaces"); + strictEqual(result.exitInterfaces[0].name, "A"); + strictEqual(result.exitOperations.length, 1, "finds operations"); + strictEqual(result.exitOperations[0].name, "a"); + }); + it("finds owned or inherited properties", async () => { const result = await runNavigator(` model Pet {