Skip to content

Add an opt-in pre-commit hook - #415

Merged
tylervick merged 1 commit into
mainfrom
precommit-hooks
Aug 8, 2026
Merged

Add an opt-in pre-commit hook#415
tylervick merged 1 commit into
mainfrom
precommit-hooks

Conversation

@tylervick

@tylervick tylervick commented Aug 8, 2026

Copy link
Copy Markdown
Member

Closes #383. Last of the maintenance-automation batch (#402, #410, #413 preceding).

Runs the same three checks CI runs — shellcheck, SwiftFormat, SwiftLint — against staged files only, so it stays fast rather than linting the whole tree on every commit.

Three deliberate design choices

Opt-in, not automatic. Enabled with git config core.hooksPath .githooks, documented in CONTRIBUTING. Nothing installs it for you — a repository that silently takes over your git config is worse than one that asks.

Lints rather than formatting in place. A hook that rewrites files underneath you produces commits whose contents you never read. It tells you to run swiftformat . instead.

Degrades rather than blocking. Each tool is skipped with a note if it is not installed, so a contributor who has not run brew install shellcheck swiftformat swiftlint yet can still commit.

Verified by running it, not by reading it

Case Result
Staged .swift with a formatting violation blockedgit log confirmed no commit landed
Clean staged .swift committed
git commit --no-verify bypassed as expected
Docs-only commit both linters skipped entirely

The hook also passes shellcheck itself, which felt like the minimum for a script whose job is running shellcheck.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Chores

    • Added optional pre-commit checks for staged shell and Swift files.
    • Reports issues from available linting and formatting tools before commits.
    • Provides clear guidance when checks fail, while allowing commits to bypass checks when needed.
  • Documentation

    • Added setup and usage instructions for enabling and configuring the pre-commit checks.

Closes #383.

Runs the same three checks CI runs — shellcheck, SwiftFormat, SwiftLint —
against staged files only, so it stays fast and does not lint the whole
tree on every commit.

Opt-in via `git config core.hooksPath .githooks`, documented in
CONTRIBUTING. Nothing installs it automatically: a repository that
silently takes over your git config is worse than one that asks.

It lints rather than formatting in place. A hook that rewrites files
underneath you produces commits whose contents you never read.

Each tool is skipped with a note if it is not installed, so the hook is
usable before `brew install shellcheck swiftformat swiftlint` and never
blocks a contributor who has not set the tooling up yet.

Verified all four behaviours by running it: a staged file with a
formatting violation is blocked, a clean file commits, --no-verify
bypasses, and a docs-only commit skips both linters entirely. The hook
also passes shellcheck itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds an optional Git pre-commit hook. The hook checks staged shell and Swift files with available lint tools, reports failures, and documents setup, behavior, and bypass options.

Changes

Pre-commit validation

Layer / File(s) Summary
Staged-file checks
.githooks/pre-commit
The Bash hook runs ShellCheck on staged shell files and SwiftFormat and SwiftLint on staged Swift files. It reports missing tools, tracks failures, and returns a nonzero status when checks fail.
Hook setup documentation
CONTRIBUTING.md
The contributor guide documents hook activation, tool installation, staged-file checks, diagnostic-only behavior, and the --no-verify bypass.

Estimated code review effort: 2 (Simple) | ~10 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Git
  participant PreCommitHook
  participant ShellCheck
  participant SwiftFormat
  participant SwiftLint
  Git->>PreCommitHook: Run pre-commit hook
  PreCommitHook->>ShellCheck: Check staged .sh files
  PreCommitHook->>SwiftFormat: Lint staged Swift files
  PreCommitHook->>SwiftLint: Lint staged Swift files
  PreCommitHook->>Git: Return success or failure status
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the opt-in pre-commit hook added by this pull request.
Linked Issues check ✅ Passed The hook runs ShellCheck, SwiftFormat, and SwiftLint on staged files, and CONTRIBUTING.md documents setup as required by issue #383.
Out of Scope Changes check ✅ Passed The changes are limited to the pre-commit hook and its documentation, which match issue #383 and the pull request objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch precommit-hooks

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
CONTRIBUTING.md (1)

71-73: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Document the CI tool versions and sources.

CI installs unversioned swiftformat and swiftlint with Homebrew, but it relies on a runner-provided shellcheck. Package.swift does not control the CI formatter. Pin the tools or document this provisioning policy.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CONTRIBUTING.md` around lines 71 - 73, Update the CI tooling documentation
around the Homebrew install command to state the provisioning source and
versions for swiftformat and swiftlint, and clarify that shellcheck is supplied
by the runner rather than installed there. If versions cannot be pinned,
explicitly document that these tools are intentionally provisioned unversioned
by Homebrew and runner defaults.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.githooks/pre-commit:
- Around line 15-18: Align the pre-commit hook’s staged Swift-file selector with
CI by excluding Sources/XCTestHTMLReportCore/Classes/HTMLTemplates.swift, while
preserving the existing *.sh ShellCheck selector. Update the relevant
staged-file filtering logic in the hook and retain the exclusion consistently
with .swiftformat and .swiftlint.yml.
- Line 43: Update the SwiftLint invocation in the pre-commit hook to include
--force-exclude so configured exclusions from .swiftlint.yml apply to staged
Swift paths. Preserve xargs handling for multiple paths, and treat SwiftLint’s
“No lintable files found” result as success when all staged files are excluded,
while retaining failure behavior for genuine lint errors.
- Line 10: Update the pre-commit lint flow around staged() so ShellCheck,
SwiftFormat, and SwiftLint analyze the staged index snapshot rather than
working-tree paths; materialize each index blob through temporary files or
stdin, or reject files that also have unstaged changes. Add a regression test
covering staged-clean content with conflicting bad working-tree content.

---

Nitpick comments:
In `@CONTRIBUTING.md`:
- Around line 71-73: Update the CI tooling documentation around the Homebrew
install command to state the provisioning source and versions for swiftformat
and swiftlint, and clarify that shellcheck is supplied by the runner rather than
installed there. If versions cannot be pinned, explicitly document that these
tools are intentionally provisioned unversioned by Homebrew and runner defaults.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e7fc47ba-4122-434f-875c-24c8486406eb

📥 Commits

Reviewing files that changed from the base of the PR and between 041749f and 0a6fa41.

📒 Files selected for processing (2)
  • .githooks/pre-commit
  • CONTRIBUTING.md

Comment thread .githooks/pre-commit
# Skip a single commit with: git commit --no-verify
set -uo pipefail

staged() { git diff --cached --name-only --diff-filter=ACMR -z -- "$@"; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- hook files ---'
git ls-files '.githooks/pre-commit' '.githooks/*' '.github/*' 2>/dev/null || true

printf '%s\n' '--- pre-commit outline ---'
ast-grep outline .githooks/pre-commit 2>/dev/null || true

printf '%s\n' '--- pre-commit contents ---'
cat -n .githooks/pre-commit

printf '%s\n' '--- related staged/path handling ---'
rg -n -C 4 'staged\(|swiftformat|swiftlint|SwiftFormat|SwiftLint|diff-filter|git diff --cached' .githooks .github . 2>/dev/null | head -n 500

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 8080


🏁 Script executed:

#!/bin/bash
set -eu

cat -n .githooks/pre-commit
rg -n -C 5 'staged\(|swiftformat|swiftlint|SwiftFormat|SwiftLint|git diff --cached' .githooks .github 2>/dev/null || true

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 5881


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

p = Path(".githooks/pre-commit")
text = p.read_text()
checks = {
    "staged names only": "git diff --cached --name-only -z" in text,
    "shellcheck receives staged() output": bool(re.search(r"staged '\*\.sh'.*\|\s*xargs -0 shellcheck", text)),
    "SwiftFormat receives staged() output": bool(re.search(r"staged '\*\.swift'.*\|\s*xargs -0 swiftformat", text)),
    "SwiftLint receives staged() output": bool(re.search(r"staged '\*\.swift'.*\|\s*xargs -0 swiftlint", text)),
    "index-content read mechanism": any(x in text for x in ("git show :", "git cat-file", "git checkout-index", "mktemp", "stdin")),
}
for name, value in checks.items():
    print(f"{name}: {value}")
assert checks["staged names only"]
assert checks["shellcheck receives staged() output"]
assert checks["SwiftFormat receives staged() output"]
assert checks["SwiftLint receives staged() output"]
assert not checks["index-content read mechanism"]
PY

printf '%s\n' '--- current staged Swift paths (NUL-safe) ---'
git diff --cached --name-only --diff-filter=ACMR -z -- '*.swift' |
  python3 -c 'import sys; print(repr(sys.stdin.buffer.read()))'

printf '%s\n' '--- staged versus working-tree entries, if any ---'
git status --short
git diff --cached --name-status --diff-filter=ACMR -- '*.swift' || true
git diff --name-status -- '*.swift' || true

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 447


🌐 Web query:

SwiftFormat Git hook staged files working tree content official documentation

💡 Result:

The official documentation for SwiftFormat recommends using a Git pre-commit hook to format only staged files by utilizing the git-format-staged tool [1][2]. This approach prevents formatting unstaged changes or creating inconsistent states in the Git index [3]. To implement this, follow these steps: 1. Install the SwiftFormat command-line tool [1]. 2. Install git-format-staged [1]. 3. Create or edit the .git/hooks/pre-commit file in your repository [1]. 4. Add the following configuration to the file [1]: #!/bin/bash git-format-staged --formatter "swiftformat stdin --stdin-path '{}'" "*.swift" 5. Make the hook executable by running chmod +x .git/hooks/pre-commit in your terminal [1]. Important Considerations: - Stdin Usage: The use of --stdin-path is critical [4]. Because git-format-staged passes file content via stdin, SwiftFormat requires this argument to correctly identify the file path and apply project-specific configurations (such as ignores or exclusions) [4][5]. - Versioning: Git hooks are stored in the local .git directory and are not automatically checked into source control [1][2]. For collaborative teams, you may need a mechanism (like an install script) to ensure all developers have the hook configured, or consider running formatting checks on your CI server instead [1][6]. - GUI Clients: If you use a Git GUI client, you may need to perform additional configuration steps to ensure the hook is triggered correctly [1]. If you need to explicitly reference a configuration file within the hook, you can include the --config flag within the formatter string: git-format-staged --formatter "swiftformat stdin --stdin-path '{}' --config .swiftformat" "*.swift" [4][5].

Citations:


🌐 Web query:

SwiftLint command line lint explicit file paths official documentation

💡 Result:

To lint explicit files with SwiftLint via the command line, you can provide the file paths directly as arguments to the lint command [1]. When no paths are specified, SwiftLint defaults to linting the current working directory [1]. Example command: swiftlint lint path/to/file1.swift path/to/file2.swift For specialized use cases, such as linting a list of files modified by Xcode or Git, SwiftLint provides options to read file paths from environment variables: --use-script-input-files: Uses environment variables (SCRIPT_INPUT_FILE_COUNT and SCRIPT_INPUT_FILE_0, etc.) to define the files to lint [2][3]. --use-script-input-file-lists: Uses environment variables (SCRIPT_INPUT_FILE_LIST_COUNT and SCRIPT_INPUT_FILE_LIST_0, etc.) to read file lists [3][4]. Note that when providing explicit paths, behavior can be influenced by your.swiftlint.yml configuration. Specifically, if you have an included property defined in your configuration file, SwiftLint may ignore directory paths passed on the command line [2][5]. Additionally, using the --force-exclude flag will cause SwiftLint to exclude files listed in your configuration's excluded property even if they are explicitly passed as arguments [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

text = Path(".githooks/pre-commit").read_text()
for label, pattern in {
    "staged names only": r"git diff --cached --name-only -z",
    "ShellCheck gets working-tree paths": r"staged '\*\.sh'.*\|\s*xargs -0 shellcheck",
    "SwiftFormat gets working-tree paths": r"staged '\*\.swift'.*\|\s*xargs -0 swiftformat",
    "SwiftLint gets working-tree paths": r"staged '\*\.swift'.*\|\s*xargs -0 swiftlint",
}.items():
    print(f"{label}: {bool(re.search(pattern, text))}")

print("index-content handling:",
      any(token in text for token in
          ("git show :", "git cat-file", "git checkout-index", "mktemp")))
PY

git status --short
git diff --cached --name-status --diff-filter=ACMR -- '*.swift' || true
git diff --name-status -- '*.swift' || true

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 349


🌐 Web query:

SwiftFormat official Git hook staged files working tree content

💡 Result:

To run SwiftFormat on only your staged files as a Git pre-commit hook, the official documentation and community consensus recommend using the tool git-format-staged [1][2]. Using this tool prevents issues where formatting partially staged files results in committing both staged and unstaged changes [3]. Installation and Setup: 1. Ensure the SwiftFormat command-line tool is installed [1][2]. 2. Install git-format-staged (e.g., via npm or as a standalone script) [1][2][4]. 3. Navigate to your project folder and create or edit the file at.git/hooks/pre-commit [1][2]. 4. Add the following content to that file: #!/bin/bash git-format-staged --formatter "swiftformat stdin --stdin-path '{}'" "*.swift" 5. Make the hook executable by running: chmod +x.git/hooks/pre-commit [1][2]. This configuration ensures that only the staged portions of your.swift files are formatted [3][2]. When you run git commit, SwiftFormat will process the staged content; running git commit --no-verify will bypass this hook if necessary [1][2]. Note that Git hooks stored in.git/hooks are not automatically tracked by version control [1][2]. For collaborative projects, teams often use a script to install these hooks or rely on CI/CD pipelines to enforce formatting [1][2][4]. Alternative approaches, such as manually iterating over staged files with a loop and re-staging them, are possible but less robust compared to using git-format-staged, which is designed specifically to handle the complexities of the git index [5][6].

Citations:


🌐 Web query:

SwiftLint official command line explicit file paths lint

💡 Result:

To lint specific files or directories using SwiftLint from the command line, you can provide the file paths as arguments directly to the swiftlint or swiftlint lint command [1][2]. Usage example: swiftlint lint Path/To/File.swift Path/To/AnotherFile.swift Path/To/Directory/ If no paths are specified, SwiftLint defaults to linting the current working directory [1]. Key considerations: 1. Argument Handling: When you pass explicit file paths to the command line, SwiftLint typically lints those files directly [3][4]. 2. Configuration Files: If you provide a configuration file using the --config flag, it is intended to act as an override for the default.swiftlint.yml configuration [5][6]. 3. Script Input Files: For CI/CD workflows or complex build phases (such as when integrating with Xcode's file lists or git output), you can use the --use-script-input-files or --use-script-input-file-lists flags [5][2]. This allows you to provide a list of files via environment variables (e.g., SCRIPT_INPUT_FILE_0, SCRIPT_INPUT_FILE_1) instead of command-line arguments [5][7]. 4. Behavior with 'Included' paths: Be aware that if your.swiftlint.yml file contains an explicit included: key, some versions of SwiftLint may ignore directory paths provided on the command line in favor of the paths defined in the configuration file [5][8]. For common use cases like linting only changed files, you can use shell expansion to pass file paths dynamically: swiftlint lint $(git diff --name-only --diff-filter=ACM "*.swift") [4]

Citations:


Lint the staged snapshot, not the working tree.

staged() returns path names only. ShellCheck, SwiftFormat, and SwiftLint then read those paths from the working tree. Read each index blob through a temporary file or stdin path, or reject files with unstaged changes. Add a regression test for staged-clean and working-tree-bad content.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.githooks/pre-commit at line 10, Update the pre-commit lint flow around
staged() so ShellCheck, SwiftFormat, and SwiftLint analyze the staged index
snapshot rather than working-tree paths; materialize each index blob through
temporary files or stdin, or reject files that also have unstaged changes. Add a
regression test covering staged-clean content with conflicting bad working-tree
content.

Comment thread .githooks/pre-commit
Comment on lines +15 to +18
if [[ -n "$(staged '*.sh' | tr -d '\0')" ]]; then
if command -v shellcheck >/dev/null; then
note "shellcheck"
staged '*.sh' | xargs -0 shellcheck || fail=1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 6 'shellcheck|swiftformat|swiftlint|\.sh|\.swift' .

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 50391


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- tracked workflow and hook files ---'
git ls-files '.github/**' '.githooks/**' '*workflow*' '*ci*' | sed -n '1,160p'

printf '%s\n' '--- workflow and hook references ---'
find .github .githooks -type f -maxdepth 3 -print 2>/dev/null | while IFS= read -r file; do
  printf '\n### %s\n' "$file"
  rg -n -C 5 'shellcheck|swiftformat|swiftlint|staged|find |git diff|\.sh|\.swift|paths:|paths-ignore:' "$file" || true
done

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 6675


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- repository configuration files ---'
git ls-files -z | python3 - <<'PY'
import sys
paths = sys.stdin.buffer.read().split(b'\0')
for raw in paths:
    path = raw.decode()
    name = path.rsplit('/', 1)[-1]
    if name in {'.swiftformat', '.swiftlint.yml', '.swiftlint.yaml', '.gitignore'} or 'workflow' in path:
        print(path)
PY

printf '%s\n' '--- relevant configuration contents ---'
for file in .swiftformat .swiftlint.yml .swiftlint.yaml .gitignore; do
  if [ -f "$file" ]; then
    printf '\n### %s\n' "$file"
    cat -n "$file"
  fi
done

printf '%s\n' '--- tracked files relevant to selector boundaries ---'
git ls-files -z | python3 - <<'PY'
import os, stat, sys
paths = [p.decode() for p in sys.stdin.buffer.read().split(b'\0') if p]
for path in paths:
    base = path.rsplit('/', 1)[-1]
    if base.endswith(('.sh', '.bash', '.zsh', '.ksh', '.swift')) or '.' not in base:
        mode = os.stat(path).st_mode
        executable = bool(mode & stat.S_IXUSR)
        print(f"{path}\t{'executable' if executable else 'non-executable'}")
PY

printf '%s\n' '--- workflow files with complete lint sections ---'
cat -n .github/workflows/lint.yml

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 214


🏁 Script executed:

#!/bin/bash
set -euo pipefail

git ls-files -z > /tmp/xctesthtmlreport-files

printf '%s\n' '--- repository configuration files ---'
python3 - <<'PY'
paths = [p.decode() for p in open('/tmp/xctesthtmlreport-files', 'rb').read().split(b'\0') if p]
for path in paths:
    name = path.rsplit('/', 1)[-1]
    if name in {'.swiftformat', '.swiftlint.yml', '.swiftlint.yaml', '.gitignore'} or 'workflow' in path:
        print(path)
PY

printf '%s\n' '--- relevant configuration contents ---'
for file in .swiftformat .swiftlint.yml .swiftlint.yaml .gitignore; do
  if [ -f "$file" ]; then
    printf '\n### %s\n' "$file"
    cat -n "$file"
  fi
done

printf '%s\n' '--- tracked files relevant to selector boundaries ---'
python3 - <<'PY'
import os, stat
paths = [p.decode() for p in open('/tmp/xctesthtmlreport-files', 'rb').read().split(b'\0') if p]
for path in paths:
    base = path.rsplit('/', 1)[-1]
    if base.endswith(('.sh', '.bash', '.zsh', '.ksh', '.swift')) or '.' not in base:
        mode = os.stat(path).st_mode
        executable = bool(mode & stat.S_IXUSR)
        print(f"{path}\t{'executable' if executable else 'non-executable'}")
PY

printf '%s\n' '--- lint workflow ---'
cat -n .github/workflows/lint.yml

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 10243


Align the hook selectors with CI.

CI checks only *.sh files for ShellCheck, so extensionless and other shell extensions are not missed by the selector. However, CI excludes Sources/XCTestHTMLReportCore/Classes/HTMLTemplates.swift through both .swiftformat and .swiftlint.yml, while the hook selects every staged *.swift path. Preserve the generated-file exclusion in the hook, or document why the hook intentionally differs from CI.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.githooks/pre-commit around lines 15 - 18, Align the pre-commit hook’s
staged Swift-file selector with CI by excluding
Sources/XCTestHTMLReportCore/Classes/HTMLTemplates.swift, while preserving the
existing *.sh ShellCheck selector. Update the relevant staged-file filtering
logic in the hook and retain the exclusion consistently with .swiftformat and
.swiftlint.yml.

Comment thread .githooks/pre-commit
note "swiftlint"
# SwiftLint reads .swiftlint.yml for its own include/exclude rules, so pass
# paths explicitly to keep the hook scoped to what is being committed.
staged '*.swift' | xargs -0 swiftlint lint --quiet || fail=1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

command -v swiftlint >/dev/null
swiftlint lint --help | grep -q -- '--use-script-input-files'

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
printf 'struct One { let value = 1 }\n' > "$tmp/One.swift"
printf 'struct Two { let value = 2 }\n' > "$tmp/Two.swift"

(
  cd "$tmp"
  swiftlint lint --quiet "$tmp/One.swift" "$tmp/Two.swift"
)

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 803


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- hook and configuration files ---'
git ls-files '.githooks/pre-commit' '.swiftlint.yml' '.swiftlint.yaml' 'Package.swift' 'README.md'
printf '%s\n' '--- pre-commit context ---'
sed -n '1,90p' .githooks/pre-commit
printf '%s\n' '--- SwiftLint references ---'
rg -n --hidden --glob '!/.git/**' 'swiftlint|SCRIPT_INPUT_FILE|force-exclude|staged' .
printf '%s\n' '--- SwiftLint version and supported flags ---'
swiftlint version 2>&1 || true
swiftlint lint --help 2>&1 | grep -E -- '--use-script-input-files|--force-exclude|Usage|CONFIGURATION' || true
printf '%s\n' '--- SwiftLint configuration ---'
for f in .swiftlint.yml .swiftlint.yaml; do
  if [ -f "$f" ]; then
    cat -n "$f"
  fi
done

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 5120


🏁 Script executed:

#!/bin/bash
set -u

printf '%s\n' '--- full lint usage and relevant options ---'
swiftlint lint --help 2>&1 | sed -n '1,220p'

printf '%s\n' '--- controlled positional-path test ---'
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/Sources"
printf 'struct One { let value = 1 }\n' > "$tmp/Sources/One.swift"
printf 'struct Two { let value = 2 }\n' > "$tmp/Sources/Two.swift"
cat > "$tmp/.swiftlint.yml" <<'YAML'
included:
  - Sources
disabled_rules:
  - statement_position
YAML

(
  cd "$tmp"
  swiftlint lint --no-cache --quiet "$tmp/Sources/One.swift" "$tmp/Sources/Two.swift" 2>&1
)
positional_status=$?
printf 'positional_status=%s\n' "$positional_status"

printf '%s\n' '--- controlled script-input-file test ---'
(
  cd "$tmp"
  export SCRIPT_INPUT_FILE_COUNT=2
  export SCRIPT_INPUT_FILE_0="$tmp/Sources/One.swift"
  export SCRIPT_INPUT_FILE_1="$tmp/Sources/Two.swift"
  swiftlint lint --no-cache --quiet --use-script-input-files 2>&1
)
script_input_status=$?
printf 'script_input_status=%s\n' "$script_input_status"

printf '%s\n' '--- force-exclude behavior with an explicitly passed excluded file ---'
printf 'struct Excluded { let value = 1 }\n' > "$tmp/Sources/Excluded.swift"
cat > "$tmp/.swiftlint.yml" <<'YAML'
included:
  - Sources
excluded:
  - Sources/Excluded.swift
line_length:
  error: 1
disabled_rules:
  - statement_position
YAML

(
  cd "$tmp"
  swiftlint lint --no-cache --quiet "$tmp/Sources/Excluded.swift" 2>&1
)
without_force_exclude_status=$?
printf 'without_force_exclude_status=%s\n' "$without_force_exclude_status"

(
  cd "$tmp"
  swiftlint lint --no-cache --quiet --force-exclude "$tmp/Sources/Excluded.swift" 2>&1
)
with_force_exclude_status=$?
printf 'with_force_exclude_status=%s\n' "$with_force_exclude_status"

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 4081


🏁 Script executed:

#!/bin/bash
set -u

tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
mkdir -p "$tmp/Sources"
printf 'struct Included { let value = 1 }\n' > "$tmp/Sources/Included.swift"
printf 'struct Excluded { let value = 1 }\n' > "$tmp/Sources/Excluded.swift"
cat > "$tmp/.swiftlint.yml" <<'YAML'
included:
  - Sources
excluded:
  - Sources/Excluded.swift
line_length:
  error: 1
disabled_rules:
  - statement_position
YAML

printf '%s\n' '--- mixed explicit paths with --force-exclude ---'
(
  cd "$tmp"
  swiftlint lint --no-cache --quiet --force-exclude \
    "$tmp/Sources/Included.swift" "$tmp/Sources/Excluded.swift" 2>&1
)
printf 'mixed_status=%s\n' "$?"

printf '%s\n' '--- only explicitly excluded path with --force-exclude ---'
(
  cd "$tmp"
  swiftlint lint --no-cache --quiet --force-exclude \
    "$tmp/Sources/Excluded.swift" 2>&1
)
printf 'excluded_only_status=%s\n' "$?"

Repository: XCTestHTMLReport/XCTestHTMLReport

Length of output: 564


Apply configured exclusions to staged paths.

SwiftLint supports multiple positional paths, so xargs is valid. Add --force-exclude to honor .swiftlint.yml exclusions. Handle the No lintable files found result as success when all staged Swift files are excluded.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.githooks/pre-commit at line 43, Update the SwiftLint invocation in the
pre-commit hook to include --force-exclude so configured exclusions from
.swiftlint.yml apply to staged Swift paths. Preserve xargs handling for multiple
paths, and treat SwiftLint’s “No lintable files found” result as success when
all staged files are excluded, while retaining failure behavior for genuine lint
errors.

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.

Add pre-commit hooks for format and lint

1 participant