# tools/core/analyzers/marker.analyzer.ts

> 109 lines of code and 28 definitions.

Tree: Coordination tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-core-analyzers-marker-analyzer-ts
Source text: https://banes-lab.com/assets/sources/source.00a847151f798cbe1292dca5d301778b3bb438deb4a452bfd7840f015a4d2d2c.generated.txt

## Definitions

- `carriesMarker` (lexical_declaration, line 117, exported)
- `standsAlone` (lexical_declaration, line 111)
- `joinedToWord` (lexical_declaration, line 88)
- `terminatesClause` (lexical_declaration, line 99)
- `balanced` (lexical_declaration, line 49)
- `spansFor` (lexical_declaration, line 60)
- `quotedSpans` (lexical_declaration, line 75)
- `QUOTES` (lexical_declaration, line 3)
- `normalizedWords` (lexical_declaration, line 5)
- `out` (lexical_declaration, line 6)
- `word` (lexical_declaration, line 7)
- `character` (lexical_declaration, line 10)
- `isLetter` (lexical_declaration, line 11)
- `isDigit` (lexical_declaration, line 12)
- `repeatedSpan` (lexical_declaration, line 30, exported)
- `words` (lexical_declaration, line 31, exported)
- `seen` (lexical_declaration, line 36, exported)
- `start` (lexical_declaration, line 38, exported)
- `run` (lexical_declaration, line 39, exported)
- `count` (lexical_declaration, line 50)
- `inside` (lexical_declaration, line 61)
- `index` (lexical_declaration, line 63)
- `cursor` (lexical_declaration, line 100)
- `before` (lexical_declaration, line 112)
- `after` (lexical_declaration, line 113)
- `quoted` (lexical_declaration, line 118, exported)
- `from` (lexical_declaration, line 119, exported)
- `at` (lexical_declaration, line 122, exported)

## Uses

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

## Used by

- [tools/core/validators/checklist.validator.ts](https://banes-lab.com/source/coordination/tools/core/validators/checklist.validator.ts.md)
- [tools/rules/board.rule.ts](https://banes-lab.com/source/coordination/tools/rules/board.rule.ts.md)

## Source

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

const QUOTES = ["`", '"'];

const normalizedWords = function normalizedWords(text: string): string[] {
    const out: string[] = [];
    let word = "";

    for (let index = 0; index < text.length; index += 1) {
        const character = text.charAt(index).toLowerCase();
        const isLetter = character >= "a" && character <= "z";
        const isDigit = character >= "0" && character <= "9";

        if (isLetter || isDigit) {
            word += character;
            continue;
        }
        if (word.length > 0) {
            out.push(word);
        }
        word = "";
    }

    if (word.length > 0) {
        out.push(word);
    }
    return out;
};

export const repeatedSpan = function repeatedSpan(text: string, span: number): string | null {
    const words = normalizedWords(text);
    if (words.length < span * 2) {
        return null;
    }

    const seen = new Set<string>();

    for (let start = 0; start + span <= words.length; start += 1) {
        const run = words.slice(start, start + span).join(" ");
        if (seen.has(run)) {
            return run;
        }
        seen.add(run);
    }

    return null;
};

const balanced = function balanced(line: string, delimiter: string): boolean {
    let count = 0;
    for (let index = 0; index < line.length; index += 1) {
        if (line.charAt(index) === delimiter) {
            count += 1;
        }
    }

    return count > 0 && count % 2 === 0;
};

const spansFor = function spansFor(line: string, delimiter: string, quoted: boolean[]): void {
    let inside = false;

    for (let index = 0; index < line.length; index += 1) {
        if (line.charAt(index) === delimiter) {
            inside = !inside;
            quoted[index] = true;
            continue;
        }
        if (inside) {
            quoted[index] = true;
        }
    }
};

const quotedSpans = function quotedSpans(line: string): boolean[] {
    const quoted = Array.from({ length: line.length }, () => false);

    for (const delimiter of QUOTES) {
        if (!balanced(line, delimiter)) {
            continue;
        }
        spansFor(line, delimiter, quoted);
    }

    return quoted;
};

const joinedToWord = function joinedToWord(line: string, at: number): boolean {
    if (at === 0) {
        return false;
    }
    if (line.charAt(at - 1) !== "-") {
        return false;
    }

    return isWordCharacter(at < 2 ? "" : line.charAt(at - 2));
};

const terminatesClause = function terminatesClause(line: string, after: number): boolean {
    let cursor = after;
    while (cursor < line.length && line.charAt(cursor) === " ") {
        cursor += 1;
    }
    if (cursor >= line.length) {
        return true;
    }

    return !isWordCharacter(line.charAt(cursor));
};

const standsAlone = function standsAlone(line: string, at: number, marker: string): boolean {
    const before = at === 0 ? "" : line.charAt(at - 1);
    const after = line.charAt(at + marker.length);
    return !isWordCharacter(before) && !isWordCharacter(after) && !joinedToWord(line, at);
};

export const carriesMarker = function carriesMarker(line: string, marker: string): boolean {
    const quoted = quotedSpans(line);
    let from = 0;

    while (from <= line.length - marker.length) {
        const at = line.indexOf(marker, from);
        if (at === -1) {
            return false;
        }

        if (standsAlone(line, at, marker) && quoted[at] !== true && terminatesClause(line, at + marker.length)) {
            return true;
        }

        from = at + 1;
    }

    return false;
};
```
