Skip to content
This repository was archived by the owner on Jul 2, 2026. It is now read-only.

Commit 01f2ef9

Browse files
committed
feat: add rust-max-lines hook
Built-in shell hook that fails when a Rust source file exceeds a configurable line-count ceiling. Default --max=1000; counts total file lines via wc -l, no Rust toolchain required. Scoped to types: [rust]. Also escapes the C# heading anchor so the rumdl markdown formatter stops eating the trailing #.
1 parent a8cb251 commit 01f2ef9

9 files changed

Lines changed: 1287 additions & 2 deletions

File tree

.pre-commit-hooks.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,14 @@
254254
pass_filenames: false
255255
args: []
256256

257+
- id: rust-max-lines
258+
name: rust-max-lines (Rust file line-count ceiling)
259+
description: Fail when a Rust source file exceeds the configured line-count ceiling.
260+
entry: hooks/rust-max-lines/run.sh
261+
language: script
262+
types: [rust]
263+
args: ["--max=1000"]
264+
257265
- id: gleam-format
258266
name: gleam format
259267
description: Format Gleam source files using the project's installed gleam toolchain.

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.3.0] - 2026-06-06
11+
12+
### Added
13+
14+
- `rust-max-lines`: new built-in hook that fails when a Rust source file
15+
exceeds a configurable line-count ceiling. Default `--max=1000`; pure shell
16+
via `wc -l`, no Rust toolchain required. Scoped to `types: [rust]`.
17+
(`hooks/rust-max-lines/run.sh`)
18+
1019
## [1.2.4] - 2026-06-04
1120

1221
### Fixed

README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Each hook falls into one of four implementation categories:
8181
| `cargo-machete` | Detect unused Rust dependencies | Toolchain wrapper (pinned) |
8282
| `cargo-deny` | Check Rust dependency advisories and licenses | Toolchain wrapper (pinned) |
8383
| `rustdoc-lint` | Check Rust documentation completeness (v1.1.0+) | Toolchain wrapper |
84+
| `rust-max-lines` | Fail when a Rust file exceeds a line-count ceiling (default 1000) | Built-in |
8485

8586
### Python
8687

@@ -130,7 +131,7 @@ Each hook falls into one of four implementation categories:
130131
| `steep` | Type-check Ruby projects | Toolchain wrapper |
131132
| `yard-coverage` | Check Ruby YARD documentation coverage (v1.1.0+) | Toolchain wrapper |
132133

133-
### C#
134+
### C\#
134135

135136
| Hook | Purpose | Category |
136137
|---|---|---|
@@ -276,12 +277,14 @@ All doc hooks are opt-in — they complement linting/formatting hooks but are mo
276277
Use this table to copy-paste a language block into your `.pre-commit-config.yaml`:
277278

278279
**Shell:**
280+
279281
```yaml
280282
- id: shfmt
281283
- id: shellcheck
282284
```
283285
284286
**Rust:**
287+
285288
```yaml
286289
- id: cargo-fmt
287290
- id: cargo-check
@@ -290,15 +293,18 @@ Use this table to copy-paste a language block into your `.pre-commit-config.yaml
290293
- id: cargo-machete
291294
- id: cargo-deny
292295
- id: rustdoc-lint
296+
- id: rust-max-lines
293297
```
294298
295299
**Python:**
300+
296301
```yaml
297302
- id: mypy
298303
- id: pydocstyle
299304
```
300305
301306
**Go:**
307+
302308
```yaml
303309
- id: go-fmt
304310
- id: golangci-lint
@@ -307,18 +313,21 @@ Use this table to copy-paste a language block into your `.pre-commit-config.yaml
307313
```
308314
309315
**TypeScript:**
316+
310317
```yaml
311318
- id: tsc-typecheck
312319
- id: tsdoc-lint
313320
```
314321
315322
**JavaScript/TypeScript (formatting):**
323+
316324
```yaml
317325
- id: oxlint
318326
- id: oxfmt
319327
```
320328
321329
**Java:**
330+
322331
```yaml
323332
- id: palantir-java-format
324333
- id: checkstyle
@@ -328,6 +337,7 @@ Use this table to copy-paste a language block into your `.pre-commit-config.yaml
328337
```
329338
330339
**Kotlin:**
340+
331341
```yaml
332342
- id: ktfmt
333343
- id: detekt
@@ -336,6 +346,7 @@ Use this table to copy-paste a language block into your `.pre-commit-config.yaml
336346
```
337347
338348
**Ruby:**
349+
339350
```yaml
340351
- id: rubocop
341352
- id: rubocop-lint
@@ -344,27 +355,31 @@ Use this table to copy-paste a language block into your `.pre-commit-config.yaml
344355
```
345356
346357
**C#:**
358+
347359
```yaml
348360
- id: dotnet-format
349361
- id: dotnet-format-check
350362
- id: cs-xmldoc-lint
351363
```
352364
353365
**PHP:**
366+
354367
```yaml
355368
- id: php-cs-fixer
356369
- id: phpstan
357370
- id: phpdoc-lint
358371
```
359372
360373
**Elixir:**
374+
361375
```yaml
362376
- id: mix-format
363377
- id: mix-credo
364378
- id: ex-doc-coverage
365379
```
366380
367381
**R:**
382+
368383
```yaml
369384
- id: air-format
370385
- id: air-check
@@ -373,58 +388,67 @@ Use this table to copy-paste a language block into your `.pre-commit-config.yaml
373388
```
374389
375390
**Swift:**
391+
376392
```yaml
377393
- id: swift-format
378394
- id: swiftlint
379395
- id: swift-doc-coverage
380396
```
381397
382398
**Dart:**
399+
383400
```yaml
384401
- id: dart-format
385402
- id: dart-analyze
386403
- id: dart-doc-lint
387404
```
388405
389406
**Zig:**
407+
390408
```yaml
391409
- id: zig-fmt
392410
- id: zig-build-check
393411
- id: zig-doc-lint
394412
```
395413
396414
**Gleam:**
415+
397416
```yaml
398417
- id: gleam-format
399418
- id: gleam-check
400419
```
401420
402421
**C/C++:**
422+
403423
```yaml
404424
- id: clang-format
405425
- id: clang-tidy
406426
- id: cppcheck
407427
```
408428
409429
**Docker:**
430+
410431
```yaml
411432
- id: hadolint
412433
```
413434
414435
**Helm/Kubernetes:**
436+
415437
```yaml
416438
- id: helm-lint
417439
- id: kubeconform
418440
```
419441
420442
**Markdown:**
443+
421444
```yaml
422445
- id: rumdl-fmt
423446
- id: markdownlint-rumdl-strict
424447
- id: textlint
425448
```
426449
427450
**Polyglot (Alef):**
451+
428452
```yaml
429453
- id: alef-docs-fresh
430454
```
@@ -454,7 +478,7 @@ CI tests against `ubuntu-latest`, `macos-latest`, `windows-latest` × Python 3.1
454478

455479
## Cache layout
456480

457-
```
481+
```text
458482
${KREUZBERG_HOOKS_CACHE:-$HOME/.cache/kreuzberg-pre-commit-hooks}/<tool>/<version>/<arch>/
459483
```
460484

docs/hooks/rust-max-lines.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# rust-max-lines
2+
3+
Fail when a Rust source file exceeds a configurable line-count ceiling.
4+
5+
This hook is check-only.
6+
7+
## Configuration
8+
9+
```yaml
10+
- id: rust-max-lines
11+
args: ["--max=1000"]
12+
```
13+
14+
Override the ceiling per repo:
15+
16+
```yaml
17+
- id: rust-max-lines
18+
args: ["--max=1500"]
19+
```
20+
21+
`N` must be a positive integer; any other value exits with status 2.
22+
23+
## Counting method
24+
25+
Total file lines via `wc -l` — blanks and comments included. Pure shell, no
26+
Rust toolchain required.
27+
28+
## Implementation
29+
30+
Built-in shell hook. Source: `hooks/rust-max-lines/run.sh`.

hooks/rust-max-lines/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# rust-max-lines
2+
3+
Fail when a Rust source file exceeds a configurable line-count ceiling. Pure
4+
shell — no external toolchain required.
5+
6+
## Category
7+
8+
Built-in. The hook counts total file lines (including blanks and comments) via
9+
`wc -l` and exits non-zero when any staged `.rs` file is above the threshold.
10+
11+
## Default args
12+
13+
`--max=1000` — files with more than 1000 lines fail the hook. Override per repo:
14+
15+
```yaml
16+
- id: rust-max-lines
17+
args: ["--max=1500"]
18+
```
19+
20+
Only `--max=N` is accepted. `N` must be a positive integer.
21+
22+
## Why total lines
23+
24+
Counting raw file length avoids parser complexity and gives a deterministic
25+
signal that's easy to reason about in PR review. If you want stricter "code
26+
only" metrics, pair with `cargo-clippy` cognitive-complexity lints.
27+
28+
## Troubleshooting
29+
30+
- `rust-max-lines: --max must be a positive integer` — the configured `--max`
31+
value is non-numeric or zero. Use `args: ["--max=1000"]` form.
32+
- File exceeds limit — split the module into focused submodules; in Rust this
33+
is usually a sign one type is doing too much.

hooks/rust-max-lines/run.sh

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#!/usr/bin/env bash
2+
# rust-max-lines — fail when any staged Rust source exceeds the configured
3+
# line-count ceiling. Counts total file lines (including blanks and comments)
4+
# via `wc -l`, which is fast and parser-free.
5+
#
6+
# Configuration:
7+
# args: ["--max=<N>"] default 1000
8+
#
9+
# All non-flag arguments are treated as file paths supplied by pre-commit.
10+
set -euo pipefail
11+
12+
MAX_LINES=1000
13+
files=()
14+
15+
for arg in "$@"; do
16+
case "$arg" in
17+
--max=*)
18+
MAX_LINES="${arg#--max=}"
19+
;;
20+
--max)
21+
printf 'rust-max-lines: --max requires "=N" form (e.g. --max=1000)\n' >&2
22+
exit 2
23+
;;
24+
--*)
25+
printf 'rust-max-lines: unknown flag %q\n' "$arg" >&2
26+
exit 2
27+
;;
28+
*)
29+
files+=("$arg")
30+
;;
31+
esac
32+
done
33+
34+
if ! [[ "$MAX_LINES" =~ ^[1-9][0-9]*$ ]]; then
35+
printf 'rust-max-lines: --max must be a positive integer, got %q\n' "$MAX_LINES" >&2
36+
exit 2
37+
fi
38+
39+
if [[ ${#files[@]} -eq 0 ]]; then
40+
exit 0
41+
fi
42+
43+
status=0
44+
for file in "${files[@]}"; do
45+
if [[ ! -f "$file" ]]; then
46+
continue
47+
fi
48+
lines=$(wc -l <"$file" | tr -d '[:space:]')
49+
if ((lines > MAX_LINES)); then
50+
printf '%s: %d lines exceeds limit of %d\n' "$file" "$lines" "$MAX_LINES" >&2
51+
status=1
52+
fi
53+
done
54+
55+
exit "$status"

0 commit comments

Comments
 (0)