# core/analyzers/definition.analyzer.ts

> 130 lines of code and 38 definitions.

Tree: GovLab Patterns
Language: typescript
Layer: processing
Canonical: https://banes-lab.com/anatomy/patterns#file-patterns-core-analyzers-definition-analyzer-ts
Source text: https://banes-lab.com/source/patterns/core/analyzers/definition.analyzer.ts.txt

Listed in [core/analyzers](https://banes-lab.com/api/source/patterns/core/analyzers.md), after [core/analyzers/concern.analyzer.ts](https://banes-lab.com/source/patterns/core/analyzers/concern.analyzer.ts.md) and before [core/analyzers/dependency.analyzer.ts](https://banes-lab.com/source/patterns/core/analyzers/dependency.analyzer.ts.md).

## Definitions

- `duplicateGroups` (lexical_declaration, line 105)
- `duplicateFindings` (lexical_declaration, line 135, exported)
- `startsUpper` (lexical_declaration, line 24)
- `isCleanName` (lexical_declaration, line 33)
- `isModuleDefinition` (lexical_declaration, line 42)
- `deadFindings` (lexical_declaration, line 93, exported)
- `isNamedDefinition` (lexical_declaration, line 46)
- `usedNames` (lexical_declaration, line 70)
- `isExported` (lexical_declaration, line 29)
- `REL_DEAD` (lexical_declaration, line 8)
- `REL_DUP` (lexical_declaration, line 9)
- `REFERENCE_MIN` (lexical_declaration, line 10)
- `MIN_DUP_FILES` (lexical_declaration, line 11)
- `DUP_MAX_FILES` (lexical_declaration, line 12)
- `MIN_DUP_SIZE` (lexical_declaration, line 13)
- `CONFIDENCE` (lexical_declaration, line 14)
- `GO_EXT` (lexical_declaration, line 15)
- `SHELL_EXT` (lexical_declaration, line 16)
- `METHOD_KIND` (lexical_declaration, line 17)
- `GENERATED_MARKER` (lexical_declaration, line 18)
- `GO_ENTRY` (lexical_declaration, line 19)
- `SCHEMA_EXTS` (lexical_declaration, line 20)
- `TYPE_DECL_KINDS` (lexical_declaration, line 21)
- `MALFORMED_CHARS` (lexical_declaration, line 22)
- `first` (lexical_declaration, line 25)
- `isUnreferenced` (lexical_declaration, line 50)
- `isHandWritten` (lexical_declaration, line 58)
- `isDiagnosableSource` (lexical_declaration, line 62)
- `counts` (lexical_declaration, line 71)
- `deadFinding` (lexical_declaration, line 78)
- `used` (lexical_declaration, line 98, exported)
- `dead` (lexical_declaration, line 99, exported)
- `byHash` (lexical_declaration, line 106)
- `files` (lexical_declaration, line 111)
- `[first]` (lexical_declaration, line 112)
- `qualifies` (lexical_declaration, line 113)
- `byName` (lexical_declaration, line 136, exported)
- `exported` (lexical_declaration, line 137, exported)

## Contained in

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

## Uses

- [configuration/strings/code.strings.ts](https://banes-lab.com/source/patterns/configuration/strings/code.strings.ts.md)
- [core/analyzers/syntax.analyzer.ts](https://banes-lab.com/source/patterns/core/analyzers/syntax.analyzer.ts.md)
- [core/counters/base.counter.ts](https://banes-lab.com/source/patterns/core/counters/base.counter.ts.md)

## Used by

- [core/coordinators/code.coordinator.ts](https://banes-lab.com/source/patterns/core/coordinators/code.coordinator.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/patterns/folder-patterns-configuration-strings.md)
- [core/analyzers](https://banes-lab.com/anatomy/patterns/folder-patterns-core-analyzers.md)
- [core/coordinators](https://banes-lab.com/anatomy/patterns/folder-patterns-core-coordinators.md)
- [core/counters](https://banes-lab.com/anatomy/patterns/folder-patterns-core-counters.md)

## Source

```typescript
import type { CodeFinding, CodeSymbol } from "#types/code.types";
import { DEAD_DETAIL, FINDING_KINDS, REMEDY, duplicateDetail } from "#configuration/strings/code.strings";
import { HIGH_SEVERITY, MEDIUM_SEVERITY, MODULE_SCOPE } from "#configuration/constants/report.constants";
import { ROLE_DEFINITION } from "#core/classifiers/syntax.classifier";
import { identifierNames } from "#core/analyzers/syntax.analyzer";
import { increment } from "#core/counters/base.counter";

const REL_DEAD = 9;
const REL_DUP = 9;
const REFERENCE_MIN = 2;
const MIN_DUP_FILES = 2;
const DUP_MAX_FILES = 4;
const MIN_DUP_SIZE = 160;
const CONFIDENCE = "high";
const GO_EXT = ".go";
const SHELL_EXT = ".sh";
const METHOD_KIND = "method";
const GENERATED_MARKER = ".generated.";
const GO_ENTRY: ReadonlySet<string> = new Set(["main", "init"]);
const SCHEMA_EXTS: readonly string[] = [".sql", ".graphql", ".gql", ".prisma"];
const TYPE_DECL_KINDS: ReadonlySet<string> = new Set(["interface_declaration", "type_alias_declaration"]);
const MALFORMED_CHARS: ReadonlySet<string> = new Set([" ", "{", "}", "(", ")", ",", ".", '"', "'", "[", "]"]);

const startsUpper = function startsUpper(name: string): boolean {
    const first = name.charAt(0);
    return first !== "" && first === first.toUpperCase() && first !== first.toLowerCase();
};

const isExported = function isExported(symbol: CodeSymbol): boolean {
    return symbol.exported || (symbol.file.endsWith(GO_EXT) && (startsUpper(symbol.name) || GO_ENTRY.has(symbol.name)));
};

const isCleanName = function isCleanName(name: string): boolean {
    for (const char of name) {
        if (MALFORMED_CHARS.has(char)) {
            return false;
        }
    }
    return name.length > 0;
};

const isModuleDefinition = function isModuleDefinition(symbol: CodeSymbol): boolean {
    return symbol.role === ROLE_DEFINITION && symbol.enclosing === MODULE_SCOPE;
};

const isNamedDefinition = function isNamedDefinition(symbol: CodeSymbol): boolean {
    return isModuleDefinition(symbol) && isCleanName(symbol.name);
};

const isUnreferenced = function isUnreferenced(
    symbol: CodeSymbol,
    used: ReadonlySet<string>,
    testUses: ReadonlySet<string>,
): boolean {
    return !used.has(symbol.name) && !testUses.has(symbol.name);
};

const isHandWritten = function isHandWritten(symbol: CodeSymbol): boolean {
    return !symbol.kind.includes(METHOD_KIND) && !symbol.file.includes(GENERATED_MARKER);
};

const isDiagnosableSource = function isDiagnosableSource(symbol: CodeSymbol): boolean {
    return (
        !symbol.file.endsWith(SHELL_EXT) &&
        !SCHEMA_EXTS.some((ext) => symbol.file.endsWith(ext)) &&
        !TYPE_DECL_KINDS.has(symbol.kind)
    );
};

const usedNames = function usedNames(records: readonly Record<string, unknown>[]): Set<string> {
    const counts = new Map<string, number>();
    for (const name of identifierNames(records)) {
        increment(counts, name);
    }
    return new Set([...counts].filter(([, count]) => count >= REFERENCE_MIN).map(([name]) => name));
};

const deadFinding = function deadFinding(def: CodeSymbol): CodeFinding {
    return {
        confidence: CONFIDENCE,
        detail: DEAD_DETAIL,
        file: def.file,
        kind: FINDING_KINDS.deadCode,
        line: def.line,
        members: [],
        name: def.name,
        relevance: REL_DEAD,
        remedy: REMEDY.get(FINDING_KINDS.deadCode) ?? "",
        severity: MEDIUM_SEVERITY,
    };
};

export const deadFindings = function deadFindings(
    symbols: readonly CodeSymbol[],
    records: readonly Record<string, unknown>[],
    testUses: ReadonlySet<string>,
): CodeFinding[] {
    const used = usedNames(records);
    const dead = symbols
        .filter((symbol) => isNamedDefinition(symbol) && isDiagnosableSource(symbol) && !isExported(symbol))
        .filter((symbol) => isUnreferenced(symbol, used, testUses));
    return [...new Map(dead.map((def) => [`${def.file}:${def.line}`, def])).values()].map(deadFinding);
};

const duplicateGroups = function duplicateGroups(name: string, defs: readonly CodeSymbol[]): CodeFinding[] {
    const byHash = new Map<string, CodeSymbol[]>();
    for (const def of defs) {
        byHash.set(def.hash, [...(byHash.get(def.hash) ?? []), def]);
    }
    return [...byHash.values()].flatMap((group): CodeFinding[] => {
        const files = [...new Set(group.map((def) => def.file))].sort((a, b) => a.localeCompare(b));
        const [first] = group;
        const qualifies =
            files.length >= MIN_DUP_FILES && files.length <= DUP_MAX_FILES && (first?.size ?? 0) >= MIN_DUP_SIZE;
        if (!qualifies || first === undefined) {
            return [];
        }
        return [
            {
                confidence: CONFIDENCE,
                detail: duplicateDetail(files.length, files.join(", ")),
                file: first.file,
                kind: FINDING_KINDS.duplicate,
                line: first.line,
                members: files,
                name,
                relevance: REL_DUP,
                remedy: REMEDY.get(FINDING_KINDS.duplicate) ?? "",
                severity: HIGH_SEVERITY,
            },
        ];
    });
};

export const duplicateFindings = function duplicateFindings(symbols: readonly CodeSymbol[]): CodeFinding[] {
    const byName = new Map<string, CodeSymbol[]>();
    const exported = symbols.filter(
        (symbol) => isNamedDefinition(symbol) && isExported(symbol) && isHandWritten(symbol),
    );
    for (const def of exported) {
        byName.set(def.name, [...(byName.get(def.name) ?? []), def]);
    }
    return [...byName].flatMap(([name, defs]) => duplicateGroups(name, defs));
};
```
