core/parsers/syntax.parser.ts

core/parsers/syntax.parser.ts is a file in GovLab Patterns. 67 lines of code and 21 definitions.

import { ROLE_DEFINITION, ROLE_NODE, roleOf } from "#core/classifiers/syntax.classifier";
import { childNodes, lineOf } from "#core/selectors/syntax.selector";
import type { CstNode } from "@govlab/code-parse";
import { MODULE_SCOPE } from "#configuration/constants/report.constants";
const SNIPPET_MAX = 76;
const HIGH_SURROGATE_MIN = 0xD8_00;
const HIGH_SURROGATE_MAX = 0xDB_FF;
const LINE_BREAK = "\n";

interface WalkContext {
    depth: number;
    language: string;
    caller: string;
}

interface WalkFrame {
    context: WalkContext;
    node: CstNode;
}

const snippetOf = function snippetOf(node: CstNode): string {
    const raw = node.text ?? "";
    const firstLine = raw.split(LINE_BREAK)[0] ?? raw;
    const clipped = firstLine.trim().slice(0, SNIPPET_MAX);
    const lastCode = clipped.codePointAt(clipped.length - 1) ?? 0;
    return lastCode >= HIGH_SURROGATE_MIN && lastCode <= HIGH_SURROGATE_MAX ? clipped.slice(0, -1) : clipped;
};

const recordFor = function recordFor(node: CstNode, context: WalkContext, role: string): Record<string, unknown> {
    const childTypes = childNodes(node)
        .filter((child) => child.isNamed)
        .map((child) => child.type);
    return {
        caller: context.caller,
        childCount: childTypes.length,
        childTypes,
        depth: context.depth,
        language: context.language,
        line: lineOf(node),
        nodeType: node.type,
        role,
        text: snippetOf(node),
    };
};

const contextBelow = function contextBelow(node: CstNode, context: WalkContext, role: string): WalkContext {
    return {
        caller: node.isNamed && role === ROLE_DEFINITION ? node.type : context.caller,
        depth: node.isNamed ? context.depth + 1 : context.depth,
        language: context.language,
    };
};

export const codeRecords = function codeRecords(root: CstNode, language: string): Record<string, unknown>[] {
    const out: Record<string, unknown>[] = [];
    const stack: WalkFrame[] = [{ context: { caller: MODULE_SCOPE, depth: 0, language }, node: root }];
    let frame = stack.pop();
    while (frame !== undefined) {
        const { context, node } = frame;
        const role = node.isNamed ? roleOf(node.type) : ROLE_NODE;
        if (node.isNamed) {
            out.push(recordFor(node, context, role));
        }
        const below = contextBelow(node, context, role);
        stack.push(
            ...childNodes(node)
                .toReversed()
                .map((child) => ({ context: below, node: child })),
        );
        frame = stack.pop();
    }
    return out;
};