From 89089b1e2f71bdee3450708b3ea3a1ff45531417 Mon Sep 17 00:00:00 2001 From: Ondrej Belusky Date: Wed, 28 Oct 2026 10:22:42 +0100 Subject: [PATCH] Lint every Go repository with one shared configuration and version, reporting only what a change brings --- README.md | 27 ++++++++++++++++ action.yml | 76 ++++++++++++++++++++++++++++++++++++++++++++ golangci.yml | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 README.md create mode 100644 action.yml create mode 100644 golangci.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..8da2634 --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# go-lint + +golangci-lint for every Go repository, with one shared configuration +(`golangci.yml` here) and one pinned version. Add it as a step after the tests: + +```yaml + - name: Lint + uses: https://lab.zlymeda.dynu.net/actions/go-lint@master +``` + +It reports only issues in what the run brings: the pushed commits, or what a +pull request or a new branch adds to its target. A repository that was never +linted therefore does not go red, and its old code is cleaned as it is touched. +`new_from_rev: ''` reports everything, for a repository that is already clean. + +A repository with its own `.golangci.yml` is linted with that instead, for rules +only it needs. Copy the shared file and add to it, so the common rules stay. + +The version is the one baked into the go-executor image; another runner, or a +different `version`, installs it first. Bump both together. + +Locally, with the same rules: + +```sh +curl -sSfo /tmp/golangci.yml https://lab.zlymeda.dynu.net/actions/go-lint/raw/branch/master/golangci.yml +golangci-lint run --config /tmp/golangci.yml +``` diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..10adea0 --- /dev/null +++ b/action.yml @@ -0,0 +1,76 @@ +name: Go lint +description: > + Run golangci-lint with the shared configuration, or the repository's own + .golangci.yml when it has one, reporting only issues in the code a push or + pull request changed. + +inputs: + version: + description: golangci-lint version, installed when the runner has a different one + default: 2.15.0 + config: + description: Configuration file; empty uses the repository's .golangci.yml, else the shared one + default: '' + new_from_rev: + description: > + Report issues only after this revision. auto: the pushed range, or the merge + base with the pull request's target; empty: report everything. + default: auto + working_directory: + description: Directory of the Go module + default: . + args: + description: Extra golangci-lint arguments + default: '' + +runs: + using: composite + steps: + - name: Lint + shell: bash + working-directory: ${{ inputs.working_directory }} + env: + VERSION: ${{ inputs.version }} + CONFIG: ${{ inputs.config }} + NEW_FROM_REV: ${{ inputs.new_from_rev }} + ARGS: ${{ inputs.args }} + SHARED_CONFIG: ${{ github.action_path }}/golangci.yml + EVENT: ${{ github.event_name }} + BEFORE: ${{ github.event.before }} + BASE_REF: ${{ github.base_ref }} + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + run: | + set -euo pipefail + lint=golangci-lint + if ! command -v golangci-lint >/dev/null || [[ "$(golangci-lint version --short 2>/dev/null)" != "$VERSION" ]]; then + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b "$RUNNER_TEMP/golangci" "v$VERSION" + lint="$RUNNER_TEMP/golangci/golangci-lint" + fi + + if [ -z "$CONFIG" ]; then + CONFIG=$(ls .golangci.yml .golangci.yaml 2>/dev/null | head -1 || true) + CONFIG=${CONFIG:-$SHARED_CONFIG} + fi + echo "config: $CONFIG" + + # The checkout is usually shallow, and a shallow history has no base to compare against. + [ "$(git rev-parse --is-shallow-repository)" = true ] && git fetch -q --unshallow + new=() + if [ "$NEW_FROM_REV" = auto ]; then + if [ "$EVENT" = pull_request ] && [ -n "$BASE_REF" ]; then + git fetch -q origin "$BASE_REF" + new=(--new-from-merge-base="origin/$BASE_REF") + elif [ -n "$BEFORE" ] && [[ ! "$BEFORE" =~ ^0+$ ]] && git cat-file -e "$BEFORE^{commit}" 2>/dev/null; then + new=(--new-from-rev="$BEFORE") + else + # A new branch or a manual run: what the branch adds to the default branch. + git fetch -q origin "${DEFAULT_BRANCH:-master}" + new=(--new-from-merge-base="origin/${DEFAULT_BRANCH:-master}") + fi + elif [ -n "$NEW_FROM_REV" ]; then + new=(--new-from-rev="$NEW_FROM_REV") + fi + echo "reporting: ${new[*]:-everything}" + + # shellcheck disable=SC2086 + "$lint" run --config "$CONFIG" "${new[@]}" $ARGS diff --git a/golangci.yml b/golangci.yml new file mode 100644 index 0000000..f484800 --- /dev/null +++ b/golangci.yml @@ -0,0 +1,90 @@ +version: "2" + +run: + timeout: 5m + tests: true + +linters: + default: standard + enable: + # Baseline & Core Analyzers + - errcheck + - govet + - ineffassign + - staticcheck + - unused + - unconvert + - unparam + - misspell + + # Error Handling & Precision + - errorlint + - nilerr + + # Concurrency & Context + - contextcheck + - durationcheck + - noctx + + # Diagnostics & Code Quality + - gocritic + - copyloopvar + - prealloc + - revive + + exclusions: + rules: + - path: _test\.go + linters: + - unparam + - errcheck + - contextcheck + - gocritic + - revive + - noctx + - govet + - errorlint + - prealloc + + settings: + errcheck: + check-type-assertions: true + check-blank: false + govet: + enable: + - shadow + - nilness + - unusedwrite + errorlint: + errorf: true + asserts: true + comparison: true + misspell: + ignore-rules: + - marginable + gocritic: + enabled-tags: + - diagnostic + - performance + disabled-checks: + - commentFormatting + - hugeParam + - commentedOutCode + - captLocal + - assignOp + - rangeValCopy + - unnecessaryDefer + - sloppyReassign + revive: + rules: + - name: exported + disabled: true + +formatters: + enable: + - gofmt + - goimports + +issues: + max-issues-per-linter: 0 + max-same-issues: 0