# core/normalizers/diagram.normalizer.ts

> 85 lines of code and 27 definitions.

Tree: GovLab Docs
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/docs#file-docs-core-normalizers-diagram-normalizer-ts
Source text: https://banes-lab.com/source/docs/core/normalizers/diagram.normalizer.ts.txt

Listed in [core/normalizers](https://banes-lab.com/api/source/docs/core/normalizers.md), before [core/normalizers/reference.normalizer.ts](https://banes-lab.com/source/docs/core/normalizers/reference.normalizer.ts.md).

## Definitions

- `idSanitize` (lexical_declaration, line 52, exported)
- `nodeId` (lexical_declaration, line 91, exported)
- `isIdStart` (lexical_declaration, line 16)
- `collapseWhitespace` (lexical_declaration, line 20)
- `labelChar` (lexical_declaration, line 34)
- `hash6` (lexical_declaration, line 63)
- `label` (lexical_declaration, line 44, exported)
- `alnumOnly` (lexical_declaration, line 81)
- `FORBIDDEN_LABEL_CHARS` (lexical_declaration, line 4)
- `SEGMENT_BREAKS` (lexical_declaration, line 5)
- `MAX_ASCII` (lexical_declaration, line 6)
- `HASH_SEED` (lexical_declaration, line 7)
- `HASH_MULTIPLIER` (lexical_declaration, line 8)
- `HASH_MODULUS` (lexical_declaration, line 9)
- `HASH_RADIX` (lexical_declaration, line 10)
- `HASH_ID_WIDTH` (lexical_declaration, line 11)
- `FALLBACK_ID` (lexical_declaration, line 12)
- `ID_PAD` (lexical_declaration, line 13)
- `ID_FILL` (lexical_declaration, line 14)
- `pendingSpace` (lexical_declaration, line 22)
- `hash` (lexical_declaration, line 64)
- `lastSegment` (lexical_declaration, line 71)
- `cut` (lexical_declaration, line 72)
- `at` (lexical_declaration, line 73)
- `out` (lexical_declaration, line 82, exported)
- `head` (lexical_declaration, line 92, exported)
- `start` (lexical_declaration, line 93, exported)

## Contained in

- [core/normalizers](https://banes-lab.com/anatomy/docs/folder-docs-core-normalizers.md)

## Uses

- [core/predicates/character.predicate.ts](https://banes-lab.com/source/docs/core/predicates/character.predicate.ts.md)

## Used by

- [core/analyzers/code.go.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/code.go.analyzer.ts.md)
- [core/analyzers/definition.analyzer.ts](https://banes-lab.com/source/docs/core/analyzers/definition.analyzer.ts.md)
- [core/factories/code.typescript.factory.ts](https://banes-lab.com/source/docs/core/factories/code.typescript.factory.ts.md)
- [core/formatters/diagram.formatter.ts](https://banes-lab.com/source/docs/core/formatters/diagram.formatter.ts.md)
- [core/formatters/diagram.machine.formatter.ts](https://banes-lab.com/source/docs/core/formatters/diagram.machine.formatter.ts.md)
- [core/formatters/diagram.sequence.formatter.ts](https://banes-lab.com/source/docs/core/formatters/diagram.sequence.formatter.ts.md)
- [core/formatters/diagram.structure.formatter.ts](https://banes-lab.com/source/docs/core/formatters/diagram.structure.formatter.ts.md)
- [core/visitors/code.typescript.visitor.ts](https://banes-lab.com/source/docs/core/visitors/code.typescript.visitor.ts.md)

## Linked from

- [core/analyzers](https://banes-lab.com/anatomy/docs/folder-docs-core-analyzers.md)
- [core/factories](https://banes-lab.com/anatomy/docs/folder-docs-core-factories.md)
- [core/formatters](https://banes-lab.com/anatomy/docs/folder-docs-core-formatters.md)
- [core/normalizers](https://banes-lab.com/anatomy/docs/folder-docs-core-normalizers.md)
- [core/predicates](https://banes-lab.com/anatomy/docs/folder-docs-core-predicates.md)
- [core/visitors](https://banes-lab.com/anatomy/docs/folder-docs-core-visitors.md)

## Source

```typescript
import { isAlpha, isDigit } from "@govlab/constants";
import { isAsciiAlnum } from "#core/predicates/character.predicate";

const FORBIDDEN_LABEL_CHARS: ReadonlySet<string> = new Set(["(", ")", "[", "]", "{", "}", "<", ">", "&", '"', "|"]);
const SEGMENT_BREAKS: ReadonlySet<string> = new Set(["/", ".", "#", ":"]);
const MAX_ASCII = 126;
const HASH_SEED = 5381;
const HASH_MULTIPLIER = 33;
const HASH_MODULUS = 2_147_483_648;
const HASH_RADIX = 16;
const HASH_ID_WIDTH = 6;
const FALLBACK_ID = "n";
const ID_PAD = "0";
const ID_FILL = "_";

const isIdStart = function isIdStart(char: string): boolean {
    return isAlpha(char) || char === ID_FILL;
};

const collapseWhitespace = function collapseWhitespace(text: string): string {
    let out = "";
    let pendingSpace = false;
    for (const char of text) {
        if (char === " " || char === "\t") {
            pendingSpace = out.length > 0;
            continue;
        }
        out += pendingSpace ? ` ${char}` : char;
        pendingSpace = false;
    }
    return out;
};

const labelChar = function labelChar(char: string): string {
    if ((char.codePointAt(0) ?? 0) > MAX_ASCII) {
        return " ";
    }
    if (char === ";") {
        return ",";
    }
    return FORBIDDEN_LABEL_CHARS.has(char) ? " " : char;
};

export const label = function label(text: string): string {
    let out = "";
    for (const char of text) {
        out += labelChar(char);
    }
    return collapseWhitespace(out);
};

export const idSanitize = function idSanitize(id: string): string {
    let out = "";
    for (const char of id) {
        out += isAsciiAlnum(char) ? char : ID_FILL;
    }
    if (out.length === 0) {
        return FALLBACK_ID;
    }
    return isIdStart(out.charAt(0)) ? out : `${FALLBACK_ID}${ID_FILL}${out}`;
};

const hash6 = function hash6(text: string): string {
    let hash = HASH_SEED;
    for (let at = 0; at < text.length; at += 1) {
        hash = (hash * HASH_MULTIPLIER + (text.codePointAt(at) ?? 0)) % HASH_MODULUS;
    }
    return hash.toString(HASH_RADIX).padStart(HASH_ID_WIDTH, ID_PAD).slice(0, HASH_ID_WIDTH);
};

const lastSegment = function lastSegment(fqName: string): string {
    let cut = 0;
    for (let at = 0; at < fqName.length; at += 1) {
        if (SEGMENT_BREAKS.has(fqName.charAt(at))) {
            cut = at + 1;
        }
    }
    return fqName.slice(cut);
};

const alnumOnly = function alnumOnly(text: string): string {
    let out = "";
    for (const char of text) {
        if (isAsciiAlnum(char)) {
            out += char;
        }
    }
    return out;
};

export const nodeId = function nodeId(fqName: string): string {
    const head = alnumOnly(lastSegment(fqName)) || FALLBACK_ID;
    const start = isDigit(head.charAt(0)) ? `${FALLBACK_ID}${head}` : head;
    return `${start}${ID_FILL}${hash6(fqName)}`;
};
```
