# core/formatters/config.yaml.formatter.ts

> 100 lines of code and 15 definitions.

Tree: GovLab Quality
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-formatters-config-yaml-formatter-ts
Source text: https://banes-lab.com/source/quality/core/formatters/config.yaml.formatter.ts.txt

Listed in [core/formatters](https://banes-lab.com/api/source/quality/core/formatters.md), after [core/formatters/config.xml.formatter.ts](https://banes-lab.com/source/quality/core/formatters/config.xml.formatter.ts.md) and before [core/formatters/dependency.formatter.ts](https://banes-lab.com/source/quality/core/formatters/dependency.formatter.ts.md).

## Definitions

- `inline` (lexical_declaration, line 64)
- `emitArray` (lexical_declaration, line 71)
- `scalar` (lexical_declaration, line 47)
- `rootLines` (lexical_declaration, line 97)
- `emitMap` (lexical_declaration, line 84)
- `needsQuote` (lexical_declaration, line 37)
- `isScalar` (lexical_declaration, line 60)
- `carriesTrigger` (lexical_declaration, line 33)
- `serializeYaml` (lexical_declaration, line 107, exported)
- `YAML_INDENT` (lexical_declaration, line 7)
- `YAML_RESERVED` (lexical_declaration, line 8)
- `YAML_INDICATORS` (lexical_declaration, line 9)
- `QUOTE_TRIGGERS` (lexical_declaration, line 31)
- `flow` (lexical_declaration, line 75)
- `pad` (lexical_declaration, line 85)

## Contained in

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

## Uses

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

## Linked from

- [core/formatters](https://banes-lab.com/anatomy/quality/folder-quality-core-formatters.md)
- [core/predicates](https://banes-lab.com/anatomy/quality/folder-quality-core-predicates.md)

## Source

```typescript
import type { ConfigValue } from "#types/emitter.types";
import { defineFormatter } from "#core/registries/formatter.registry";
import { everyChar } from "@govlab/constants";
import { isConfigObject } from "#core/predicates/config.predicate";
import { treeFor } from "#core/converters/emitter.converter";

const YAML_INDENT = 2;
const YAML_RESERVED = new Set(["true", "false", "null", "yes", "no", "on", "off", "~", ""]);
const YAML_INDICATORS = new Set([
    "-",
    "?",
    ":",
    ",",
    "[",
    "]",
    "{",
    "}",
    "#",
    "&",
    "*",
    "!",
    "|",
    ">",
    "'",
    '"',
    "%",
    "@",
    "`",
    " ",
]);
const QUOTE_TRIGGERS = new Set([":", "#", "\n", "\t"]);

const carriesTrigger = function carriesTrigger(value: string): boolean {
    return !everyChar(value, (ch) => !QUOTE_TRIGGERS.has(ch));
};

const needsQuote = function needsQuote(value: string): boolean {
    if (YAML_RESERVED.has(value.toLowerCase())) {
        return true;
    }
    if (value.length > 0 && YAML_INDICATORS.has(value[0] ?? "")) {
        return true;
    }
    return carriesTrigger(value) || (value !== "" && Number.isFinite(Number(value)));
};

const scalar = function scalar(value: boolean | number | string | null): string {
    if (value === null) {
        return "null";
    }
    if (typeof value === "boolean") {
        return value ? "true" : "false";
    }
    if (typeof value === "number") {
        return String(value);
    }
    return needsQuote(value) ? JSON.stringify(value) : value;
};

const isScalar = function isScalar(value: ConfigValue): value is boolean | number | string | null {
    return !isConfigObject(value) && !Array.isArray(value);
};

const inline = function inline(value: ConfigValue): string {
    if (Array.isArray(value)) {
        return "[]";
    }
    return isConfigObject(value) ? "{}" : scalar(value);
};

const emitArray = function emitArray(items: ConfigValue[], indent: number): string[] {
    const pad = " ".repeat(indent);
    return items.map((item) => {
        if (isConfigObject(item) && Object.keys(item).length > 0) {
            const flow = Object.entries(item)
                .map(([key, child]) => `${key}: ${inline(child)}`)
                .join(", ");
            return `${pad}- { ${flow} }`;
        }
        return `${pad}- ${inline(item)}`;
    });
};

const emitMap = function emitMap(obj: Record<string, ConfigValue>, indent: number): string[] {
    const pad = " ".repeat(indent);
    return Object.entries(obj).flatMap(([key, child]): string[] => {
        if (isConfigObject(child) && Object.keys(child).length > 0) {
            return [`${pad}${key}:`, ...emitMap(child, indent + YAML_INDENT)];
        }
        if (Array.isArray(child) && child.length > 0) {
            return [`${pad}${key}:`, ...emitArray(child, indent + YAML_INDENT)];
        }
        return [`${pad}${key}: ${inline(child)}`];
    });
};

const rootLines = function rootLines(tree: ConfigValue): string[] {
    if (isConfigObject(tree)) {
        return emitMap(tree, 0);
    }
    if (Array.isArray(tree)) {
        return emitArray(tree, 0);
    }
    return isScalar(tree) ? [scalar(tree)] : [];
};

export const serializeYaml = function serializeYaml(tree: ConfigValue): string {
    return `${rootLines(tree).join("\n")}\n`;
};

defineFormatter({ format: "yaml", render: (input) => serializeYaml(treeFor(input)) });
```
