# codemods/analyzers/field.vocabulary.analyzer.ts

> 102 lines of code and 26 definitions.

Tree: Governance tree
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/governance#file-governance-codemods-analyzers-field-vocabulary-analyzer-ts
Source text: https://banes-lab.com/source/governance/codemods/analyzers/field.vocabulary.analyzer.ts.txt

Listed in [codemods/analyzers](https://banes-lab.com/api/source/governance/codemods/analyzers.md), after [codemods/analyzers/field.reference.analyzer.ts](https://banes-lab.com/source/governance/codemods/analyzers/field.reference.analyzer.ts.md) and before [codemods/analyzers/identifier.analyzer.ts](https://banes-lab.com/source/governance/codemods/analyzers/identifier.analyzer.ts.md).

## Definitions

- `isPlainString` (lexical_declaration, line 31)
- `isLiteralSet` (lexical_declaration, line 8)
- `valuesUnder` (lexical_declaration, line 95)
- `plainValueAt` (lexical_declaration, line 64)
- `vocabularyOf` (lexical_declaration, line 13)
- `plainClosedValues` (lexical_declaration, line 105, exported)
- `targetOf` (lexical_declaration, line 40)
- `parts` (lexical_declaration, line 9)
- `named` (lexical_declaration, line 18)
- `Target` (interface_declaration, line 35)
- `declared` (lexical_declaration, line 44)
- `name` (lexical_declaration, line 45)
- `property` (lexical_declaration, line 47)
- `declaration` (lexical_declaration, line 48)
- `key` (lexical_declaration, line 49)
- `assignedValue` (lexical_declaration, line 57)
- `value` (lexical_declaration, line 69)
- `vocabulary` (lexical_declaration, line 73)
- `target` (lexical_declaration, line 74)
- `source` (lexical_declaration, line 78)
- `childrenOf` (lexical_declaration, line 87)
- `children` (lexical_declaration, line 88)
- `here` (lexical_declaration, line 100)
- `below` (lexical_declaration, line 101)
- `program` (lexical_declaration, line 106, exported)
- `checker` (lexical_declaration, line 107, exported)

## Uses

- [codemods/selectors/field.selector.ts](https://banes-lab.com/source/governance/codemods/selectors/field.selector.ts.md)
- [codemods/selectors/program.selector.ts](https://banes-lab.com/source/governance/codemods/selectors/program.selector.ts.md)
- [rules/eslint/closure-no-cross-member-relative.eslint.rule.ts](https://banes-lab.com/source/governance/rules/eslint/closure-no-cross-member-relative.eslint.rule.ts.md)

## Source

```typescript
import { declaredTarget, innerTypes, keyOf, within } from "../selectors/field.selector.ts";
import { lineOf, programFor, toPosix } from "../selectors/program.selector.ts";
import type { FieldScope } from "../../types/field.types.ts";
import type { PlainClosedValue } from "../../types/analyzer.types.ts";
import { relative } from "node:path";
import ts from "typescript";

const isLiteralSet = function isLiteralSet(checker: ts.TypeChecker, type: ts.Type): boolean {
    const parts = innerTypes(checker, type);
    return parts.length > 0 && parts.every((part) => part.isStringLiteral());
};

const vocabularyOf = function vocabularyOf(
    checker: ts.TypeChecker,
    value: ts.Expression,
    typeRoots: readonly string[],
): string | null {
    const named = ts.isPropertyAccessExpression(value) ? value.name : value;
    const declaration = checker.getSymbolAtLocation(named)?.declarations?.[0];
    if (
        declaration === undefined ||
        !ts.isPropertySignature(declaration) ||
        declaration.type === undefined ||
        !within(declaration.getSourceFile().fileName, typeRoots)
    ) {
        return null;
    }
    return isLiteralSet(checker, checker.getTypeAtLocation(value)) ? declaration.type.getText() : null;
};

const isPlainString = function isPlainString(checker: ts.TypeChecker, type: ts.Type): boolean {
    return innerTypes(checker, type).some((part) => (part.flags & ts.TypeFlags.String) !== 0);
};

interface Target {
    readonly key: string;
    readonly type: ts.Type;
}

const targetOf = function targetOf(
    checker: ts.TypeChecker,
    node: ts.PropertyAssignment | ts.ShorthandPropertyAssignment,
): Target | null {
    const declared = ts.isObjectLiteralExpression(node.parent) ? declaredTarget(checker, node.parent) : undefined;
    const name = node.name.getText();
    for (const type of declared === undefined ? [] : innerTypes(checker, declared)) {
        const property = type.getProperty(name);
        const declaration = property?.declarations?.[0];
        const key = declaration === undefined ? null : keyOf(declaration);
        if (property !== undefined && key !== null) {
            return { key, type: checker.getTypeOfSymbol(property) };
        }
    }
    return null;
};

const assignedValue = function assignedValue(node: ts.Node): ts.Expression | null {
    if (ts.isPropertyAssignment(node)) {
        return node.initializer;
    }
    return ts.isShorthandPropertyAssignment(node) ? node.name : null;
};

const plainValueAt = function plainValueAt(
    checker: ts.TypeChecker,
    node: ts.Node,
    scope: FieldScope,
): PlainClosedValue | null {
    const value = assignedValue(node);
    if (value === null || !(ts.isPropertyAssignment(node) || ts.isShorthandPropertyAssignment(node))) {
        return null;
    }
    const vocabulary = vocabularyOf(checker, value, scope.typeRoots);
    const target = vocabulary === null ? null : targetOf(checker, node);
    if (vocabulary === null || target === null || !isPlainString(checker, target.type)) {
        return null;
    }
    const source = node.getSourceFile();
    return {
        file: toPosix(relative(process.cwd(), source.fileName)),
        line: lineOf(source, node),
        target: target.key,
        vocabulary,
    };
};

const childrenOf = function childrenOf(node: ts.Node): readonly ts.Node[] {
    const children: ts.Node[] = [];
    ts.forEachChild(node, (child) => {
        children.push(child);
    });
    return children;
};

const valuesUnder = function valuesUnder(
    checker: ts.TypeChecker,
    node: ts.Node,
    scope: FieldScope,
): readonly PlainClosedValue[] {
    const here = plainValueAt(checker, node, scope);
    const below = childrenOf(node).flatMap((child) => valuesUnder(checker, child, scope));
    return here === null ? below : [here, ...below];
};

export const plainClosedValues = function plainClosedValues(scope: FieldScope): readonly PlainClosedValue[] {
    const program = programFor(scope.tsconfig);
    const checker = program.getTypeChecker();
    return program
        .getSourceFiles()
        .filter((source) => !source.isDeclarationFile && within(source.fileName, scope.readerRoots))
        .flatMap((source) => valuesUnder(checker, source, scope));
};
```
