# core/producers/tool.golangci-lint.producer.ts

> 117 lines of code and 30 definitions.

Tree: GovLab Quality
Language: typescript
Layer: application
Canonical: https://banes-lab.com/anatomy/quality#file-quality-core-producers-tool-golangci-lint-producer-ts
Source text: https://banes-lab.com/source/quality/core/producers/tool.golangci-lint.producer.ts.txt

Listed in [core/producers](https://banes-lab.com/api/source/quality/core/producers.md), after [core/producers/tool.detekt.producer.ts](https://banes-lab.com/source/quality/core/producers/tool.detekt.producer.ts.md) and before [core/producers/tool.gosec.producer.ts](https://banes-lab.com/source/quality/core/producers/tool.gosec.producer.ts.md).

## Definitions

- `knobs` (lexical_declaration, line 114)
- `TOOL` (lexical_declaration, line 10)
- `DOCS` (lexical_declaration, line 11)
- `SCHEMA` (lexical_declaration, line 12)
- `SCHEMA_WINDOW` (lexical_declaration, line 13)
- `AGGREGATORS` (lexical_declaration, line 14)
- `SECTION_TOGGLE` (lexical_declaration, line 22)
- `STOP_PREFIXES` (lexical_declaration, line 26)
- `KNOB_SETTINGS` (lexical_declaration, line 27)
- `stripFlags` (lexical_declaration, line 42)
- `desc` (lexical_declaration, line 43)
- `flags` (lexical_declaration, line 44)
- `open` (lexical_declaration, line 46)
- `linterRule` (lexical_declaration, line 53)
- `colon` (lexical_declaration, line 54)
- `name` (lexical_declaration, line 58)
- `{ desc, flags }` (lexical_declaration, line 59)
- `linters` (lexical_declaration, line 77)
- `enabled` (lexical_declaration, line 79)
- `text` (lexical_declaration, line 81)
- `toggled` (lexical_declaration, line 85)
- `rule` (lexical_declaration, line 87)
- `count` (lexical_declaration, line 96)
- `produce` (lexical_declaration, line 100)
- `version` (lexical_declaration, line 101)
- `rules` (lexical_declaration, line 102)
- `summary` (lexical_declaration, line 103)
- `schema` (lexical_declaration, line 115)
- `at` (lexical_declaration, line 117)
- `value` (lexical_declaration, line 118)

## Contained in

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

## Uses

- [core/predicates/knob.predicate.ts](https://banes-lab.com/source/quality/core/predicates/knob.predicate.ts.md)

## Linked from

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

## Source

```typescript
import type { CatalogProduct, CatalogRule, KnobEntry } from "#types/catalog.types";
import { commandOutput, commandVersion } from "#core/adapters/invocation.adapter";
import { INTEGER_TYPE } from "#configuration/constants/knob.constants";
import { byRuleId } from "#core/comparators/rule.comparator";
import { defineProducer } from "#core/registries/producer.registry";
import { intAfter } from "#core/parsers/knob.parser";
import { isThreshold } from "#core/predicates/knob.predicate";
import { remoteText } from "#core/adapters/remote.adapter";

const TOOL = "golangci-lint";
const DOCS = "https://golangci-lint.run/usage/linters/#";
const SCHEMA = "https://raw.githubusercontent.com/golangci/golangci-lint/main/jsonschema/golangci.next.jsonschema.json";
const SCHEMA_WINDOW = 4000;
const AGGREGATORS: ReadonlySet<string> = new Set([
    "staticcheck",
    "gocritic",
    "revive",
    "gosec",
    "govet",
    "g.staticcheck",
]);
const SECTION_TOGGLE: ReadonlyMap<string, boolean> = new Map([
    ["Enabled by default linters:", true],
    ["Disabled by default linters:", false],
]);
const STOP_PREFIXES = ["Linters presets:", "Linters:"];
const KNOB_SETTINGS: readonly [string, string][] = [
    ["gocyclo", "min-complexity"],
    ["cyclop", "max-complexity"],
    ["gocognit", "min-complexity"],
    ["funlen", "lines"],
    ["funlen", "statements"],
    ["nestif", "min-complexity"],
    ["lll", "line-length"],
    ["dupl", "threshold"],
    ["maintidx", "under"],
    ["varnamelen", "min-name-length"],
    ["interfacebloat", "max"],
    ["maligned", "suggest-new"],
];

const stripFlags = function stripFlags(text: string): { desc: string; flags: string[] } {
    let desc = text;
    const flags: string[] = [];
    while (desc.endsWith("]") && desc.lastIndexOf("[") !== -1) {
        const open = desc.lastIndexOf("[");
        flags.push(desc.slice(open + 1, -1));
        desc = desc.slice(0, open).trim();
    }
    return { desc, flags };
};

const linterRule = function linterRule(line: string, enabled: boolean, version: string): CatalogRule | null {
    const colon = line.indexOf(": ");
    if (colon === -1) {
        return null;
    }
    const name = line.slice(0, colon);
    const { desc, flags } = stripFlags(line.slice(colon + ": ".length).trim());
    return {
        aggregate: AGGREGATORS.has(name),
        autofix: flags.includes("auto-fix"),
        canonical: null,
        category: "linter",
        description: desc === "" ? null : desc,
        ecosystem: "go",
        enabledByDefault: enabled,
        fast: flags.includes("fast"),
        name,
        ruleId: name,
        tool: TOOL,
        toolVersion: version,
        url: `${DOCS}${name}`,
    };
};

const linters = function linters(lines: readonly string[], version: string): CatalogRule[] {
    const rules: CatalogRule[] = [];
    let enabled: boolean | null = null;
    for (const raw of lines) {
        const text = raw.trim();
        if (STOP_PREFIXES.some((prefix) => text.startsWith(prefix))) {
            break;
        }
        const toggled = SECTION_TOGGLE.get(text);
        enabled = toggled ?? enabled;
        const rule =
            toggled === undefined && enabled !== null && text !== "" ? linterRule(text, enabled, version) : null;
        if (rule !== null) {
            rules.push(rule);
        }
    }
    return rules;
};

const count = function count(rules: readonly CatalogRule[], key: string): number {
    return rules.filter((rule) => rule[key] === true).length;
};

const produce = async function produce(): Promise<CatalogProduct[]> {
    const version = commandVersion(TOOL, ["version"]);
    const rules = linters(commandOutput(TOOL, ["help", "linters"]).split("\n"), version).toSorted(byRuleId);
    const summary = {
        aggregators: count(rules, "aggregate"),
        autofix: count(rules, "autofix"),
        enabledByDefault: count(rules, "enabledByDefault"),
        tool: TOOL,
        toolVersion: version,
        total: rules.length,
    };
    return [{ rules, source: TOOL, summary }];
};

const knobs = async function knobs(): Promise<KnobEntry[]> {
    const schema = (await remoteText(SCHEMA)) ?? "";
    return KNOB_SETTINGS.flatMap(([linter, setting]): KnobEntry[] => {
        const at = schema.indexOf(`"${linter}"`);
        const value = at === -1 ? null : intAfter(schema, `"${setting}"`, { from: at, window: SCHEMA_WINDOW });
        return value === null
            ? []
            : [[TOOL, linter, { default: value, knob: setting, threshold: isThreshold(setting), type: INTEGER_TYPE }]];
    });
};

defineProducer({ knobs, name: TOOL, produce, refresh: "manual" });
```
