diff --git a/packages/compiler/src/emitter-framework/asset-emitter.ts b/packages/compiler/src/emitter-framework/asset-emitter.ts index 7e68a303bc..8d76fe0e9a 100644 --- a/packages/compiler/src/emitter-framework/asset-emitter.ts +++ b/packages/compiler/src/emitter-framework/asset-emitter.ts @@ -238,6 +238,7 @@ export function createAssetEmitter( entity: EmitEntity ): EmitEntity { if (entity.kind !== "declaration") { + typeEmitter.circularReference(entity); return entity; } diff --git a/packages/compiler/src/emitter-framework/type-emitter.ts b/packages/compiler/src/emitter-framework/type-emitter.ts index 0633b75a14..a49e073348 100644 --- a/packages/compiler/src/emitter-framework/type-emitter.ts +++ b/packages/compiler/src/emitter-framework/type-emitter.ts @@ -702,6 +702,13 @@ export class TypeEmitter> { return this.emitter.result.none(); } + circularReference(target: EmitEntity): EmitEntity | T { + // if (target.kind === "declaration") { + // return this.reference(target, pathUp, pathDown, commonScope); + // } + return this.emitter.result.none(); + } + declarationName(declarationType: TypeSpecDeclaration): string | undefined { compilerAssert( declarationType.name !== undefined, diff --git a/packages/compiler/src/emitter-framework/types.ts b/packages/compiler/src/emitter-framework/types.ts index 0033762820..b4e4d90dd3 100644 --- a/packages/compiler/src/emitter-framework/types.ts +++ b/packages/compiler/src/emitter-framework/types.ts @@ -174,6 +174,7 @@ export type TypeEmitterMethod = keyof Omit< | "sourceFile" | "declarationName" | "reference" + | "circularReference" | "emitValue" | "writeOutput" | EndingWith, "Context"> diff --git a/packages/compiler/test/emitter-framework/emitter.test.ts b/packages/compiler/test/emitter-framework/emitter.test.ts index 408f99b778..3e3fe81d5c 100644 --- a/packages/compiler/test/emitter-framework/emitter.test.ts +++ b/packages/compiler/test/emitter-framework/emitter.test.ts @@ -1,4 +1,4 @@ -import assert from "assert"; +import assert, { strictEqual } from "assert"; import * as prettier from "prettier"; import { Enum, @@ -735,4 +735,91 @@ describe("emitter-framework: object emitter", () => { const contents = JSON.parse((await host.compilerHost.readFile("tsp-output/test.json")!).text); assert.strictEqual(contents.declarations.length, 2); }); + + describe.only("circular references", () => { + interface FindOptions { + noDeclaration: boolean; + circleReference: boolean; + } + async function findCircularReferences(options: FindOptions) { + const invalidReferences: any[] = []; + + const cls = class extends TypeEmitter { + modelDeclaration(model: Model, _: string): EmitterOutput { + const obj = new ObjectBuilder(); + obj.set("props", this.emitter.emitModelProperties(model)); + if (options.noDeclaration) { + return obj; // Never make a declaration + } else { + return this.emitter.result.declaration(model.name, obj); + } + } + + modelProperties(model: Model) { + const arr = new ArrayBuilder(); + for (const prop of model.properties.values()) { + arr.push(this.emitter.emitModelProperty(prop)); + } + return arr; + } + + modelPropertyLiteral(property: ModelProperty) { + if (options.circleReference) { + return this.emitter.emitTypeReference(property.type); + } else { + const obj = new ObjectBuilder(); + obj.set("name", property.name); + return obj; + } + } + + programContext(program: Program): Context { + const sourceFile = this.emitter.createSourceFile("main"); + return { scope: sourceFile.globalScope }; + } + + circularReference(target: EmitEntity) { + invalidReferences.push({ entity: target }); + } + }; + + const host = await getHostForTypeSpecFile( + ` + model Foo { + foo: Foo + } + ` + ); + const assetEmitter = createAssetEmitter(host.program, cls, { + emitterOutputDir: host.program.compilerOptions.outputDir!, + options: {}, + } as any); + assetEmitter.emitProgram(); + + return invalidReferences; + } + it("self referencing with declaration works fine", async () => { + const invalidReferences = await findCircularReferences({ + noDeclaration: false, + circleReference: true, + }); + strictEqual(invalidReferences.length, 0); + }); + + it("self referencing without declaration report circular reference", async () => { + const invalidReferences = await findCircularReferences({ + noDeclaration: true, + circleReference: true, + }); + strictEqual(invalidReferences.length, 1); + }); + + it("without circular reference inline types cause no issue", async () => { + const invalidReferences = await findCircularReferences({ + noDeclaration: true, + circleReference: true, + }); + strictEqual(invalidReferences.length, 0); + }); + }); });