tools/core/analyzers/source.analyzer.ts

tools/core/analyzers/source.analyzer.ts is a file in Coordination Surface. 106 lines of code and 30 definitions.

import { isWordCharacter } from "../predicates/token.predicate.ts";

const QUOTES = new Set(["'", '"', "`"]);

const ESCAPE = "\\";

const BRACE_DELTA = new Map([
    ["{", 1],
    ["}", -1],
]);

interface QuoteState {
    readonly quote: string;
    readonly escaped: boolean;
}

interface MaskStep extends QuoteState {
    readonly code: boolean;
}

export const isWordChar = function isWordChar(char: string): boolean {
    return isWordCharacter(char) || char === "$";
};

const isLetter = function isLetter(char: string): boolean {
    return (char >= "a" && char <= "z") || (char >= "A" && char <= "Z");
};

const isAllLetters = function isAllLetters(text: string): boolean {
    for (const character of text) {
        if (!isLetter(character)) {
            return false;
        }
    }
    return true;
};

const maskStep = function maskStep(state: QuoteState, char: string): MaskStep {
    if (state.quote === "") {
        return QUOTES.has(char) ? { code: false, escaped: false, quote: char } : { ...state, code: true };
    }
    if (state.escaped) {
        return { ...state, code: false, escaped: false };
    }
    return { code: false, escaped: char === ESCAPE, quote: char === state.quote ? "" : state.quote };
};

export const codeMask = function codeMask(source: string): boolean[] {
    const mask: boolean[] = [];
    let state: QuoteState = { escaped: false, quote: "" };

    for (let index = 0; index < source.length; index += 1) {
        const step = maskStep(state, source.charAt(index));
        mask.push(step.code);
        state = step;
    }

    return mask;
};

export const matchesAt = function matchesAt(source: string, index: number, word: string): boolean {
    if (!source.startsWith(word, index)) {
        return false;
    }
    if (index > 0 && isWordChar(source.charAt(index - 1))) {
        return false;
    }
    return !isWordChar(source.charAt(index + word.length));
};

export const spacesFrom = function spacesFrom(source: string, from: number): number {
    let cursor = from;
    while (cursor < source.length && source.charAt(cursor) === " ") {
        cursor += 1;
    }
    return cursor;
};

export const wordFrom = function wordFrom(source: string, from: number): string {
    let cursor = from;
    while (cursor < source.length && isWordChar(source.charAt(cursor))) {
        cursor += 1;
    }
    return source.slice(from, cursor);
};

export const identifiersIn = function identifiersIn(text: string): string[] {
    const mask = codeMask(text);
    const out: string[] = [];
    let held = "";

    for (let index = 0; index < text.length; index += 1) {
        const char = text.charAt(index);
        if (mask[index] === true && isWordChar(char)) {
            held += char;
        } else {
            out.push(...(held.length > 0 ? [held] : []));
            held = "";
        }
    }

    return [...out, ...(held.length > 0 ? [held] : [])];
};

export const memberValue = function memberValue(trimmed: string): string {
    const colon = trimmed.indexOf(":");
    if (colon <= 0) {
        return "";
    }
    const raw = trimmed.slice(colon + 1).trim();
    const value = raw.endsWith(",") ? raw.slice(0, -1).trim() : raw;
    return isAllLetters(value) ? value : "";
};

export const memberKey = function memberKey(trimmed: string): string {
    const colon = trimmed.indexOf(":");
    const key = colon <= 0 ? "" : trimmed.slice(0, colon).trim();
    return isAllLetters(key) ? key : "";
};

export const braceDelta = function braceDelta(line: string): number {
    let delta = 0;
    for (const character of line) {
        delta += BRACE_DELTA.get(character) ?? 0;
    }
    return delta;
};