# core/adapters/tool.adapter.ts

> 106 lines of code and 18 definitions.

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

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

## Definitions

- `scanTool` (lexical_declaration, line 24, exported)
- `passTool` (lexical_declaration, line 19, exported)
- `fixCycle` (lexical_declaration, line 44, exported)
- `residualOf` (lexical_declaration, line 55, exported)
- `runAdvisory` (lexical_declaration, line 76, exported)
- `scanTargets` (lexical_declaration, line 29, exported)
- `runSelectable` (lexical_declaration, line 89, exported)
- `standardScan` (lexical_declaration, line 59, exported)
- `messageScan` (lexical_declaration, line 67, exported)
- `statusOkIn` (lexical_declaration, line 114, exported)
- `findings` (lexical_declaration, line 33, exported)
- `outcome` (lexical_declaration, line 35, exported)
- `output` (lexical_declaration, line 85, exported)
- `advisory` (lexical_declaration, line 93, exported)
- `advisoryContext` (lexical_declaration, line 94, exported)
- `configFile` (lexical_declaration, line 95, exported)
- `result` (lexical_declaration, line 99, exported)
- `terminal` (lexical_declaration, line 104, exported)

## Contained in

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

## Uses

- [core/adapters/tool.sqlfluff.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.sqlfluff.adapter.ts.md)
- [core/classifiers/invocation.classifier.ts](https://banes-lab.com/source/quality/core/classifiers/invocation.classifier.ts.md)
- [core/factories/finding.factory.ts](https://banes-lab.com/source/quality/core/factories/finding.factory.ts.md)
- [core/predicates/failure.predicate.ts](https://banes-lab.com/source/quality/core/predicates/failure.predicate.ts.md)

## Used by

- [core/adapters/tool.actionlint.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.actionlint.adapter.ts.md)
- [core/adapters/tool.bandit.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.bandit.adapter.ts.md)
- [core/adapters/tool.brakeman.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.brakeman.adapter.ts.md)
- [core/adapters/tool.checkov.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.checkov.adapter.ts.md)
- [core/adapters/tool.checkstyle.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.checkstyle.adapter.ts.md)
- [core/adapters/tool.clang-tidy.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.clang-tidy.adapter.ts.md)
- [core/adapters/tool.clippy.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.clippy.adapter.ts.md)
- [core/adapters/tool.clj-kondo.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.clj-kondo.adapter.ts.md)
- [core/adapters/tool.credo.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.credo.adapter.ts.md)
- [core/adapters/tool.depcheck.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.depcheck.adapter.ts.md)
- [core/adapters/tool.dependency-cruiser.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.dependency-cruiser.adapter.ts.md)
- [core/adapters/tool.detekt.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.detekt.adapter.ts.md)
- [core/adapters/tool.go-critic.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.go-critic.adapter.ts.md)
- [core/adapters/tool.golangci-lint.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.golangci-lint.adapter.ts.md)
- [core/adapters/tool.gosec.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.gosec.adapter.ts.md)
- [core/adapters/tool.hadolint.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.hadolint.adapter.ts.md)
- [core/adapters/tool.kics.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.kics.adapter.ts.md)
- [core/adapters/tool.lintr.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.lintr.adapter.ts.md)
- [core/adapters/tool.luacheck.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.luacheck.adapter.ts.md)
- [core/adapters/tool.madge.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.madge.adapter.ts.md)
- [core/adapters/tool.oxlint.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.oxlint.adapter.ts.md)
- [core/adapters/tool.perlcritic.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.perlcritic.adapter.ts.md)
- [core/adapters/tool.phpcs.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.phpcs.adapter.ts.md)
- [core/adapters/tool.phpmd.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.phpmd.adapter.ts.md)
- [core/adapters/tool.pmd.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.pmd.adapter.ts.md)
- [core/adapters/tool.pylint.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.pylint.adapter.ts.md)
- [core/adapters/tool.revive.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.revive.adapter.ts.md)
- [core/adapters/tool.rubocop.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.rubocop.adapter.ts.md)
- [core/adapters/tool.ruff.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.ruff.adapter.ts.md)
- [core/adapters/tool.scalastyle.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.scalastyle.adapter.ts.md)
- [core/adapters/tool.semgrep.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.semgrep.adapter.ts.md)
- [core/adapters/tool.shellcheck.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.shellcheck.adapter.ts.md)
- [core/adapters/tool.slither.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.slither.adapter.ts.md)
- [core/adapters/tool.sqlfluff.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.sqlfluff.adapter.ts.md)
- [core/adapters/tool.staticcheck.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.staticcheck.adapter.ts.md)
- [core/adapters/tool.stylua.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.stylua.adapter.ts.md)
- [core/adapters/tool.swiftlint.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.swiftlint.adapter.ts.md)
- [core/adapters/tool.tflint.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.tflint.adapter.ts.md)
- [core/adapters/tool.trivy.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.trivy.adapter.ts.md)
- [core/adapters/tool.ts-prune.adapter.ts](https://banes-lab.com/source/quality/core/adapters/tool.ts-prune.adapter.ts.md)

## Linked from

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

## Source

```typescript
import type {
    AdvisoryContext,
    AdvisoryToolSpec,
    RunnerContext,
    ScanSpec,
    SelectableToolSpec,
    ToolCall,
    ToolExit,
} from "#types/tool.types";
import { EMPTY_RESULT, findingsResult, fixedResult, toolError, toolFailure } from "#core/factories/finding.factory";
import type { Finding, PassOutcome, RunResult } from "#types/finding.types";
import { exitOf, failedWhenEmpty, passOf, terminalOf } from "#core/classifiers/invocation.classifier";
import { signalKilled, statusIn } from "#core/predicates/failure.predicate";
import { electedMissing } from "#configuration/strings/tool.strings";
import { gatingAdvisory } from "#core/factories/tool.factory";
import { spawnTool } from "#core/adapters/invocation.adapter";
import { writeToolFile } from "#core/persistence/tool.persistence";

export const passTool = function passTool(advisory: AdvisoryContext, call: ToolCall, spec: ScanSpec): PassOutcome {
    const result = spawnTool(call.bin, call.args, { cwd: call.cwd, encoding: "utf8", env: call.env, shell: false });
    return passOf(advisory, result, spec);
};

export const scanTool = function scanTool(advisory: AdvisoryContext, call: ToolCall, spec: ScanSpec): RunResult {
    const outcome = passTool(advisory, call, spec);
    return outcome.terminal ? outcome.result : findingsResult(outcome.findings);
};

export const scanTargets = function scanTargets(
    targets: readonly string[],
    scanOne: (target: string) => PassOutcome | null,
): RunResult {
    const findings: Finding[] = [];
    for (const target of targets) {
        const outcome = scanOne(target);
        if (outcome?.terminal === true) {
            return outcome.result;
        }
        findings.push(...(outcome?.findings ?? []));
    }
    return findingsResult(findings);
};

export const fixCycle = function fixCycle(
    reported: PassOutcome,
    shouldFix: (findings: readonly Finding[]) => boolean,
    applyFix: (findings: Finding[]) => RunResult,
): RunResult {
    if (reported.terminal) {
        return reported.result;
    }
    return shouldFix(reported.findings) ? applyFix(reported.findings) : findingsResult(reported.findings);
};

export const residualOf = function residualOf(reported: readonly Finding[], outcome: PassOutcome): RunResult {
    return outcome.terminal ? outcome.result : fixedResult(reported, outcome.findings);
};

export const standardScan = function standardScan(
    advisory: AdvisoryContext,
    statuses: ReadonlySet<number>,
    parse: ScanSpec["parse"],
): ScanSpec {
    return { failed: failedWhenEmpty(statuses), failure: (exit) => toolError(advisory, exit), parse };
};

export const messageScan = function messageScan(
    advisory: AdvisoryContext,
    statuses: ReadonlySet<number>,
    parse: ScanSpec["parse"],
    message: (exit: ToolExit) => string,
): ScanSpec {
    return { failed: failedWhenEmpty(statuses), failure: (exit) => toolFailure(advisory, message(exit)), parse };
};

export const runAdvisory = function runAdvisory(spec: AdvisoryToolSpec, context: RunnerContext): RunResult {
    const result = spawnTool(spec.tool, spec.argsFor(context), {
        cwd: spec.cwdFor(context),
        encoding: "utf8",
        shell: false,
    });
    if (result.error || signalKilled(result.status) || !spec.statusOk(result.status)) {
        return EMPTY_RESULT;
    }
    const output = spec.stream === "stderr" ? result.stderr : result.stdout;
    return findingsResult(spec.parse(output, context.ecosystem));
};

export const runSelectable = async function runSelectable(
    spec: SelectableToolSpec,
    context: RunnerContext,
): Promise<RunResult> {
    const advisory = context.elected === false;
    const advisoryContext = gatingAdvisory(context, spec.tool, electedMissing(spec.tool));
    const configFile =
        context.elected === true
            ? writeToolFile(context.root, spec.configFilename, await spec.loadConfig(context.root))
            : null;
    const result = spawnTool(spec.tool, spec.argsFor(configFile, context), {
        cwd: spec.cwdFor(context),
        encoding: "utf8",
        shell: false,
    });
    const terminal = terminalOf(advisoryContext, result);
    if (terminal !== null) {
        return advisory ? EMPTY_RESULT : terminal;
    }
    if (!spec.statusOk(result.status)) {
        return advisory ? EMPTY_RESULT : toolError(advisoryContext, exitOf(result));
    }
    return findingsResult(spec.parse(result.stdout, context.ecosystem, advisory));
};

export const statusOkIn = function statusOkIn(statuses: ReadonlySet<number>): (status: number | null) => boolean {
    return (status) => statusIn(statuses, status);
};
```
