Skip to content

Circular refs#19

Merged
skonves merged 2 commits into
mainfrom
circular-refs
Mar 20, 2026
Merged

Circular refs#19
skonves merged 2 commits into
mainfrom
circular-refs

Conversation

@skonves

@skonves skonves commented Mar 20, 2026

Copy link
Copy Markdown
Member

resolves #18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes parsing of JSON Schemas containing circular $ref graphs (issue #18) by adding reference-cycle detection to avoid unbounded recursion / stack overflow.

Changes:

  • Track currently-resolving $ref pointers to break circular reference recursion during parsing.
  • When a circular $ref is detected, short-circuit to a named ComplexValue instead of recursing.
  • Add a regression test exercising a mutual circular reference between definitions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
src/json-schema-parser.ts Adds $ref cycle detection (resolvingRefs) and short-circuits circular $ref parsing to avoid stack overflows.
src/json-schema-parser.test.ts Adds a regression test to ensure circular-reference schemas can be parsed without crashing.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

const service = (await parser(sourceContent, absoluteSourcePath)).service;

// ASSERT
expect(service).toBeTruthy();

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only asserts that service is truthy, so it won't catch cases where circular refs no longer overflow but still produce an invalid/partially-parsed service (e.g., missing type declarations or parser violations). It would be stronger to also validate the returned service (and/or assert no parser violations) and check that the expected types are present (e.g. A and B).

Suggested change
expect(service).toBeTruthy();
const validation = validate(service);
expect(validation.errors).toEqual([]);
const typeNames = service.types.map((t) => t.name);
expect(typeNames).toEqual(expect.arrayContaining(['A', 'B']));

Copilot uses AI. Check for mistakes.
Comment thread src/json-schema-parser.ts
inheritedDescription: [],
};
}

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the circular-ref short-circuit path, the parser returns a ComplexValue based on parseTypeName(resolved) without ensuring the referenced schema will actually result in a declared Type/Enum/Union. For cycles made entirely of $ref aliases (e.g. A -> B -> A where both schemas are just $ref), this will break the stack overflow but can still produce a service that references a type name that never gets created, and no violation is reported. Consider detecting $ref-only cycles and emitting a parser error (return untyped()), or registering an in-progress placeholder in the relevant map before descending so circular references always point at an eventual declaration.

Suggested change
const { range, sourceIndex } = decodeRange(
encodeRange(0, schema.ref.loc),
);
this.violations.push({
code: 'PARSER_ERROR',
message: `Circular ref '${schema.ref.value}' does not resolve to a concrete type`,
severity: 'error',
range,
sourcePath: this.sourcePaths[sourceIndex],
});

Copilot uses AI. Check for mistakes.
@skonves skonves merged commit 0b7e445 into main Mar 20, 2026
10 checks passed
@skonves skonves deleted the circular-refs branch March 20, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot parse circular references

2 participants