Skip to content

feat: add run-linters script for native linting without super-linter#136

Closed
zeitlinger wants to merge 3 commits intomainfrom
feat/run-linters
Closed

feat: add run-linters script for native linting without super-linter#136
zeitlinger wants to merge 3 commits intomainfrom
feat/run-linters

Conversation

@zeitlinger
Copy link
Copy Markdown
Member

@zeitlinger zeitlinger commented Mar 31, 2026

Will likely close in favor of #139

Summary

  • New tasks/lint/run-linters.sh: a lightweight alternative to the super-linter native mode that runs native linters with changed-file detection, without requiring the super-linter env file or VALIDATE_* flag machinery
  • Callers pass tool names from a built-in registry: run-linters prettier markdownlint shfmt
  • RUN_LINTERS_TOOLS env var as an alternative to positional args (needed for mise file = tasks called from a depends list)
  • RUN_LINTERS_EXTRA_REGISTRY env var for injecting additional registry entries (used by tests)
  • --autofix always uses the fix command when one is defined (no per-linter FIX_* opt-in)
  • --full to lint all files instead of only changed files
  • {MERGE_BASE} placeholder support, replacing the hardcoded golangci-lint special case in both scripts
  • Existence filter on the cached file list — skips files that appear in the PR diff but no longer exist on disk (uncommitted deletions/renames); applied to both run-linters.sh and super-linter.sh
  • Bats test suite covering: changed-file detection, --full, --autofix, linter failure propagation, unknown linter, missing tool binary, no matching files

Changes to super-linter.sh

  • Generalize golangci-lint --new-from-rev handling via {MERGE_BASE} placeholder (works for any future tool too)
  • Add existence filter to _CACHED_FILES (same fix as run-linters.sh)

Test plan

- New tasks/lint/run-linters.sh: runs native linters with changed-file
  detection (merge base + staged + unstaged), --autofix, --full flags,
  and a built-in tool registry. Callers pass tool names as args:
  run-linters prettier markdownlint shfmt
- RUN_LINTERS_EXTRA_REGISTRY env var for extending the registry
- Generalize golangci-lint {MERGE_BASE} handling in super-linter.sh
- Add bats tests and mise test task
@zeitlinger zeitlinger marked this pull request as ready for review March 31, 2026 14:53
@zeitlinger zeitlinger requested a review from a team as a code owner March 31, 2026 14:53
Copilot AI review requested due to automatic review settings March 31, 2026 14:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new lightweight native-lint runner script (as an alternative to Super-Linter native mode) with changed-file detection, plus a Bats test suite and mise wiring to run those tests. Also updates super-linter.sh to generalize merge-base placeholder handling and filter out deleted files from cached file lists.

Changes:

  • Add tasks/lint/run-linters.sh with a built-in linter registry, changed-file detection, --autofix, --full, and {MERGE_BASE} placeholder support.
  • Update tasks/lint/super-linter.sh native mode to use {MERGE_BASE} placeholder substitution and filter cached files by existence.
  • Add Bats tests and mise task/tool entries to run them.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tasks/lint/run-linters.sh New script to run selected native linters using changed-file detection and a registry-based configuration model.
tasks/lint/super-linter.sh Refactors merge-base handling into a generalized {MERGE_BASE} placeholder and filters deleted files out of cached lists.
tests/run-linters.bats Adds Bats coverage for changed-file detection, --full, --autofix, failures, missing tools, and no-match behavior.
mise.toml Adds bats tool + tasks.test to run the new suite.
.github/renovate-tracked-deps.json Tracks the new mise-managed tools for Renovate.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"VALIDATE_GITHUB_ACTIONS|actionlint|actionlint {FILE}||.github/workflows/*.yml .github/workflows/*.yaml"
"VALIDATE_DOCKERFILE_HADOLINT|hadolint|hadolint {FILE}||Dockerfile Dockerfile.* *.dockerfile"
"VALIDATE_GO_GOLANGCI_LINT|golangci-lint|golangci-lint run||SELF"
"VALIDATE_GO_GOLANGCI_LINT|golangci-lint|golangci-lint run --new-from-rev={MERGE_BASE}||SELF"
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--full is intended to lint the entire codebase, but golangci-lint is now always invoked with --new-from-rev={MERGE_BASE} when a merge base exists. That keeps golangci-lint in diff-only mode even when LINT_ALL=true.

Suggestion: only include/expand the {MERGE_BASE} flag when LINT_ALL != true (or strip --*={MERGE_BASE} when LINT_ALL=true).

Suggested change
"VALIDATE_GO_GOLANGCI_LINT|golangci-lint|golangci-lint run --new-from-rev={MERGE_BASE}||SELF"
"VALIDATE_GO_GOLANGCI_LINT|golangci-lint|golangci-lint run \$( [ \"\${LINT_ALL}\" != \"true\" ] && printf '%s' '--new-from-rev={MERGE_BASE}' )||SELF"

Copilot uses AI. Check for mistakes.
Comment thread tasks/lint/run-linters.sh
Comment on lines +164 to +169
# Substitute {MERGE_BASE}; strip --flag={MERGE_BASE} entirely when no merge base available
if [ -n "$_MERGE_BASE" ]; then
cmd_template="${cmd_template//\{MERGE_BASE\}/$_MERGE_BASE}"
else
cmd_template=$(printf '%s' "$cmd_template" | sed 's/ \?--[a-zA-Z_-]*={MERGE_BASE}//g')
fi
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The {MERGE_BASE} substitution logic doesn’t consider LINT_ALL. With --full, _MERGE_BASE will typically be non-empty, so commands like golangci-lint run --new-from-rev={MERGE_BASE} will still run in diff-only mode, contradicting --full semantics.

Suggestion: when LINT_ALL=true, skip {MERGE_BASE} substitution and remove any --*={MERGE_BASE} flags (even if _MERGE_BASE is set).

Copilot uses AI. Check for mistakes.
Comment thread tasks/lint/run-linters.sh
Comment on lines +135 to +140
trap _on_exit EXIT

_LINTER_RAN=true
_failed=()
_skipped=()

Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_LINTER_RAN is set to true before any linter command is actually executed. This causes the EXIT trap to print the “Try mise run fix…” hint even for failures like an unknown linter name or other early exits.

Suggestion: only set _LINTER_RAN=true immediately before running the first linter command (or gate the hint on a separate flag indicating a linter command was invoked).

Copilot uses AI. Check for mistakes.
Comment thread tasks/lint/run-linters.sh
_register actionlint "actionlint {FILE}" "" ".github/workflows/*.yml .github/workflows/*.yaml"
_register hadolint "hadolint {FILE}" "" "Dockerfile Dockerfile.* *.dockerfile"
_register codespell "codespell {FILES}" "codespell --write-changes {FILES}" "*"
_register ec "ec {FILES}" "" "*"
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The editorconfig checker is registered as ec and invoked as ec {FILES}, but the rest of the repo (e.g. tasks/lint/super-linter.sh) uses the editorconfig-checker binary. If ec isn’t available on PATH, this entry will always be reported as a missing tool.

Suggestion: switch the command to editorconfig-checker {FILES} (and optionally keep ec as an alias tool name if you want the shorter invocation).

Suggested change
_register ec "ec {FILES}" "" "*"
_register ec "editorconfig-checker {FILES}" "" "*"

Copilot uses AI. Check for mistakes.
Comment thread tests/run-linters.bats
git -C "$TMPDIR/repo" add baseline.txt changing.txt
git -C "$TMPDIR/repo" commit -m "initial" -q
git -C "$TMPDIR/repo" branch -M main
git -C "$TMPDIR/repo" push origin main -q
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After git push origin main, the local repo typically won’t have a refs/remotes/origin/main remote-tracking ref until a git fetch is run. Since run-linters.sh computes the merge base against origin/main, these tests may fall back to git ls-files (linting everything) and become flaky.

Suggestion: add a git -C "$TMPDIR/repo" fetch -q origin main (or equivalent) after the push so origin/main exists locally.

Suggested change
git -C "$TMPDIR/repo" push origin main -q
git -C "$TMPDIR/repo" push origin main -q
git -C "$TMPDIR/repo" fetch -q origin main

Copilot uses AI. Check for mistakes.
Comment thread tests/run-linters.bats
Comment on lines +79 to +82
grep -q "check:changing.txt" "$MOCK_LOG"
grep -q "check:new.txt" "$MOCK_LOG"
run ! grep -q "check:baseline.txt" "$MOCK_LOG"
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

run ! grep ... doesn’t do shell negation in Bats; run executes the first argument as a command, so this will try to execute a program named ! (status 127) rather than asserting that grep fails.

Suggestion: use run grep -q ... and then assert [ "$status" -ne 0 ] (or just use ! grep -q ... without run).

Copilot uses AI. Check for mistakes.
Comment thread tests/run-linters.bats
Comment on lines +94 to +97
[ "$status" -eq 0 ]
grep -q "fix:changing.txt" "$MOCK_LOG"
run ! grep -q "check:" "$MOCK_LOG"
}
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same issue as above: run ! grep ... will try to execute ! as a command rather than negating grep’s exit status.

Suggestion: run grep -q "check:" "$MOCK_LOG" followed by [ "$status" -ne 0 ] (or ! grep -q ... without run).

Copilot uses AI. Check for mistakes.
@zeitlinger
Copy link
Copy Markdown
Member Author

I have to think about if this is the right way to go forward (I do like to get rid of the big super linter docker image though):

  • maybe have a flint.toml with config
  • maybe use python or rust to execute the linters instead of bash
  • mega linter uses python - see how that compares - maybe mega linter can be used

@zeitlinger zeitlinger marked this pull request as draft April 1, 2026 07:40
Bats test files use bash syntax and benefit from shellcheck.
zeitlinger added a commit that referenced this pull request Apr 1, 2026
## Summary

- Add `*.bats` to the shellcheck file pattern in `super-linter.sh`
native mode

## Why

Bats test files use bash syntax and shellcheck catches real issues in
them (SC2034 unused variables, SC2314 bats-specific `!` usage, etc.).
Docker super-linter already lints `.bats` files; native mode was missing
the glob pattern, creating a gap where CI catches issues that local
`native-lint` misses.

Discovered while fixing shellcheck errors in
[grafana/docker-otel-lgtm#1156](grafana/docker-otel-lgtm#1156).

The same fix is applied to `run-linters.sh` in #136.

## Test plan

- [ ] `mise run test` passes
@zeitlinger
Copy link
Copy Markdown
Member Author

close in favor of #139

@zeitlinger zeitlinger closed this Apr 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants