Skip to content

Commit 32f78fc

Browse files
ViralBShahclaude
andcommitted
Add trailing whitespace check (#720)
## Summary - Adds `.ci/check-whitespace.jl`, adapted from [JuliaLang/julia's `contrib/check-whitespace.jl`](https://github.com/JuliaLang/julia/blob/master/contrib/check-whitespace.jl), with patterns trimmed to files relevant to this repo (`*.jl`, `*.md`, `*.yml`, `*Makefile`). - Adds `.github/workflows/Whitespace.yml` to run the check on push to `main` and on pull requests. - Fixes pre-existing whitespace violations (trailing whitespace, trailing blank lines, a non-breaking space) so the new check passes. Companion to JuliaLang/LinearAlgebra.jl#1633. ## Test plan - [x] `julia .ci/check-whitespace.jl` reports no issues locally - [x] Whitespace workflow passes in CI 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Viral B. Shah <ViralBShah@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> (cherry picked from commit 5926672)
1 parent f647b92 commit 32f78fc

7 files changed

Lines changed: 129 additions & 6 deletions

File tree

.ci/check-whitespace.jl

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#!/usr/bin/env julia
2+
3+
const patterns = split("""
4+
*.jl
5+
*.md
6+
*.yml
7+
*Makefile
8+
""")
9+
10+
const is_gha = something(tryparse(Bool, get(ENV, "GITHUB_ACTIONS", "false")), false)
11+
12+
# Note: `git ls-files` gives `/` as a path separator on Windows,
13+
# so we just use `/` for all platforms.
14+
allow_tabs(path) =
15+
endswith(path, "Makefile") ||
16+
endswith(path, ".make") ||
17+
endswith(path, ".mk")
18+
19+
function check_whitespace()
20+
errors = Set{Tuple{String,Int,String}}()
21+
files_to_check = filter(arg -> !startswith(arg, "-"), ARGS)
22+
if isempty(files_to_check)
23+
if "--stdin" in ARGS
24+
files_to_check = collect(eachline(stdin))
25+
else
26+
files_to_check = collect(eachline(`git ls-files -- $patterns`))
27+
end
28+
end
29+
30+
files_fixed = 0
31+
if "--fix" in ARGS
32+
for path in files_to_check
33+
content = newcontent = read(path, String)
34+
isempty(content) && continue
35+
if !allow_tabs(path)
36+
tabpattern = r"^([ \t]+)"m => (x -> replace(x, r"((?: {4})*)( *\t)" => s"\1 ")) # Replace tab sequences at start of line after any number of 4-space groups
37+
newcontent = replace(newcontent, tabpattern)
38+
end
39+
newcontent = replace(newcontent,
40+
r"\s*$" => '\n', # Remove trailing whitespace and normalize line ending at eof
41+
r"\s*?[\r\n]" => '\n', # Remove trailing whitespace and normalize line endings on each line
42+
r"\xa0" => ' ' # Replace non-breaking spaces
43+
)
44+
if content != newcontent
45+
write(path, newcontent)
46+
files_fixed += 1
47+
end
48+
end
49+
if files_fixed > 0
50+
println(stderr, "Fixed whitespace issues in $files_fixed files.")
51+
end
52+
end
53+
54+
for path in files_to_check
55+
lineno = 0
56+
non_blank = 0
57+
58+
file_err(msg) = push!(errors, (path, 0, msg))
59+
line_err(msg) = push!(errors, (path, lineno, msg))
60+
61+
isfile(path) || continue
62+
for line in eachline(path, keep=true)
63+
lineno += 1
64+
contains(line, '\r') && file_err("non-UNIX line endings")
65+
contains(line, '\ua0') && line_err("non-breaking space")
66+
allow_tabs(path) ||
67+
contains(line, '\t') && line_err("tab")
68+
endswith(line, '\n') || line_err("no trailing newline")
69+
line = chomp(line)
70+
endswith(line, r"\s") && line_err("trailing whitespace")
71+
contains(line, r"\S") && (non_blank = lineno)
72+
end
73+
non_blank < lineno && line_err("trailing blank lines")
74+
end
75+
76+
if isempty(errors)
77+
println(stderr, "Whitespace check found no issues.")
78+
exit(0)
79+
else
80+
println(stderr, "Whitespace check found $(length(errors)) issues:")
81+
for (path, lineno, msg) in sort!(collect(errors))
82+
if lineno == 0
83+
println(stderr, "$path -- $msg")
84+
if is_gha
85+
println(stdout, "::warning title=Whitespace check,file=", path, "::", msg)
86+
end
87+
else
88+
println(stderr, "$path:$lineno -- $msg")
89+
if is_gha
90+
println(stdout, "::warning title=Whitespace check,file=", path, ",line=", lineno, "::", msg)
91+
end
92+
end
93+
end
94+
exit(1)
95+
end
96+
end
97+
98+
check_whitespace()

.github/workflows/Whitespace.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Whitespace
2+
3+
permissions: {}
4+
5+
on:
6+
push:
7+
branches:
8+
- main
9+
pull_request:
10+
11+
jobs:
12+
whitespace:
13+
name: Check whitespace
14+
runs-on: ubuntu-latest
15+
timeout-minutes: 2
16+
steps:
17+
- name: Checkout the JuliaSparse/SparseArrays.jl repository
18+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
19+
with:
20+
persist-credentials: false
21+
- uses: julia-actions/setup-julia@f6f565d9f7cf12f53dc8045742460d6260ad3b39 # v3.0.1
22+
with:
23+
version: '1.11.6'
24+
- name: Check whitespace
25+
run: |
26+
julia .ci/check-whitespace.jl

gen/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ all: clean download
77
julia --project generator.jl ./SuiteSparse-$(VER)
88

99
clean:
10-
rm -fr *.tar.gz SuiteSparse*
10+
rm -fr *.tar.gz SuiteSparse*
1111

1212
download:
1313
curl -L -O https://github.com/JuliaBinaryWrappers/SuiteSparse_jll.jl/releases/download/SuiteSparse-v$(VER)%2B0/SuiteSparse.v$(VER).x86_64-linux-gnu.tar.gz

src/solvers/spqr.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function _qr!(ordering::Integer, tol::Real, econ::Integer, getCTX::Integer,
8585
# Free memory allocated by SPQR. This call will make sure that the
8686
# correct deallocator function is called and that the memory count in
8787
# the common struct is updated
88-
Ti === Int64 ?
88+
Ti === Int64 ?
8989
cholmod_l_free(n, sizeof(Ti), e, CHOLMOD.getcommon(Ti)) :
9090
cholmod_free(n, sizeof(Ti), e, CHOLMOD.getcommon(Ti))
9191
end
@@ -100,7 +100,7 @@ function _qr!(ordering::Integer, tol::Real, econ::Integer, getCTX::Integer,
100100
# Free memory allocated by SPQR. This call will make sure that the
101101
# correct deallocator function is called and that the memory count in
102102
# the common struct is updated
103-
Ti === Int64 ?
103+
Ti === Int64 ?
104104
cholmod_l_free(m, sizeof(Ti), hpinv, CHOLMOD.getcommon(Ti)) :
105105
cholmod_free(m, sizeof(Ti), hpinv, CHOLMOD.getcommon(Ti))
106106
end

src/sparseconvert.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,4 +280,3 @@ function _sparse_gen(m, n, newcolptr, newrowval, newnzval)
280280
newcolptr[1] = 1
281281
SparseMatrixCSC(m, n, newcolptr, newrowval, newnzval)
282282
end
283-

test/cholmod.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1025,7 +1025,7 @@ end
10251025
end
10261026

10271027
f = ones(size(K, 1))
1028-
u = K \ f
1028+
u = K \ f
10291029
residual = norm(f - K * u) / norm(f)
10301030
@test residual < 1e-6
10311031
end

test/fixed.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ end
118118
@test f(x, y, z) == 0
119119
t = similar(x)
120120
@test typeof(t) == typeof(x)
121-
@test struct_eq(t, x)
121+
@test struct_eq(t, x)
122122
end
123123

124124
@testset "Issue #190" begin

0 commit comments

Comments
 (0)