# core/adapters/tool.knip.adapter.ts

> 93 lines of code and 29 definitions.

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

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

## Definitions

- `isDir` (lexical_declaration, line 34)
- `entriesFor` (lexical_declaration, line 44)
- `packageWorkspaces` (lexical_declaration, line 54)
- `namesIn` (lexical_declaration, line 36)
- `extras` (lexical_declaration, line 45)
- `excluded` (lexical_declaration, line 68, exported)
- `runKnip` (lexical_declaration, line 93)
- `containsMjs` (lexical_declaration, line 38)
- `govlabKnipConfig` (lexical_declaration, line 65, exported)
- `SECTION` (lexical_declaration, line 23)
- `CONFIG_FILE` (lexical_declaration, line 24)
- `moduleRequire` (lexical_declaration, line 25)
- `DEFAULT_BARRELS` (lexical_declaration, line 26)
- `BIN_FOLDER` (lexical_declaration, line 27)
- `BIN_ENTRY` (lexical_declaration, line 28)
- `MJS_EXTENSION` (lexical_declaration, line 29)
- `MJS_ENTRY` (lexical_declaration, line 30)
- `TS_PROJECT` (lexical_declaration, line 31)
- `MIXED_PROJECT` (lexical_declaration, line 32)
- `rel` (lexical_declaration, line 60)
- `hasMjs` (lexical_declaration, line 61)
- `config` (lexical_declaration, line 66, exported)
- `section` (lexical_declaration, line 67, exported)
- `shared` (lexical_declaration, line 69, exported)
- `rootWorkspace` (lexical_declaration, line 70, exported)
- `knipBin` (lexical_declaration, line 89)
- `configFile` (lexical_declaration, line 94)
- `args` (lexical_declaration, line 95)
- `result` (lexical_declaration, line 96)

## Contained in

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

## Uses

- [core/factories/finding.factory.ts](https://banes-lab.com/source/quality/core/factories/finding.factory.ts.md)
- [core/loaders/package.loader.ts](https://banes-lab.com/source/quality/core/loaders/package.loader.ts.md)
- [core/loaders/source.loader.ts](https://banes-lab.com/source/quality/core/loaders/source.loader.ts.md)
- [core/parsers/tool.knip.parser.ts](https://banes-lab.com/source/quality/core/parsers/tool.knip.parser.ts.md)
- [core/predicates/failure.predicate.ts](https://banes-lab.com/source/quality/core/predicates/failure.predicate.ts.md)
- [core/selectors/exclusions.selector.ts](https://banes-lab.com/source/quality/core/selectors/exclusions.selector.ts.md)
- [core/selectors/record.selector.ts](https://banes-lab.com/source/quality/core/selectors/record.selector.ts.md)

## Used by

- [core/schemas/tool.schema.ts](https://banes-lab.com/source/quality/core/schemas/tool.schema.ts.md)

## Linked from

- [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/loaders](https://banes-lab.com/anatomy/quality/folder-quality-core-loaders.md)
- [core/parsers](https://banes-lab.com/anatomy/quality/folder-quality-core-parsers.md)
- [core/predicates](https://banes-lab.com/anatomy/quality/folder-quality-core-predicates.md)
- [core/schemas](https://banes-lab.com/anatomy/quality/folder-quality-core-schemas.md)
- [core/selectors](https://banes-lab.com/anatomy/quality/folder-quality-core-selectors.md)

## Source

```typescript
import { findingsResult, signalDeathResult } from "#core/factories/finding.factory";
import { isRecord, stringArrayField, stringArrayFieldOr } from "#core/selectors/record.selector";
import { masterExcludeMarkers, withMasterExclude } from "#core/selectors/exclusions.selector";
import { safeReaddir, safeStat } from "#core/loaders/source.loader";
import type { PathExclusion } from "#types/exclusions.types";
import type { RunResult } from "#types/finding.types";
import type { RunnerContext } from "#types/tool.types";
import { createRequire } from "node:module";
import { defineTool } from "#core/registries/tool.registry";
import { existsSync } from "node:fs";
import { forwardSlashed } from "#core/converters/filename.converter";
import { loadGovlabConfig } from "#core/loaders/config.loader";
import { parseKnipOutput } from "#core/parsers/tool.knip.parser";
import path from "node:path";
import { pathExclusion } from "#core/matchers/exclusions.matcher";
import process from "node:process";
import { sectionOf } from "#core/selectors/config.selector";
import { signalKilled } from "#core/predicates/failure.predicate";
import { spawnTool } from "#core/adapters/invocation.adapter";
import { workspaceDirs } from "#core/loaders/package.loader";
import { writeToolJson } from "#core/persistence/tool.persistence";

const SECTION = "knip";
const CONFIG_FILE = "knip.json";
const moduleRequire = createRequire(import.meta.url);
const DEFAULT_BARRELS = ["index.ts"];
const BIN_FOLDER = "bin";
const BIN_ENTRY = "bin/**/*.{ts,mjs}";
const MJS_EXTENSION = ".mjs";
const MJS_ENTRY = "**/*.mjs";
const TS_PROJECT = ["**/*.ts"];
const MIXED_PROJECT = ["**/*.ts", "**/*.mjs"];

const isDir = (target: string): boolean => safeStat(target)?.isDirectory() ?? false;

const namesIn = (dir: string): string[] => safeReaddir(dir).map((entry) => entry.name);

const containsMjs = (dir: string, excluded: PathExclusion): boolean =>
    namesIn(dir)
        .map((name) => `${dir}/${name}`)
        .filter((full) => !excluded(full))
        .some((full) => (isDir(full) ? containsMjs(full, excluded) : full.endsWith(MJS_EXTENSION)));

const entriesFor = (dir: string, rel: string, section: Record<string, unknown>, hasMjs: boolean): string[] => {
    const extras = isRecord(section["entryExtras"]) ? stringArrayField(section["entryExtras"], rel) : [];
    return [
        ...stringArrayFieldOr(section, "barrels", DEFAULT_BARRELS).filter((barrel) => existsSync(`${dir}/${barrel}`)),
        ...(isDir(`${dir}/${BIN_FOLDER}`) ? [BIN_ENTRY] : []),
        ...(hasMjs ? [MJS_ENTRY] : []),
        ...extras,
    ];
};

const packageWorkspaces = (
    consumerRoot: string,
    section: Record<string, unknown>,
    excluded: PathExclusion,
): [string, unknown][] =>
    workspaceDirs(consumerRoot).map((dir) => {
        const rel = forwardSlashed(path.relative(consumerRoot, dir));
        const hasMjs = containsMjs(rel, excluded);
        return [rel, { entry: entriesFor(rel, rel, section, hasMjs), project: hasMjs ? MIXED_PROJECT : TS_PROJECT }];
    });

export const govlabKnipConfig = async (consumerRoot: string = process.cwd()): Promise<Record<string, unknown>> => {
    const config = await loadGovlabConfig(consumerRoot);
    const section = sectionOf(config, SECTION);
    const excluded = pathExclusion(consumerRoot, masterExcludeMarkers(config));
    const shared = isRecord(section["sharedWorkspaces"]) ? Object.entries(section["sharedWorkspaces"]) : [];
    const rootWorkspace: [string, unknown] = [
        ".",
        { entry: stringArrayField(section, "rootEntry"), project: stringArrayField(section, "rootProject") },
    ];
    return {
        ignore: withMasterExclude(config, stringArrayField(section, "ignore")),
        ignoreBinaries: stringArrayField(section, "ignoreBinaries"),
        ignoreDependencies: stringArrayField(section, "ignoreDependencies"),
        ignoreWorkspaces: stringArrayField(section, "ignoreWorkspaces"),
        paths: isRecord(section["paths"]) ? section["paths"] : {},
        tags: stringArrayField(section, "tags"),
        workspaces: Object.fromEntries([
            rootWorkspace,
            ...shared,
            ...packageWorkspaces(consumerRoot, section, excluded),
        ]),
    };
};

const knipBin = function knipBin(): string {
    return path.resolve(path.dirname(moduleRequire.resolve(SECTION)), "..", "bin", "knip.ts");
};

const runKnip = async function runKnip(context: RunnerContext): Promise<RunResult> {
    const configFile = await writeToolJson(context.root, CONFIG_FILE, await govlabKnipConfig(context.root));
    const args = [knipBin(), "--config", configFile, "--no-progress", "--reporter", "json"];
    const result = spawnTool(process.execPath, args, { cwd: context.root, encoding: "utf8", shell: false });
    if (signalKilled(result.status)) {
        return signalDeathResult({ ecosystem: context.ecosystem, root: context.root, tool: SECTION }, result.signal);
    }
    return findingsResult(parseKnipOutput(result.stdout, context.ecosystem));
};

defineTool({ ecosystems: ["javascript", "typescript"], run: runKnip, tool: SECTION });
```
