# tools/rules/literal.rule.ts

> 243 lines of code and 53 definitions.

Tree: Coordination tree
Language: typescript
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-rules-literal-rule-ts
Source text: https://banes-lab.com/assets/sources/source.362ab7fbb5ce31c4eb2e936e57eb3ca68aadfa6c43ac1f672fa44fb42234eb7f.generated.txt

## Definitions

- `lineOf` (lexical_declaration, line 57)
- `enumerationAt` (lexical_declaration, line 89)
- `unboundedEnumerations` (lexical_declaration, line 103, exported)
- `nameEndingAt` (lexical_declaration, line 74)
- `endsLine` (lexical_declaration, line 141)
- `closingQuote` (lexical_declaration, line 138)
- `rawPathFinding` (lexical_declaration, line 196)
- `check` (method_definition, line 236, exported)
- `COMPOSERS` (lexical_declaration, line 9)
- `FILESYSTEM_CALLS` (lexical_declaration, line 11)
- `MODULE_SUFFIX` (lexical_declaration, line 26)
- `DECIDE` (lexical_declaration, line 28)
- `ENUMERATORS` (lexical_declaration, line 35)
- `RECURSIVE_FLAG` (lexical_declaration, line 37)
- `ESCAPING_ROOTS` (lexical_declaration, line 39)
- `NARROWERS` (lexical_declaration, line 41)
- `ENUMERATION_DECIDE` (lexical_declaration, line 43)
- `OPEN` (lexical_declaration, line 53)
- `CLOSE` (lexical_declaration, line 55)
- `argumentsOf` (lexical_declaration, line 61)
- `depth` (lexical_declaration, line 62)
- `start` (lexical_declaration, line 75)
- `Enumeration` (interface_declaration, line 82)
- `QUOTES` (lexical_declaration, line 87)
- `tokens` (lexical_declaration, line 94)
- `escaping` (lexical_declaration, line 95)
- `narrowed` (lexical_declaration, line 96)
- `character` (lexical_declaration, line 108, exported)
- `isPathShaped` (lexical_declaration, line 120)
- `ESCAPE` (lexical_declaration, line 134)
- `TEMPLATE_QUOTE` (lexical_declaration, line 136)
- `BLANKS` (lexical_declaration, line 151)
- `MEMBER_ACCESS` (lexical_declaration, line 153)
- `callBefore` (lexical_declaration, line 155)
- `name` (lexical_declaration, line 164)
- `Quoted` (interface_declaration, line 168)
- `SCANNED_QUOTES` (lexical_declaration, line 174)
- `quotedLiterals` (lexical_declaration, line 176)
- `out` (lexical_declaration, line 177, exported)
- `cursor` (lexical_declaration, line 178, exported)
- `char` (lexical_declaration, line 181)
- `close` (lexical_declaration, line 182)
- `LiteralScan` (interface_declaration, line 220)
- `scanLiterals` (lexical_declaration, line 225)
- `shaped` (lexical_declaration, line 226)
- `composer` (lexical_declaration, line 228)
- `raw` (lexical_declaration, line 229)
- `rule` (lexical_declaration, line 235, exported)
- `findings` (lexical_declaration, line 237, exported)
- `scoped` (lexical_declaration, line 238, exported)
- `literals` (lexical_declaration, line 239, exported)
- `source` (lexical_declaration, line 242, exported)
- `scan` (lexical_declaration, line 243, exported)

## Uses

- [tools/core/predicates/comment.predicate.ts](https://banes-lab.com/source/coordination/tools/core/predicates/comment.predicate.ts.md)
- [tools/core/predicates/token.predicate.ts](https://banes-lab.com/source/coordination/tools/core/predicates/token.predicate.ts.md)

## Source

```typescript
import type { RuleContext, RuleDeclaration, RuleResult } from "../core/types/rule.types.ts";
import { accumulate, isWordCharacter } from "../core/predicates/token.predicate.ts";
import { AUTHORED_ROOTS } from "../core/constants/path.constants.ts";
import { Buffer } from "node:buffer";
import type { Finding } from "../core/types/segment.types.ts";
import { endOfQuoted } from "../core/predicates/comment.predicate.ts";
import { underRoots } from "../core/filters/scope.filter.ts";

const COMPOSERS: ReadonlySet<string> = new Set(["within", "withinSurface", "surfacePath", "slotText", "inSurface"]);

const FILESYSTEM_CALLS: ReadonlySet<string> = new Set([
    "join",
    "resolve",
    "existsSync",
    "readFileSync",
    "readdirSync",
    "writeFileSync",
    "statSync",
    "mkdirSync",
    "rmdirSync",
    "renameSync",
    "walk",
    "readSource",
]);

const MODULE_SUFFIX = ".ts";

const DECIDE =
    "a path spelled in source is a claim about a tree this package does not own — it resolves onto the " +
    "consuming project or onto nothing, differently in every consumer, and the failure surfaces as a check " +
    "that silently governs the wrong files or refuses citations that are true. Every root resolves through " +
    "the parameter surface, which computes its prefix from its own file location, so the literal becomes an " +
    "argument to a composer or a declared slot rather than a value a reader trusts";

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 ENUMERATION_DECIDE =
    "a RECURSIVE enumeration rooted at a path that resolves outside the declared surface prefix lists a tree " +
    "this package does not own — in a consumer that tree is the whole host repository, including everything " +
    "its dependencies install, so the call reads correct and costs minutes rather than milliseconds and the " +
    "failure presents as a run that hangs rather than as anything a verdict reports. The discriminator is " +
    "ENUMERATION rather than the root: resolving a NAMED path through the same root walks nothing and is the " +
    "correct form, which is why the safe uses far outnumber the hazardous one and a scan for the root alone " +
    "reports mostly noise. Narrow the enumeration to the surface's own prefix and prepend it to each entry, so " +
    "the listing covers exactly what this package declares";

const OPEN = "(";

const CLOSE = ")";

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

interface Enumeration {
    readonly line: number;
    readonly root: string;
}

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

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

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 ESCAPE = "\\";

const TEMPLATE_QUOTE = "`";

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

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

const MEMBER_ACCESS = ".";

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

interface Quoted {
    readonly open: number;
    readonly close: number;
    readonly value: string;
}

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

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

const rawPathFinding = function rawPathFinding(path: string, source: string, literal: Quoted, composer: string): Finding {
    return {
        actual: literal.value,
        expected: null,
        healed: false,
        line: lineOf(source, literal.close),
        locus: literal.value,
        path,
        remediation: {
            action: "declare",
            decide: DECIDE,
            deterministic: false,
            from: literal.value,
            target: path,
            to: null,
        },
        rule: "literal/rawPath",
        stack: [
            { check: "shape", resolved: "path" },
            { check: "composer", resolved: composer.length === 0 ? "none" : composer },
        ],
    };
};

interface LiteralScan {
    readonly findings: Finding[];
    readonly literals: number;
}

const scanLiterals = function scanLiterals(path: string, source: string): LiteralScan {
    const shaped = quotedLiterals(source).filter((literal) => isPathShaped(literal.value));
    const findings = shaped.flatMap((literal) => {
        const composer = callBefore(source, literal.open);
        const raw = FILESYSTEM_CALLS.has(composer) && !COMPOSERS.has(composer);
        return raw ? [rawPathFinding(path, source, literal, composer)] : [];
    });
    return { findings, literals: shaped.length };
};

export const rule: RuleDeclaration = {
    check(context: RuleContext): RuleResult {
        const findings: Finding[] = [];
        const scoped = underRoots(context.paths, AUTHORED_ROOTS);
        let literals = 0;

        for (const path of scoped) {
            const source = context.read(path);
            const scan = scanLiterals(path, source);
            literals += scan.literals;

            for (const found of unboundedEnumerations(source)) {
                findings.push({
                    actual: `a recursive enumeration rooted at ${found.root}, which resolves outside the declared surface prefix`,
                    expected: "an enumeration narrowed to the surface's own prefix",
                    healed: false,
                    line: found.line,
                    locus: found.root,
                    path,
                    remediation: {
                        action: "declare",
                        decide: ENUMERATION_DECIDE,
                        deterministic: false,
                        from: found.root,
                        target: path,
                        to: null,
                    },
                    rule: "literal/unboundedEnumeration",
                    stack: [
                        { check: "call", resolved: "recursive enumeration" },
                        { check: "root", resolved: found.root },
                        { check: "narrowed", resolved: "no" },
                    ],
                });
            }

            findings.push(...scan.findings);
        }

        return {
            derivations: {
                literals,
                reached: scoped,
                skippedAsOutsideAuthoredRoots: context.paths.filter((path) => !scoped.includes(path)),
            },
            findings,
            healed: [],
        };
    },
    extensions: [".ts"],
    heals: false,
    invariant: "a path-shaped string literal in authored source is composed through the parameter surface",
    jurisdiction: "all",
    kinds: ["rawPath", "unboundedEnumeration"],

    stage: "structure",
};
```
