# tools/core/normalizers/source.normalizer.ts

> 104 lines of code and 32 definitions.

Tree: Coordination tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/coordination#file-coordination-tools-core-normalizers-source-normalizer-ts
Source text: https://banes-lab.com/assets/sources/source.41c8c35b9ace08a04de89d0872def8d0b5009e0ceacb9c52f4a7e26d2cc76368.generated.txt

## Definitions

- `typescriptComments` (lexical_declaration, line 60, exported)
- `lineSpan` (lexical_declaration, line 50)
- `scanComments` (lexical_declaration, line 31)
- `takenBetween` (lexical_declaration, line 46)
- `hashComments` (lexical_declaration, line 115, exported)
- `blockSpan` (lexical_declaration, line 55)
- `isBlank` (lexical_declaration, line 80)
- `withoutSpan` (lexical_declaration, line 96)
- `commentsOf` (lexical_declaration, line 121, exported)
- `scannedAt` (lexical_declaration, line 19)
- `isKept` (lexical_declaration, line 84)
- `indentStart` (lexical_declaration, line 88)
- `stripComments` (lexical_declaration, line 103, exported)
- `Taken` (interface_declaration, line 5)
- `Scanned` (interface_declaration, line 10)
- `Take` (type_alias_declaration, line 15)
- `BLOCK_CLOSE` (lexical_declaration, line 17)
- `char` (lexical_declaration, line 20)
- `taken` (lexical_declaration, line 25)
- `spans` (lexical_declaration, line 32)
- `cursor` (lexical_declaration, line 33)
- `scanned` (lexical_declaration, line 36)
- `newline` (lexical_declaration, line 51)
- `close` (lexical_declaration, line 56)
- `marker` (lexical_declaration, line 66, exported)
- `StripResult` (interface_declaration, line 74, exported)
- `from` (lexical_declaration, line 97)
- `atLineStart` (lexical_declaration, line 98)
- `to` (lexical_declaration, line 99)
- `shebang` (lexical_declaration, line 104, exported)
- `dropped` (lexical_declaration, line 105, exported)
- `text` (lexical_declaration, line 106, exported)

## Uses

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

## Source

```typescript
import { type CommentSpan, at, endOfQuoted, isAttribution } from "../predicates/comment.predicate.ts";

export { type CommentSpan, isAttribution } from "../predicates/comment.predicate.ts";

interface Taken {
    readonly end: number;
    readonly span: CommentSpan;
}

interface Scanned {
    readonly next: number;
    readonly span: CommentSpan | null;
}

type Take = (source: string, cursor: number) => Taken | null;

const BLOCK_CLOSE = "*/";

const scannedAt = function scannedAt(source: string, cursor: number, quotes: readonly string[], take: Take): Scanned {
    const char = at(source, cursor);
    if (quotes.includes(char)) {
        return { next: endOfQuoted(source, cursor + 1, char), span: null };
    }

    const taken = take(source, cursor);
    return taken === null
        ? { next: cursor + 1, span: null }
        : { next: Math.max(taken.end, cursor + 1), span: taken.span };
};

const scanComments = function scanComments(source: string, quotes: readonly string[], take: Take): CommentSpan[] {
    const spans: CommentSpan[] = [];
    let cursor = 0;

    while (cursor < source.length) {
        const scanned = scannedAt(source, cursor, quotes, take);
        if (scanned.span !== null) {
            spans.push(scanned.span);
        }
        cursor = scanned.next;
    }

    return spans;
};

const takenBetween = function takenBetween(source: string, start: number, end: number): Taken {
    return { end, span: { end, start, text: source.slice(start, end) } };
};

const lineSpan = function lineSpan(source: string, start: number): Taken {
    const newline = source.indexOf("\n", start);
    return takenBetween(source, start, newline === -1 ? source.length : newline);
};

const blockSpan = function blockSpan(source: string, start: number): Taken {
    const close = source.indexOf(BLOCK_CLOSE, start + 2);
    return takenBetween(source, start, close === -1 ? source.length : close + BLOCK_CLOSE.length);
};

export const typescriptComments = function typescriptComments(source: string): CommentSpan[] {
    return scanComments(source, ['"', "'", "`"], (text, cursor) => {
        if (at(text, cursor) !== "/") {
            return null;
        }

        const marker = at(text, cursor + 1);
        if (marker === "/") {
            return lineSpan(text, cursor);
        }
        return marker === "*" ? blockSpan(text, cursor) : null;
    });
};

export interface StripResult {
    readonly text: string;
    readonly removed: number;
    readonly kept: number;
}

const isBlank = function isBlank(char: string): boolean {
    return char === " " || char === "\t";
};

const isKept = function isKept(span: CommentSpan, shebang: boolean): boolean {
    return isAttribution(span.text) || (shebang && span.start === 0);
};

const indentStart = function indentStart(text: string, start: number): number {
    let from = start;
    while (from > 0 && isBlank(at(text, from - 1))) {
        from -= 1;
    }
    return from;
};

const withoutSpan = function withoutSpan(text: string, span: CommentSpan): string {
    const from = indentStart(text, span.start);
    const atLineStart = from === 0 || at(text, from - 1) === "\n";
    const to = atLineStart && at(text, span.end) === "\n" ? span.end + 1 : span.end;
    return text.slice(0, from) + text.slice(to);
};

export const stripComments = function stripComments(source: string, spans: readonly CommentSpan[]): StripResult {
    const shebang = source.startsWith("#!");
    const dropped = spans.filter((span) => !isKept(span, shebang));
    let text = source;

    for (const span of dropped.toReversed()) {
        text = withoutSpan(text, span);
    }

    return { kept: spans.length - dropped.length, removed: dropped.length, text };
};

export const hashComments = function hashComments(source: string): CommentSpan[] {
    return scanComments(source, ['"', "'"], (text, cursor) =>
        at(text, cursor) === "#" ? lineSpan(text, cursor) : null,
    );
};

export const commentsOf = function commentsOf(path: string, source: string): CommentSpan[] {
    if (path.endsWith(".ts")) {
        return typescriptComments(source);
    }
    if (path.endsWith(".toml")) {
        return hashComments(source);
    }
    return path.endsWith(".json") || path.endsWith(".jsonc") ? typescriptComments(source) : [];
};
```
