microsoft/typespec

Public

mirrored from https://github.com/microsoft/typespecAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/fix-diff-highlight-issue

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

packages/asset-emitter/test/circular-ref.test.ts

161lines · modecode

1import {
2 type Model,
3 type ModelProperty,
4 type Program,
5 type Type,
6 getTypeName,
7} from "@typespec/compiler";
8import { deepStrictEqual, strictEqual } from "assert";
9import { describe, it } from "vitest";
10import {
11 ArrayBuilder,
12 type Context,
13 type EmitEntity,
14 type EmitterOutput,
15 ObjectBuilder,
16 ReferenceCycle,
17 type Scope,
18 TypeEmitter,
19 createAssetEmitter,
20} from "../src/index.js";
21import { getHostForTypeSpecFile } from "./host.js";
22
23describe("compiler: emitter-framework: circular references", () => {
24 interface FindOptions {
25 modelsInline: boolean;
26 circleReference: boolean;
27 }
28
29 interface CircularRefEntry {
30 target: EmitEntity<any>;
31 cycle: ReferenceCycle;
32 }
33 async function findCircularReferences(code: string, options: FindOptions) {
34 const invalidReferences: CircularRefEntry[] = [];
35
36 const cls = class extends TypeEmitter<any, any> {
37 modelDeclaration(model: Model, _: string): EmitterOutput<object> {
38 const obj = new ObjectBuilder();
39 obj.set("props", this.emitter.emitModelProperties(model));
40 if (options.modelsInline) {
41 return obj; // Never make a declaration
42 } else {
43 return this.emitter.result.declaration(model.name, obj);
44 }
45 }
46
47 modelProperties(model: Model) {
48 const arr = new ArrayBuilder();
49 for (const prop of model.properties.values()) {
50 arr.push(this.emitter.emitModelProperty(prop));
51 }
52 return arr;
53 }
54
55 modelPropertyLiteral(property: ModelProperty) {
56 if (options.circleReference) {
57 return this.emitter.emitTypeReference(property.type);
58 } else {
59 const obj = new ObjectBuilder();
60 obj.set("name", property.name);
61 return obj;
62 }
63 }
64
65 arrayLiteral(array: Model, elementType: Type) {
66 return { type: "array", items: this.emitter.emitTypeReference(elementType) };
67 }
68
69 programContext(program: Program): Context {
70 const sourceFile = this.emitter.createSourceFile("main");
71 return { scope: sourceFile.globalScope };
72 }
73
74 circularReference(target: EmitEntity<any>, scope: Scope<any>, cycle: ReferenceCycle) {
75 if (!cycle.containsDeclaration) {
76 invalidReferences.push({ target, cycle });
77 return target;
78 }
79 return super.circularReference(target, scope, cycle);
80 }
81 };
82
83 const host = await getHostForTypeSpecFile(code);
84 const assetEmitter = createAssetEmitter(host.program, cls, {
85 emitterOutputDir: host.program.compilerOptions.outputDir!,
86 options: {},
87 } as any);
88 assetEmitter.emitProgram();
89
90 return invalidReferences;
91 }
92
93 const selfRef = `model Foo { foo: Foo }`;
94 it("self referencing with declaration works fine", async () => {
95 const invalidReferences = await findCircularReferences(selfRef, {
96 modelsInline: false,
97 circleReference: true,
98 });
99 strictEqual(invalidReferences.length, 0);
100 });
101
102 it("self referencing without declaration report circular reference", async () => {
103 const invalidReferences = await findCircularReferences(selfRef, {
104 modelsInline: true,
105 circleReference: true,
106 });
107 strictEqual(invalidReferences.length, 1);
108 });
109
110 it("without circular reference inline types cause no issue", async () => {
111 const invalidReferences = await findCircularReferences(selfRef, {
112 modelsInline: true,
113 circleReference: false,
114 });
115 strictEqual(invalidReferences.length, 0);
116 });
117
118 it("resolve the circular reference stack", async () => {
119 const code = `
120 model First { foo: Foo }
121 model Foo { foo: Bar }
122 model Bar { bar: Foo }
123 `;
124 const result = await findCircularReferences(code, {
125 modelsInline: true,
126 circleReference: true,
127 });
128 strictEqual(result.length, 1);
129
130 deepStrictEqual(result[0].cycle.containsDeclaration, false);
131 deepStrictEqual(
132 [...result[0].cycle].map((x) => getTypeName(x.type)),
133 ["Foo", "Foo.foo", "Bar", "Bar.bar"],
134 );
135 });
136
137 describe("cycle with declaration inside", () => {
138 it("doesn't report issue if referencing a declaration in the cycle", async () => {
139 const code = `
140 model First { foo: Foo }
141 model Foo { foo: Foo[] }
142 `;
143 const result = await findCircularReferences(code, {
144 modelsInline: false,
145 circleReference: true,
146 });
147 strictEqual(result.length, 0);
148 });
149 it("doesn't report issue if referencing an inline type in the cycle", async () => {
150 const code = `
151 model First { foo: Foo[] }
152 model Foo { foo: Foo[] }
153 `;
154 const result = await findCircularReferences(code, {
155 modelsInline: false,
156 circleReference: true,
157 });
158 strictEqual(result.length, 0);
159 });
160 });
161});
162