tools/core/analyzers/literal.analyzer.ts

tools/core/analyzers/literal.analyzer.ts is a file in Coordination Surface. 113 lines of code and 34 definitions.

import type { Enumeration, Quoted } from "../types/literal.types.ts";
import { accumulate, isWordCharacter } from "../predicates/token.predicate.ts";
import { Buffer } from "node:buffer";
import { endOfQuoted } from "../predicates/comment.predicate.ts";

const MODULE_SUFFIX = ".ts";

const ENUMERATORS: ReadonlySet<string> = new Set(["readdirSync", "readdir", "walk"]);

const RECURSIVE_FLAG = "recursive";

const ESCAPING_ROOTS: ReadonlySet<string> = new Set(["repoRoot", "REPO_ROOT", "projectRoot"]);

const NARROWERS: ReadonlySet<string> = new Set(["surfacePrefix", "surfacePath", "surfaceRoot"]);

const OPEN = "(";

const CLOSE = ")";

const QUOTES: ReadonlySet<string> = new Set(['"', "'", "`"]);

const ESCAPE = "\\";

const TEMPLATE_QUOTE = "`";

const BLANKS: ReadonlySet<string> = new Set([" ", "\n"]);

const MEMBER_ACCESS = ".";

const SCANNED_QUOTES: ReadonlySet<string> = new Set(['"', "'"]);

export const lineOf = function lineOf(source: string, index: number): number {
    return source.slice(0, index).split("\n").length;
};

const argumentsOf = function argumentsOf(source: string, openAt: number): string {
    let depth = 0;
    let cursor = openAt;

    do {
        const character = source.charAt(cursor);
        depth += (character === OPEN ? 1 : 0) - (character === CLOSE ? 1 : 0);
        cursor += 1;
    } while (depth > 0 && cursor < source.length);

    return depth === 0 ? source.slice(openAt + 1, cursor - 1) : "";
};

const nameEndingAt = function nameEndingAt(source: string, openAt: number): string {
    let start = openAt;
    while (start > 0 && isWordCharacter(source.charAt(start - 1))) {
        start -= 1;
    }
    return source.slice(start, openAt);
};

const enumerationAt = function enumerationAt(source: string, openAt: number): Enumeration[] {
    if (!ENUMERATORS.has(nameEndingAt(source, openAt))) {
        return [];
    }

    const tokens = accumulate(argumentsOf(source, openAt), isWordCharacter);
    const escaping = tokens.find((token) => ESCAPING_ROOTS.has(token));
    const narrowed = tokens.some((token) => NARROWERS.has(token));
    if (!tokens.includes(RECURSIVE_FLAG) || escaping === undefined || narrowed) {
        return [];
    }
    return [{ line: lineOf(source, openAt), root: escaping }];
};

export const unboundedEnumerations = function unboundedEnumerations(source: string): Enumeration[] {
    const out: Enumeration[] = [];
    let cursor = 0;

    while (cursor < source.length) {
        const character = source.charAt(cursor);
        if (QUOTES.has(character)) {
            cursor = endOfQuoted(source, cursor + 1, character);
        } else {
            out.push(...(character === OPEN ? enumerationAt(source, cursor) : []));
            cursor += 1;
        }
    }

    return out;
};

export const isPathShaped = function isPathShaped(value: string): boolean {
    if (value.length === 0) {
        return false;
    }
    if (value.endsWith(MODULE_SUFFIX)) {
        return false;
    }
    if (Buffer.isEncoding(value)) {
        return false;
    }

    return !value.includes(" ");
};

const closingQuote = function closingQuote(source: string, start: number, quote: string): number {
    let cursor = start;
    let char = source.charAt(cursor);
    const endsLine = (at: string): boolean => at === "\n" && quote !== TEMPLATE_QUOTE;

    while (cursor < source.length && char !== quote && !endsLine(char)) {
        cursor += char === ESCAPE ? 2 : 1;
        char = source.charAt(cursor);
    }

    return char === quote && cursor < source.length ? cursor : -1;
};

export const callBefore = function callBefore(source: string, quoteAt: number): string {
    let cursor = quoteAt - 1;
    while (cursor >= 0 && BLANKS.has(source.charAt(cursor))) {
        cursor -= 1;
    }
    if (source.charAt(cursor) !== OPEN) {
        return "";
    }

    const name = nameEndingAt(source, cursor);
    return source.charAt(cursor - name.length - 1) === MEMBER_ACCESS ? "" : name;
};

export const quotedLiterals = function quotedLiterals(source: string): Quoted[] {
    const out: Quoted[] = [];
    let cursor = 0;

    while (cursor < source.length) {
        const char = source.charAt(cursor);
        const close = SCANNED_QUOTES.has(char) ? closingQuote(source, cursor + 1, char) : cursor;
        if (close === -1) {
            cursor = source.length;
        } else if (close === cursor) {
            cursor += 1;
        } else {
            out.push({ close, open: cursor, value: source.slice(cursor + 1, close) });
            cursor = close + 1;
        }
    }

    return out;
};