All files print-ck.ts

89.85% Statements 62/69
80% Branches 60/75
100% Functions 4/4
98.03% Lines 50/51

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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114            1x                         5x       60x 60x 60x 60x 60x   60x   7x   7x 3x 3x 3x   3x     7x 2x 2x 2x   2x     7x 1x     7x 1x     7x       7x 7x       2x 2x 2x 2x 3x 3x 3x   2x 2x 2x                         60x 60x     60x 60x     60x 5x 5x       60x 60x 60x 50x 50x 50x     60x    
import type { CkRootNode, OpResponseHeaderNode } from '@contractkit/core';
import { printModelDecl } from './print-contract.js';
import { printRoute, printSecurity, type CommentBlock } from './print-operation.js';
import { printType } from './print-type.js';
import { INDENT } from './indent.js';
 
export const DEFAULT_PRINT_WIDTH = 80;
 
// ─── Options block ──────────────────────────────────────────────────────────
 
/**
 * Quote an options-block value if it isn't a plain identifier.
 *
 * Plain identifiers (starts with letter/underscore/dollar, rest are
 * alphanumeric/underscore/dollar/hyphen/dot) are left bare. Everything
 * else — paths with slashes, values starting with `#`, values with spaces,
 * etc. — is double-quoted so the round-trip parse is unambiguous.
 */
function quoteOptionsValue(value: string): string {
    return /^[a-zA-Z_$][a-zA-Z0-9_$\-.]*$/.test(value) ? value : `"${value}"`;
}
 
function printOptionsBlock(ast: CkRootNode): string | null {
    const hasMeta = Object.keys(ast.meta).length > 0;
    const hasServices = Object.keys(ast.services).length > 0;
    const hasSecurity = ast.security !== undefined;
    const hasRequestHeaders = (ast.requestHeaders?.length ?? 0) > 0;
    const hasResponseHeaders = (ast.responseHeaders?.length ?? 0) > 0;
 
    if (!hasMeta && !hasServices && !hasSecurity && !hasRequestHeaders && !hasResponseHeaders) return null;
 
    const lines: string[] = ['options {'];
 
    if (hasMeta) {
        lines.push(`${INDENT}keys: {`);
        for (Iconst [key, value] of Object.entries(ast.meta)) {
            lines.push(`${INDENT}${INDENT}${key}: ${quoteOptionsValue(value)}`);
        }
        lines.push(`${INDENT}}`);
    }
 
    if (hasServices) {
        lines.push(`${INDENT}services: {`);
        for (Iconst [key, value] of Object.entries(ast.services)) {
            lines.push(`${INDENT}${INDENT}${key}: ${quoteOptionsValue(value)}`);
        }
        lines.push(`${INDENT}}`);
    }
 
    if (hasRequestHeaders) {
        lines.push(...printOptionsHeaderScope('request', ast.requestHeaders!));
    }
 
    if (hasResponseHeaders) {
        lines.push(...printOptionsHeaderScope('response', ast.responseHeaders!));
    }
 
    Iif (hasSecurity) {
        lines.push(...printSecurity(ast.security!, INDENT, INDENT + INDENT));
    }
 
    lines.push('}');
    return lines.join('\n');
}
 
function printOptionsHeaderScope(keyword: 'request' | 'response', headers: OpResponseHeaderNode[]): string[] {
    const I2 = INDENT + INDENT;
    const I3 = INDENT + INDENT + INDENT;
    const lines = [`${INDENT}${keyword}: {`, `${I2}headers: {`];
    for (Iconst h of headers) {
        const opt = h.optional ? '?' : '';
        const trail = h.description ? ` # ${h.description}` : '';
        lines.push(`${I3}${h.name}${opt}: ${printType(h.type)}${trail}`);
    }
    lines.push(`${I2}}`);
    lines.push(`${INDENT}}`);
    return lines;
}
 
// ─── CK file printer ───────────────────────────────────────────────────────
 
/**
 * Render a parsed `.ck` AST back to source. Output is byte-identical on
 * round-trip when the input is already canonically formatted: options block
 * first, then contracts, then operations, separated by blank lines.
 *
 * `printWidth` is forwarded to per-model printing for line wrapping inside
 * inline-object types.
 */
export function printCk(ast: CkRootNode, printWidth: number = DEFAULT_PRINT_WIDTH): string {
    const parts: string[] = [];
 
    // Options block
    const options = printOptionsBlock(ast);
    if (options) parts.push(options);
 
    // Contracts (models)
    for (Iconst model of ast.models) {
        Iif (parts.length > 0) parts.push('');
        parts.push(`contract ${printModelDecl(model, printWidth)}`);
    }
 
    // Operations (routes)
    const emptyBlocks: CommentBlock[] = [];
    const emptyIdx = { value: 0 };
    for (Iconst route of ast.routes) {
        if (parts.length > 0) parts.push('');
        const modPart = route.modifiers?.length ? `(${route.modifiers[0]})` : '';
        parts.push(`operation${modPart} ${printRoute(route, emptyBlocks, emptyIdx, Infinity)}`);
    }
 
    return parts.join('\n') + '\n';
}