core/parsers/code.go.parser.ts

core/parsers/code.go.parser.ts is a file in GovLab Docs. 121 lines of code and 37 definitions.

import {
    FUNC,
    isIdentPart,
    isIdentStart,
    lastIdent,
    skipBalanced,
    skipIdent,
    skipIdentBack,
    skipSpaces,
    skipSpacesBack,
} from "#core/lexers/code.go.lexer";
import { GO_BUILTINS, GO_KEYWORDS } from "#configuration/constants/code.go.constants";
import type { GoCall, GoCallHit, GoCallScan, GoFuncHit, GoReceiver, ParsedFunc } from "#types/code.types";

const PARENS = "()";
const BRACES = "{}";
const OPEN_PAREN = "(";
const OPEN_BRACE = "{";
const CLOSE_BRACE = "}";
const DOT = ".";
const FUNC_HEAD = "f";

const receiverAt = function receiverAt(src: string, at: number, bound: number): GoReceiver {
    if (src[at] !== OPEN_PAREN) {
        return { next: at, receiver: null };
    }
    const close = skipBalanced(src, at, PARENS);
    return { next: skipSpaces(src, close, bound), receiver: lastIdent(src.slice(at + 1, close - 1)) };
};

const signatureEnd = function signatureEnd(src: string, at: number, bound: number): number {
    let position = src[at] === OPEN_PAREN ? skipBalanced(src, at, PARENS) : at;
    while (position < bound && src[position] !== OPEN_BRACE && src[position] !== CLOSE_BRACE) {
        position = src[position] === OPEN_PAREN ? skipBalanced(src, position, PARENS) : position + 1;
    }
    return position;
};

const isFuncKeyword = function isFuncKeyword(src: string, at: number): boolean {
    return src.startsWith(FUNC, at) && !isIdentPart(src[at - 1]) && !isIdentPart(src[at + FUNC.length]);
};

const funcFrom = function funcFrom(
    src: string,
    receiver: GoReceiver & { sigPos: number },
    bound: number,
): GoFuncHit | null {
    const nameEnd = skipIdent(src, receiver.next, bound);
    const bodyStart = signatureEnd(src, skipSpaces(src, nameEnd, bound), bound);
    if (src[bodyStart] !== OPEN_BRACE) {
        return null;
    }
    const bodyEnd = skipBalanced(src, bodyStart, BRACES);
    return {
        func: {
            bodyEnd: bodyEnd - 1,
            bodyStart: bodyStart + 1,
            name: src.slice(receiver.next, nameEnd),
            receiver: receiver.receiver,
            sigPos: receiver.sigPos,
        },
        next: bodyEnd,
    };
};

const funcAt = function funcAt(src: string, at: number, bound: number): GoFuncHit | null {
    if (!isFuncKeyword(src, at)) {
        return null;
    }
    const receiver = receiverAt(src, skipSpaces(src, at + FUNC.length, bound), bound);
    return isIdentStart(src[receiver.next]) ? funcFrom(src, { ...receiver, sigPos: at }, bound) : null;
};

export const parseFuncs = function parseFuncs(src: string): ParsedFunc[] {
    const funcs: ParsedFunc[] = [];
    let at = 0;
    while (at < src.length) {
        const hit = src[at] === FUNC_HEAD ? funcAt(src, at, src.length) : null;
        if (hit === null) {
            at += 1;
        } else {
            funcs.push(hit.func);
            at = hit.next;
        }
    }
    return funcs;
};

const qualifierBefore = function qualifierBefore(src: string, at: number, start: number): string | null {
    const dot = skipSpacesBack(src, at - 1, start);
    if (src[dot] !== DOT) {
        return null;
    }
    const qualifierEnd = skipSpacesBack(src, dot - 1, start);
    const identStart = skipIdentBack(src, qualifierEnd, start);
    const qualifier = src.slice(identStart + 1, qualifierEnd + 1);
    return qualifier === "" ? null : qualifier;
};

const isCallName = function isCallName(src: string, next: number, end: number, name: string): boolean {
    return src[skipSpaces(src, next, end)] === OPEN_PAREN && !GO_KEYWORDS.has(name) && !GO_BUILTINS.has(name);
};

const callAt = function callAt(scan: GoCallScan, at: number): GoCallHit {
    const { src, start, end } = scan;
    const next = skipIdent(src, at, end);
    const name = src.slice(at, next);
    return {
        call: isCallName(src, next, end, name) ? { name, qualifier: qualifierBefore(src, at, start) } : null,
        next,
    };
};

const stepCalls = function stepCalls(scan: GoCallScan, at: number): GoCallHit {
    if (!isIdentStart(scan.src[at]) || isIdentPart(scan.src[at - 1])) {
        return { call: null, next: at + 1 };
    }
    return callAt(scan, at);
};

export const callsIn = function callsIn(src: string, start: number, end: number): GoCall[] {
    const scan: GoCallScan = { end, src, start };
    const calls: GoCall[] = [];
    let at = start;
    while (at < end) {
        const step = stepCalls(scan, at);
        if (step.call !== null) {
            calls.push(step.call);
        }
        at = step.next;
    }
    return calls;
};