Skip to content

langium-zod / detectRecursiveTypes

Function: detectRecursiveTypes()

ts
function detectRecursiveTypes(descriptors): Set<string>;

Defined in: recursion-detector.ts:88

Detects type names that participate in a reference cycle across the descriptor graph.

Builds a directed graph where each object type descriptor is a node and each type reference in its properties is an edge. A depth-first search then identifies all nodes that belong to at least one cycle. The generator uses this set to emit getter-based property accessors instead of direct value expressions, avoiding JavaScript "used before declaration" errors for mutually-recursive Zod schemas.

Only 'object' kind descriptors are considered; union and primitive-alias descriptors are transparent to cycle detection.

Parameters

ParameterTypeDescription
descriptorsZodTypeDescriptor[]The full list of type descriptors to analyse, as returned by extractTypeDescriptors.

Returns

Set<string>

A Set of type names that are involved in at least one reference cycle.

Remarks

A Langium grammar with a rule like Expression: value=Expression | ... is self-referential at the object level. Without getter syntax, the emitted const ExpressionSchema = z.object({ value: ExpressionSchema }) would fail at runtime because ExpressionSchema is referenced before it is defined. The returned set marks Expression as recursive so the generator can emit get value() { return ExpressionSchema; } instead.

Mutual cycles (A → B → A) are also detected: both A and B will appear in the returned set. Diamond-shaped references (A → C and B → C, but no back-edge) are not cycles and will not appear in the result.

Example

ts
import { extractTypeDescriptors, detectRecursiveTypes } from 'langium-zod';
import { collectAst } from 'langium/grammar';

const descriptors = extractTypeDescriptors(collectAst(myGrammar));
const recursive = detectRecursiveTypes(descriptors);
console.log([...recursive]); // e.g. ['Expression', 'Statement']

Use When

  • You need to know which grammar types are recursive before calling generateZodCode (e.g. to log or filter them).
  • You are building a custom code emitter and need the same cycle information that the built-in generator uses.

Avoid When

Never

  • NEVER pass descriptors that have already had projection applied (via applyProjectionToDescriptors) to this function if the projection strips properties that close cycles. BECAUSE the cycle detection graph will miss the back-edge and fail to mark those types as recursive, leading to undefined reference errors in the generated schemas at runtime.
  • NEVER assume the returned set is stable across different filter configurations. BECAUSE filtering with include/exclude can remove types that close a cycle, making previously recursive types appear acyclic.

See

Released under the MIT License.