stickler

package module
v0.3.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 29, 2026 License: MIT Imports: 14 Imported by: 0

README

stickler

The gomatic lint runner — a stickler for the rules. It executes a set of analyzer tools (the yze suite, golangci-lint, and others) to completion, normalizes their findings into one diagnostic schema, writes the report to stdout in the chosen format, prints a pass/fail status to stderr, and reports pass/fail via the process exit code. Any finding or any tool error is a stickler failure; every tool still runs to completion first.

stickler [--format human|json|github] [root]
  • Runner model — each tool is an executable plus an output adapter (Runner). yze is read via its native stickler-json (no adapter); golangci-lint's JSON is adapted. New tools are added as more Runners.
  • --formathuman (default, one line per finding), json (the normalized result), github (Actions annotations). SARIF output is planned.
  • Exit code0 only when every tool ran cleanly with zero findings; non-zero on any finding or tool error.

Configuration (layered merge)

stickler is a runner, so its configuration is about configuring the tools it runs. Two layers merge, global first then repo:

  • Global: $XDG_CONFIG_HOME/stickler/config.yaml (or ~/.config/stickler/config.yaml).
  • Repo: .stickler.yaml at the target (or --root).
runners: [yze, golangci-lint]   # which tools to run
format: human                   # default output (overridable by --format)
analyzers:                      # per-analyzer settings (forwarded to yze)
  ptrrecv:
    allow: [mypkg.MyMutexType]

A repo layer can replace, add, or remove relative to the global layer. A list written as a sequence replaces; a list written as a mapping adds/removes:

runners:
  add: [revive]
  remove: [golangci-lint]

Maps (the analyzers tree) deep-merge; each setting's list follows the same replace/add/remove rules. Precedence for output format is --format flag > config > human.

Per-tool config overlays (config:)

A config: block carries a generic, per-tool configuration overlay keyed by runner name. Each overlay is deep-merged onto that tool's own base config file at run time, so per-repo lint deltas live in .stickler.yaml instead of in a divergent, unmanaged base config. The merge is generic — golangci-lint is just one configured tool — and follows the same polymorphism as everything else: a mapping deep-merges, a scalar or sequence replaces, and a mapping written with only add/remove/replace keys mutates the base list.

config:
  golangci-lint:                  # merged onto the repo's managed .golangci.yaml
    linters:
      settings:
        gosec:
          excludes: { add: [G204] }   # add a per-repo exclude on top of the central config

At lint time stickler reads the tool's base config (for golangci-lint, the repo's .golangci.yaml/.golangci.yml), folds the overlays onto it, writes the effective config to a temp file, and runs the tool with --config pointing at it. With no overlay the tool is run as before (its own config discovery). This is how a repo keeps the uniform, managed base config while still expressing the handful of rules it genuinely needs to differ on.

Built on the go-yze diagnostic schema. Forwarding the merged analyzers settings to yze --config, and --fix (delegating to each tool's fixer), are the next steps.

Documentation

Overview

Package stickler is the gomatic lint runner: it executes a set of analyzer tools (the yze suite, golangci-lint, and others) to completion, normalizes their findings into one diagnostic schema, and reports pass/fail via the process exit code. Any finding or any tool error is a stickler failure.

Index

Constants

View Source
const (
	// ErrConfig reports a stickler config file that cannot be parsed.
	ErrConfig errs.Const = "cannot load stickler config"
	// ErrBadListSetting reports a list setting that is neither a sequence nor an
	// add/remove/replace mapping.
	ErrBadListSetting errs.Const = "list setting must be a sequence or an add/remove/replace mapping"
)

Configuration errors.

View Source
const (
	// ErrYzeFailed reports that the yze aggregator could not be run or parsed.
	ErrYzeFailed errs.Const = "yze runner failed"
	// ErrGolangciFailed reports that golangci-lint could not be run or parsed.
	ErrGolangciFailed errs.Const = "golangci-lint runner failed"
	// ErrExec reports that a subprocess could not be started or exited non-zero;
	// it carries the captured stderr so the failure's real reason is reported.
	ErrExec errs.Const = "command execution failed"
)

Runner failures.

View Source
const ErrRunner errs.Const = "lint runner failed"

ErrRunner wraps a failure from one tool runner, tagged with the runner name.

View Source
const ErrUnknownOutput errs.Const = "unknown output format"

ErrUnknownOutput reports an output format stickler does not support.

Variables

This section is empty.

Functions

func ConfigPaths added in v0.2.0

func ConfigPaths(getenv func(string) string, home HomeDir, repoRoot RepoRoot) []string

ConfigPaths returns the ordered config layer paths: the global config, then the repository's .stickler.yaml.

func ExecCommand

func ExecCommand(ctx context.Context, name RunnerName, args ...Arg) ([]byte, error)

ExecCommand is the default Command, executing a real subprocess. On failure the returned error wraps ErrExec with the captured stderr so the underlying reason (config error, panic, load failure) reaches the caller's message.

func Format

func Format(w io.Writer, format OutputFormat, result Result) error

Format writes the result to w in the named format.

func MarshalTree added in v0.3.0

func MarshalTree(tree map[string]any) ([]byte, error)

MarshalTree renders an effective configuration tree as a YAML document.

func MergeTree added in v0.3.0

func MergeTree(base map[string]any, overlays []Overlay) map[string]any

MergeTree folds each overlay, in layer order, onto base and returns the effective configuration tree. base is never mutated.

func OSTempWriter added in v0.3.0

func OSTempWriter(data []byte) (string, func(), error)

OSTempWriter is the production TempWriter, writing the effective config to a uniquely-named temp file and returning a cleanup that removes it.

func ParseTree added in v0.3.0

func ParseTree(data []byte) (map[string]any, error)

ParseTree decodes a base config document into a generic tree, or an empty tree when data is empty or null. A malformed document is a configuration error.

Types

type Arg added in v0.2.1

type Arg string

Arg is one command-line argument passed to a runner's binary.

type Command

type Command func(ctx context.Context, name RunnerName, args ...Arg) ([]byte, error)

Command runs an external tool and returns its stdout. A non-nil error includes a non-zero exit; callers that can still parse the stdout (linters exit non-zero when they report findings) treat the output as authoritative.

type Config added in v0.2.0

type Config struct {
	Analyzers map[string]map[string]StringList `yaml:"analyzers"`
	Config    map[string]Overlay               `yaml:"config"`
	Format    string                           `yaml:"format"`
	Runners   StringList                       `yaml:"runners"`
}

Config is one configuration layer (global or repo). Config holds per-tool config overlays keyed by runner name (the `config:` block); each is deep-merged onto that tool's own base config file at run time.

func LoadLayers added in v0.2.0

func LoadLayers(read func(path string) ([]byte, error), paths ...string) ([]Config, error)

LoadLayers reads and parses each existing config path into a layer. A path the reader cannot open is treated as an absent layer and skipped; a path that parses badly is an error.

type ConfigMerger added in v0.3.0

type ConfigMerger struct {
	Flag      string
	BaseDir   string
	Read      FileReader
	Temp      TempWriter
	BaseNames []string
	Overlays  []Overlay
}

ConfigMerger is the generic, per-tool config-merge capability: given a tool's base config filenames, its config-flag prefix, the per-repo overlays, and the repo directory, it produces the tool's --config argument pointing at an effective config (base + overlays). It is reused by every config-file tool — golangci-lint is just one configured instance — so no tool is special-cased in the merge logic.

func GolangciMerger added in v0.3.0

func GolangciMerger(overlays []Overlay, baseDir string) ConfigMerger

GolangciMerger builds the generic config merger for golangci-lint — the only golangci-specific knowledge is its base config filenames and --config flag — wired to the production I/O seams.

func (ConfigMerger) Args added in v0.3.0

func (m ConfigMerger) Args() ([]Arg, func(), error)

Args builds the tool's config argument: with overlays it merges them onto the base and writes the effective config to a temp file, returning the config flag and a cleanup; with no overlays it returns no extra args (and a no-op cleanup), leaving config discovery to the tool itself — the pre-merge behavior.

type FileReader added in v0.3.0

type FileReader func(path string) ([]byte, error)

FileReader reads a file's bytes; injected so config merging is testable without a real base config on disk.

type HomeDir added in v0.2.1

type HomeDir string

HomeDir is the current user's home directory, the base for the default global config location.

type OutputFormat

type OutputFormat string

OutputFormat names how a Result is rendered.

const (
	OutputHuman  OutputFormat = "human"
	OutputJSON   OutputFormat = "json"
	OutputGitHub OutputFormat = "github"
)

The output formats stickler supports.

type Overlay added in v0.3.0

type Overlay map[string]any

Overlay is one configuration layer's overlay for a single tool, taken from that tool's entry under the `config:` block of a .stickler.yaml. It is deep-merged onto the tool's own base config file at run time, so per-repo tool-config deltas live in .stickler.yaml instead of in a divergent, unmanaged base config. A mapping value deep-merges; a scalar or sequence replaces; a mapping written with only add/remove/replace keys mutates the base list (the StringList polymorphism).

type RepoRoot added in v0.2.1

type RepoRoot string

RepoRoot is the directory whose .stickler.yaml supplies the repository configuration layer.

type Resolved added in v0.2.0

type Resolved struct {
	Analyzers map[string]map[string][]string
	Config    map[string][]Overlay
	Format    string
	Runners   []string
}

Resolved is the concrete configuration after all layers are folded. Config maps each runner name to the ordered list of its per-layer overlays (global first, repo last); a config-file runner folds them onto its base config in the repo at run time, since that base lives in the repo, not in any stickler layer.

func Resolve added in v0.2.0

func Resolve(layers ...Config) Resolved

Resolve folds the layers in order (global first, repo last), applying each layer's add/remove/replace directives onto the accumulated result.

type Result

type Result struct {
	Diagnostics []goyze.Diagnostic
	Errors      []error
}

Result aggregates every runner's findings and failures from one stickler pass.

func Orchestrate

func Orchestrate(ctx context.Context, root Root, runners []Runner) Result

Orchestrate runs every runner to completion over root, collecting all diagnostics and wrapping any runner error with its name. One runner's failure never prevents the others from running.

func (Result) Failed

func (r Result) Failed() bool

Failed reports whether the pass should fail the build: any diagnostic or any runner error.

type Root added in v0.2.1

type Root string

Root is the directory or package pattern a runner analyzes (e.g. "./..." or a path); it is the target every Runner operates over.

type Runner

type Runner interface {
	Name() string
	Run(ctx context.Context, root Root) ([]goyze.Diagnostic, error)
}

Runner executes one analyzer tool over a root directory and returns its findings as normalized diagnostics.

func BuildRunners added in v0.2.0

func BuildRunners(command Command, names []string, ctx RunnerContext) []Runner

BuildRunners constructs the runners named in the resolved configuration, defaulting to the full set when none are configured. A config-file runner is given its merger so it runs against the effective (base + overlay) config. Unknown runner names are ignored.

func NewGolangciRunner

func NewGolangciRunner(command Command, merger ConfigMerger) Runner

NewGolangciRunner builds a Runner that invokes golangci-lint, merging the repo's base .golangci.yaml with the configured overlays at run time via the merger.

func NewYzeRunner

func NewYzeRunner(command Command) Runner

NewYzeRunner builds a Runner that invokes the yze aggregator.

type RunnerContext added in v0.3.0

type RunnerContext struct {
	Config  map[string][]Overlay
	BaseDir string
}

RunnerContext carries what config-file runners need to build their effective configuration: the repo directory holding the base config files and the resolved per-tool overlays keyed by runner name.

type RunnerName added in v0.2.1

type RunnerName string

RunnerName identifies a runner in configuration, selection, and as the binary name stickler executes.

const (
	RunnerYze      RunnerName = "yze"
	RunnerGolangci RunnerName = "golangci-lint"
)

Runner names, used in configuration and selection.

type StringList added in v0.2.0

type StringList struct {
	// contains filtered or unexported fields
}

StringList is a list-valued setting in one configuration layer. It merges onto the value accumulated from lower layers either by replacing it (when written as a YAML sequence) or by adding and removing entries (when written as a mapping with add/remove/replace keys). An absent setting leaves the lower value intact.

func (*StringList) UnmarshalYAML added in v0.2.0

func (l *StringList) UnmarshalYAML(node *yaml.Node) error

UnmarshalYAML accepts a sequence (replace) or an add/remove/replace mapping, rejecting any unknown mapping key (a config typo such as `addd`). The pointer receiver and *yaml.Node parameter are dictated by the yaml.Unmarshaler interface, which a polymorphic (sequence-or-mapping) setting must implement.

type TempWriter added in v0.3.0

type TempWriter func(data []byte) (path string, cleanup func(), err error)

TempWriter writes data to a fresh temporary file and returns its path plus a cleanup that removes it; injected so the effective-config write is testable.

Directories

Path Synopsis
cmd
stickler command
Command stickler is the gomatic lint runner: it executes the yze suite and golangci-lint to completion, normalizes their findings, writes them to stderr in the chosen format, and exits non-zero if any finding or tool error occurred.
Command stickler is the gomatic lint runner: it executes the yze suite and golangci-lint to completion, normalizes their findings, writes them to stderr in the chosen format, and exits non-zero if any finding or tool error occurred.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL