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;
};