# shared/selectors/sink.selector.ts

> 71 lines of code and 29 definitions.

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

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

## Definitions

- `errorMessageSink` (lexical_declaration, line 16, exported)
- `renderableTextAssignment` (lexical_declaration, line 54, exported)
- `streamWrite` (lexical_declaration, line 24)
- `consoleCall` (lexical_declaration, line 32)
- `terminalSink` (lexical_declaration, line 40, exported)
- `ERROR_CONSTRUCTOR_SUFFIX` (lexical_declaration, line 5)
- `PROCESS_OBJECT` (lexical_declaration, line 6)
- `CONSOLE_OBJECT` (lexical_declaration, line 7)
- `WRITE_METHOD` (lexical_declaration, line 8)
- `SET_ATTRIBUTE` (lexical_declaration, line 9)
- `ASSIGN_OPERATOR` (lexical_declaration, line 10)
- `STREAMS` (lexical_declaration, line 11)
- `CONSOLE_METHODS` (lexical_declaration, line 12)
- `RENDERABLE_TEXT_PROPERTIES` (lexical_declaration, line 13)
- `RENDERABLE_ATTRIBUTES` (lexical_declaration, line 14)
- `stream` (lexical_declaration, line 25)
- `name` (lexical_declaration, line 27)
- `isWrite` (lexical_declaration, line 28)
- `owner` (lexical_declaration, line 33)
- `method` (lexical_declaration, line 34)
- `callee` (lexical_declaration, line 41, exported)
- `sink` (lexical_declaration, line 45, exported)
- `sinkLiteral` (lexical_declaration, line 49, exported)
- `message` (lexical_declaration, line 50, exported)
- `property` (lexical_declaration, line 61, exported)
- `right` (lexical_declaration, line 65, exported)
- `setAttributeLiteral` (lexical_declaration, line 69, exported)
- `attr` (lexical_declaration, line 73, exported)
- `target` (lexical_declaration, line 77, exported)

## Uses

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

## Source

```typescript
import type { AstNode, CopyOf } from "../../types/syntax.types.ts";
import type { CopyHit, SinkArgument } from "../../types/sink.types.ts";
import { argumentAt, calleeName, isType, literalString, nameOf, nodeAt } from "./syntax.selector.ts";

const ERROR_CONSTRUCTOR_SUFFIX = "Error";
const PROCESS_OBJECT = "process";
const CONSOLE_OBJECT = "console";
const WRITE_METHOD = "write";
const SET_ATTRIBUTE = "setAttribute";
const ASSIGN_OPERATOR = "=";
const STREAMS: ReadonlySet<string> = new Set(["stdout", "stderr"]);
const CONSOLE_METHODS: ReadonlySet<string> = new Set(["debug", "error", "info", "log", "warn"]);
const RENDERABLE_TEXT_PROPERTIES: ReadonlySet<string> = new Set(["textContent"]);
const RENDERABLE_ATTRIBUTES: ReadonlySet<string> = new Set(["aria-label", "title", "placeholder"]);

export const errorMessageSink = function errorMessageSink(node: AstNode): SinkArgument | null {
    const callee = nodeAt(node, "callee");
    if (!isType(callee, "Identifier") || !nameOf(callee).endsWith(ERROR_CONSTRUCTOR_SUFFIX)) {
        return null;
    }
    return { argument: argumentAt(node, 0), sink: nameOf(callee) };
};

const streamWrite = function streamWrite(callee: AstNode | null): string | null {
    const stream = nodeAt(callee, "object");
    const owner = nodeAt(stream, "object");
    const name = nameOf(nodeAt(stream, "property"));
    const isWrite = nameOf(nodeAt(callee, "property")) === WRITE_METHOD && STREAMS.has(name);
    return isWrite && isType(owner, "Identifier") && nameOf(owner) === PROCESS_OBJECT ? name : null;
};

const consoleCall = function consoleCall(callee: AstNode | null): string | null {
    const owner = nodeAt(callee, "object");
    const method = nameOf(nodeAt(callee, "property"));
    return isType(owner, "Identifier") && nameOf(owner) === CONSOLE_OBJECT && CONSOLE_METHODS.has(method)
        ? method
        : null;
};

export const terminalSink = function terminalSink(node: AstNode): SinkArgument | null {
    const callee = nodeAt(node, "callee");
    if (!isType(callee, "MemberExpression")) {
        return null;
    }
    const sink = streamWrite(callee) ?? consoleCall(callee);
    return sink === null ? null : { argument: argumentAt(node, 0), sink };
};

export const sinkLiteral = function sinkLiteral(found: SinkArgument | null, copyOf: CopyOf): CopyHit | null {
    const message = found === null ? null : copyOf(found.argument);
    return found === null || message === null ? null : { keyName: found.sink, value: message };
};

export const renderableTextAssignment = function renderableTextAssignment(
    node: AstNode,
    copyOf: CopyOf,
): CopyHit | null {
    if (node.type !== "AssignmentExpression" || node["operator"] !== ASSIGN_OPERATOR) {
        return null;
    }
    const property = nodeAt(nodeAt(node, "left"), "property");
    if (!isType(property, "Identifier") || !RENDERABLE_TEXT_PROPERTIES.has(nameOf(property))) {
        return null;
    }
    const right = copyOf(nodeAt(node, "right"));
    return right === null ? null : { keyName: nameOf(property), value: right };
};

export const setAttributeLiteral = function setAttributeLiteral(node: AstNode, copyOf: CopyOf): CopyHit | null {
    if (node.type !== "CallExpression" || calleeName(node) !== SET_ATTRIBUTE) {
        return null;
    }
    const attr = literalString(argumentAt(node, 0));
    if (attr === null || !RENDERABLE_ATTRIBUTES.has(attr)) {
        return null;
    }
    const target = copyOf(argumentAt(node, 1));
    return target === null ? null : { keyName: attr, value: target };
};
```
