#!/bin/sh
# script/lint: run the linter. golangci-lint is never installed locally:
# it runs via docker only, one way, everywhere — script/lint builds
# Dockerfile.lint, which COPYs the repo into the pinned golangci-lint
# image and lints as a build step. This works even when the docker daemon
# is remote and bind mounts are impossible.
#
# --no-cache-filter forces the lint stage to re-execute every run, so an
# unchanged tree is still actually linted; the deps stage keeps its cache,
# so the module download is not repeated. It and --target must both stay,
# for the reason in the next paragraph: do not "simplify" either of those
# two away.
#
# The stage name is written ONCE, in $stage, and passed to both --target
# and --no-cache-filter, so the two flags cannot come to name different
# stages. That is the whole point of the variable. BuildKit silently
# ignores --no-cache-filter when no stage matches its argument: a filter
# naming a stage that does not exist is a no-op, the lint layer is served
# from cache, and script/lint reports green having linted nothing — the
# false green this setup exists to prevent. --target, by contrast, fails
# loudly on a name that is not in the file. With a single shared name, a
# typo or a stale rename therefore becomes a hard error instead of a
# silent skip, because the one name reaches both flags.
#
# What the tooling does NOT check, and is left to whoever edits this:
#
# 1. $stage must name the stage in Dockerfile.lint that actually runs
#    golangci-lint. --target verifies that the name exists, not that it is
#    the right stage, and it stops the build at that stage — so moving the
#    lint step into a different stage, or adding a stage after this one,
#    would not be caught here. Keep this file and Dockerfile.lint in sync.
#
# 2. .dockerignore decides what reaches the container, and only what
#    reaches it gets linted. Excluding a Go file there removes it from the
#    lint with no warning: verified by planting a real violation and adding
#    just that file's path to .dockerignore, which produced `0 issues.` at
#    exit 0 with the violation still sitting in the working tree. It shows
#    up only if the rest of the package still references the excluded file,
#    in which case the build fails loudly on `undefined:` typecheck errors;
#    a self-contained file drops out silently. So .dockerignore is part of
#    this gate, not housekeeping — keep it to build inputs the lint does
#    not read, and never exclude Go sources, go.mod/go.sum or
#    .golangci.yml.
#
# --output=type=cacheonly skips the image export. Nothing consumes the
# image — the deliverable of this build is an exit code — and exporting it
# costs seconds per run and leaves a dangling image behind every time. The
# lint stage still executes and a lint failure still exits non-zero.
set -eu

ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"

# Must match the stage name in Dockerfile.lint.
stage=lint

main() {
    cd "$ROOT"
    docker build \
        --target "$stage" \
        --no-cache-filter="$stage" \
        --output=type=cacheonly \
        -f Dockerfile.lint .
}

main "$@"
