All files / src circular-refs.ts

100% Statements 36/36
90% Branches 18/20
100% Functions 5/5
100% Lines 33/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72            21x 21x 21x     52x 47x 3x 3x     44x 44x 44x 44x 8x 8x 8x       44x 44x     21x 44x     21x             44x     737x 265x 152x 58x   207x 207x 8x   207x 541x       44x 44x               58x 58x    
/**
 * Detects circular $ref chains in OpenAPI schema definitions.
 * Returns the set of schema names that participate in cycles.
 * These should be wrapped in `lazy()` in the output .ck.
 */
export function detectCircularRefs(schemas: Record<string, unknown>): Set<string> {
    const circular = new Set<string>();
    const visiting = new Set<string>(); // current DFS path
    const visited = new Set<string>(); // fully explored
 
    function visit(name: string): void {
        if (visited.has(name)) return;
        if (visiting.has(name)) {
            circular.add(name);
            return;
        }
 
        visiting.add(name);
        const schema = schemas[name];
        Eif (schema && typeof schema === 'object') {
            for (const ref of collectRefs(schema as Record<string, unknown>)) {
                const refName = extractRefName(ref);
                Eif (refName && schemas[refName]) {
                    visit(refName);
                }
            }
        }
        visiting.delete(name);
        visited.add(name);
    }
 
    for (const name of Object.keys(schemas)) {
        visit(name);
    }
 
    return circular;
}
 
/**
 * Recursively collects all $ref strings from a schema object.
 */
function collectRefs(obj: Record<string, unknown>): string[] {
    const refs: string[] = [];
 
    function walk(val: unknown): void {
        if (!val || typeof val !== 'object') return;
        if (Array.isArray(val)) {
            for (const item of val) walk(item);
            return;
        }
        const record = val as Record<string, unknown>;
        if (typeof record.$ref === 'string') {
            refs.push(record.$ref);
        }
        for (const v of Object.values(record)) {
            walk(v);
        }
    }
 
    walk(obj);
    return refs;
}
 
/**
 * Extracts the schema name from a $ref like "#/components/schemas/Foo"
 * or "#/definitions/Foo" (Swagger 2.0 after normalization still uses components).
 */
export function extractRefName(ref: string): string | undefined {
    const match = ref.match(/^#\/(?:components\/schemas|definitions)\/(.+)$/);
    return match?.[1];
}