# core/adapters/eslint.adapter.ts

> 83 lines of code and 15 definitions.

Tree: GovLab Quality
Language: typescript
Layer: infrastructure
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-adapters-eslint-adapter-ts
Source text: https://banes-lab.com/source/quality/core/adapters/eslint.adapter.ts.txt

Listed in [core/adapters](https://banes-lab.com/api/source/quality/core/adapters.md), after [core/adapters/dependency.adapter.ts](https://banes-lab.com/source/quality/core/adapters/dependency.adapter.ts.md) and before [core/adapters/invocation.adapter.ts](https://banes-lab.com/source/quality/core/adapters/invocation.adapter.ts.md).

## Definitions

- `eslintFindings` (lexical_declaration, line 47)
- `isAllIgnored` (lexical_declaration, line 16)
- `applyCurated` (lexical_declaration, line 31)
- `fixedRun` (lexical_declaration, line 64)
- `lintOne` (lexical_declaration, line 20)
- `fixSummary` (lexical_declaration, line 40)
- `runEslint` (lexical_declaration, line 69, exported)
- `ESLINT_TOOL` (lexical_declaration, line 14)
- `applied` (lexical_declaration, line 33)
- `overrideConfig` (lexical_declaration, line 70, exported)
- `eslint` (lexical_declaration, line 71, exported)
- `results` (lexical_declaration, line 78, exported)
- `outcome` (lexical_declaration, line 82, exported)
- `formatter` (lexical_declaration, line 83, exported)
- `summary` (lexical_declaration, line 84, exported)

## Contained in

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

## Uses

- [configuration/strings/tool.strings.ts](https://banes-lab.com/source/quality/configuration/strings/tool.strings.ts.md)
- [core/factories/finding.factory.ts](https://banes-lab.com/source/quality/core/factories/finding.factory.ts.md)
- [core/persistence/edit.persistence.ts](https://banes-lab.com/source/quality/core/persistence/edit.persistence.ts.md)

## Linked from

- [configuration/strings](https://banes-lab.com/anatomy/quality/folder-quality-configuration-strings.md)
- [core/adapters](https://banes-lab.com/anatomy/quality/folder-quality-core-adapters.md)
- [core/factories](https://banes-lab.com/anatomy/quality/folder-quality-core-factories.md)
- [core/persistence](https://banes-lab.com/anatomy/quality/folder-quality-core-persistence.md)

## Source

```typescript
import { ALL_IGNORED_MARKER, JS_ECOSYSTEMS, STYLISH_FORMATTER } from "#configuration/constants/eslint.constants";
import { EMPTY_RESULT, toolFinding } from "#core/factories/finding.factory";
import type { Finding, RunResult } from "#types/finding.types";
import { ESLint } from "eslint";
import type { RunnerContext } from "#types/tool.types";
import type { SafeFixOutcome } from "#types/edit.types";
import { applyCuratedSuggestions } from "#core/converters/edit.converter";
import { applyFixesSafely } from "#core/persistence/edit.persistence";
import { defineTool } from "#core/registries/tool.registry";
import { dryRunFixes } from "#configuration/strings/tool.strings";
import { govlabEslintConfig } from "#core/factories/eslint.setup.factory";
import { readFileSync } from "node:fs";

const ESLINT_TOOL = "eslint";

const isAllIgnored = function isAllIgnored(error: unknown): boolean {
    return error instanceof Error && error.message.includes(ALL_IGNORED_MARKER);
};

const lintOne = async function lintOne(eslint: ESLint, target: string): Promise<ESLint.LintResult[]> {
    try {
        return await eslint.lintFiles([target]);
    } catch (error) {
        if (isAllIgnored(error)) {
            return [];
        }
        throw error;
    }
};

const applyCurated = function applyCurated(results: readonly ESLint.LintResult[]): void {
    for (const result of results) {
        const applied = applyCuratedSuggestions(result, result.output ?? readFileSync(result.filePath, "utf8"));
        if (applied !== null) {
            result.output = applied.output;
        }
    }
};

const fixSummary = function fixSummary(outcome: SafeFixOutcome, dryRun: boolean): string {
    if (!dryRun || outcome.pending.length === 0) {
        return "";
    }
    return dryRunFixes(outcome.pending.length, outcome.pending.map((file) => `  ${file}`).join("\n"));
};

const eslintFindings = function eslintFindings(results: readonly ESLint.LintResult[], ecosystem: string): Finding[] {
    return results.flatMap((result) =>
        result.messages.map((message) =>
            toolFinding({
                column: message.column,
                ecosystem,
                file: result.filePath,
                fixable: Boolean(message.fix),
                line: message.line,
                message: message.message,
                ruleId: message.ruleId ?? ESLINT_TOOL,
                tool: ESLINT_TOOL,
            }),
        ),
    );
};

const fixedRun = function fixedRun(results: readonly ESLint.LintResult[], context: RunnerContext): SafeFixOutcome {
    applyCurated(results);
    return applyFixesSafely(results, { backup: context.backup, dryRun: context.dryRun, root: context.root });
};

export const runEslint = async function runEslint(context: RunnerContext): Promise<RunResult> {
    const overrideConfig = await govlabEslintConfig(context.root);
    const eslint = new ESLint({
        cwd: context.root,
        fix: context.fix,
        overrideConfig,
        overrideConfigFile: true,
        warnIgnored: false,
    });
    const results = (await Promise.all(context.paths.map(async (target) => lintOne(eslint, target)))).flat();
    if (results.length === 0) {
        return EMPTY_RESULT;
    }
    const outcome = context.fix ? fixedRun(results, context) : null;
    const formatter = await eslint.loadFormatter(STYLISH_FORMATTER);
    const summary = outcome === null ? "" : fixSummary(outcome, context.dryRun === true);
    return {
        findings: eslintFindings(results, context.ecosystem),
        fixedCount: outcome?.written.length ?? 0,
        output: (await formatter.format(results)) + summary,
    };
};

defineTool({ ecosystems: [...JS_ECOSYSTEMS], run: runEslint, tool: ESLINT_TOOL });
```
