# shared/selectors/literal.selector.ts

> 146 lines of code and 44 definitions.

Tree: Governance tree
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/governance#file-governance-shared-selectors-literal-selector-ts
Source text: https://banes-lab.com/source/governance/shared/selectors/literal.selector.ts.txt

Listed in [shared/selectors](https://banes-lab.com/api/source/governance/shared/selectors.md), after [shared/selectors/document.selector.ts](https://banes-lab.com/source/governance/shared/selectors/document.selector.ts.md) and before [shared/selectors/sink.selector.ts](https://banes-lab.com/source/governance/shared/selectors/sink.selector.ts.md).

## Definitions

- `codeLiteralsOf` (lexical_declaration, line 161, exported)
- `literalsOf` (lexical_declaration, line 135)
- `side` (lexical_declaration, line 55)
- `templatePartsOf` (lexical_declaration, line 33)
- `kindedTextValue` (lexical_declaration, line 116)
- `isCopy` (lexical_declaration, line 83)
- `isTextNode` (lexical_declaration, line 105)
- `concatPartsOf` (lexical_declaration, line 51)
- `copyTextOf` (lexical_declaration, line 68, exported)
- `templateShapeOf` (lexical_declaration, line 37, exported)
- `wordedTemplateOf` (lexical_declaration, line 41)
- `boundCopyOf` (lexical_declaration, line 88, exported)
- `propertyOf` (lexical_declaration, line 109)
- `diagramLiteralsOf` (lexical_declaration, line 165, exported)
- `wordedConcatOf` (lexical_declaration, line 60)
- `codeSlotValue` (lexical_declaration, line 124)
- `boundInitOf` (lexical_declaration, line 72, exported)
- `diagramSlotValue` (lexical_declaration, line 131)
- `LETTERS` (lexical_declaration, line 18)
- `MIN_WORD_LETTERS` (lexical_declaration, line 19)
- `HOLE` (lexical_declaration, line 20)
- `hasWord` (lexical_declaration, line 22)
- `run` (lexical_declaration, line 23)
- `CONCAT_OPERATOR` (lexical_declaration, line 49)
- `parts` (lexical_declaration, line 61)
- `cursor` (lexical_declaration, line 73, exported)
- `variable` (lexical_declaration, line 74, exported)
- `[definition]` (lexical_declaration, line 76, exported)
- `text` (lexical_declaration, line 84)
- `bound` (lexical_declaration, line 93, exported)
- `CODE_SLOTS` (lexical_declaration, line 98, exported)
- `DIAGRAM_KINDS` (lexical_declaration, line 99, exported)
- `CODE_KINDS` (lexical_declaration, line 100, exported)
- `KIND_SLOT` (lexical_declaration, line 101)
- `TEXT_SLOT` (lexical_declaration, line 102)
- `TEXT_NODE_TYPES` (lexical_declaration, line 103)
- `found` (lexical_declaration, line 110)
- `kind` (lexical_declaration, line 120)
- `inline` (lexical_declaration, line 139)
- `names` (lexical_declaration, line 140)
- `value` (lexical_declaration, line 142)
- `named` (lexical_declaration, line 150)
- `declared` (lexical_declaration, line 151)
- `init` (lexical_declaration, line 153)

## Uses

- [shared/selectors/syntax.selector.ts](https://banes-lab.com/source/governance/shared/selectors/syntax.selector.ts.md)

## Used by

- [rules/eslint/no-literal-strings.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/no-literal-strings.eslint.rule.ts.md)
- [rules/eslint/no-plaintext-transport.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/no-plaintext-transport.eslint.rule.ts.md)
- [rules/eslint/strings-composition.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/strings-composition.eslint.rule.ts.md)
- [rules/eslint/strings-diagram-identifiers.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/strings-diagram-identifiers.eslint.rule.ts.md)
- [rules/eslint/strings-punctuation.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/strings-punctuation.eslint.rule.ts.md)
- [rules/eslint/strings-sentence-shape.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/strings-sentence-shape.eslint.rule.ts.md)
- [rules/eslint/strings-terminology.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/strings-terminology.eslint.rule.ts.md)
- [rules/eslint/strings-vocabulary.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/strings-vocabulary.eslint.rule.ts.md)

## Source

```typescript
import type { AstNode, CopyOf } from "../../types/syntax.types.ts";
import {
    asNode,
    isType,
    literalString,
    nameOf,
    nodeAt,
    nodesAt,
    propertyKeyName,
    recordAt,
    staticTextOf,
    stringAt,
    stringIn,
    walk,
} from "./syntax.selector.ts";
import type { Scope } from "eslint";

const LETTERS: ReadonlySet<string> = new Set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
const MIN_WORD_LETTERS = 3;
const HOLE = "…";

const hasWord = function hasWord(text: string): boolean {
    let run = 0;
    for (const char of text) {
        run = LETTERS.has(char) ? run + 1 : 0;
        if (run >= MIN_WORD_LETTERS) {
            return true;
        }
    }
    return false;
};

const templatePartsOf = function templatePartsOf(node: AstNode | null): readonly string[] {
    return nodesAt(node, "quasis").map((part) => stringIn(recordAt(part, "value"), "cooked"));
};

export const templateShapeOf = function templateShapeOf(node: AstNode | null): string | null {
    return isType(node, "TemplateLiteral") ? templatePartsOf(node).join(HOLE) : null;
};

const wordedTemplateOf = function wordedTemplateOf(node: AstNode | null): string | null {
    if (!isType(node, "TemplateLiteral") || nodesAt(node, "expressions").length === 0) {
        return null;
    }
    const parts = templatePartsOf(node);
    return parts.some(hasWord) ? parts.join(HOLE) : null;
};

const CONCAT_OPERATOR = "+";

const concatPartsOf = function concatPartsOf(node: AstNode | null): readonly string[] | null {
    if (!isType(node, "BinaryExpression") || stringAt(node, "operator") !== CONCAT_OPERATOR) {
        return null;
    }
    const side = (part: AstNode | null): readonly string[] =>
        concatPartsOf(part) ?? (isType(part, "TemplateLiteral") ? templatePartsOf(part) : [literalString(part) ?? ""]);
    return [...side(nodeAt(node, "left")), ...side(nodeAt(node, "right"))];
};

const wordedConcatOf = function wordedConcatOf(node: AstNode | null): string | null {
    const parts = concatPartsOf(node);
    if (parts === null) {
        return null;
    }
    return parts.some(hasWord) ? parts.join(HOLE) : null;
};

export const copyTextOf = function copyTextOf(node: AstNode | null): string | null {
    return staticTextOf(node) ?? wordedTemplateOf(node) ?? wordedConcatOf(node);
};

export const boundInitOf = function boundInitOf(scope: Scope.Scope | null, name: string): AstNode | null {
    for (let cursor = scope; cursor !== null; cursor = cursor.upper) {
        const variable = cursor.set.get(name);
        if (variable !== undefined) {
            const [definition] = variable.defs;
            return definition === undefined ? null : nodeAt(asNode(definition.node), "init");
        }
    }
    return null;
};

const isCopy = function isCopy(node: AstNode | null): boolean {
    const text = copyTextOf(node);
    return text !== null && text.length > 0;
};

export const boundCopyOf = function boundCopyOf(scope: Scope.Scope): CopyOf {
    return (node) => {
        if (isCopy(node)) {
            return node;
        }
        const bound = isType(node, "Identifier") ? boundInitOf(scope, nameOf(node)) : null;
        return isCopy(bound) ? bound : null;
    };
};

export const CODE_SLOTS: ReadonlySet<string> = new Set(["code", "grammar"]);
export const DIAGRAM_KINDS: ReadonlySet<string> = new Set(["diagram", "mermaid"]);
export const CODE_KINDS: ReadonlySet<string> = new Set(["code", ...DIAGRAM_KINDS]);
const KIND_SLOT = "kind";
const TEXT_SLOT = "text";
const TEXT_NODE_TYPES: ReadonlySet<string> = new Set(["Literal", "TemplateLiteral"]);

const isTextNode = function isTextNode(node: AstNode | null): node is AstNode {
    return node !== null && TEXT_NODE_TYPES.has(node.type);
};

const propertyOf = function propertyOf(objectExpr: AstNode, key: string): AstNode | null {
    const found = nodesAt(objectExpr, "properties").find(
        (prop) => isType(prop, "Property") && propertyKeyName(prop) === key,
    );
    return found === undefined ? null : nodeAt(found, "value");
};

const kindedTextValue = function kindedTextValue(node: AstNode, kinds: ReadonlySet<string>): AstNode | null {
    if (!isType(node, "ObjectExpression")) {
        return null;
    }
    const kind = literalString(propertyOf(node, KIND_SLOT));
    return kind !== null && kinds.has(kind) ? propertyOf(node, TEXT_SLOT) : null;
};

const codeSlotValue = function codeSlotValue(node: AstNode): AstNode | null {
    if (isType(node, "Property") && CODE_SLOTS.has(propertyKeyName(node))) {
        return nodeAt(node, "value");
    }
    return kindedTextValue(node, CODE_KINDS);
};

const diagramSlotValue = function diagramSlotValue(node: AstNode): AstNode | null {
    return kindedTextValue(node, DIAGRAM_KINDS);
};

const literalsOf = function literalsOf(
    program: AstNode,
    slotValue: (node: AstNode) => AstNode | null,
): ReadonlySet<AstNode> {
    const inline: AstNode[] = [];
    const names: string[] = [];
    walk(program, (node) => {
        const value = slotValue(node);
        if (isTextNode(value)) {
            inline.push(value);
        }
        if (isType(value, "Identifier")) {
            names.push(nameOf(value));
        }
    });
    const named = new Set(names);
    const declared: AstNode[] = [];
    walk(program, (node) => {
        const init = nodeAt(node, "init");
        if (isType(node, "VariableDeclarator") && named.has(nameOf(nodeAt(node, "id"))) && isTextNode(init)) {
            declared.push(init);
        }
    });
    return new Set([...inline, ...declared]);
};

export const codeLiteralsOf = function codeLiteralsOf(program: AstNode): ReadonlySet<AstNode> {
    return literalsOf(program, codeSlotValue);
};

export const diagramLiteralsOf = function diagramLiteralsOf(program: AstNode): ReadonlySet<AstNode> {
    return literalsOf(program, diagramSlotValue);
};
```
