pre-commit: add a hook for checking for duplicate empty lines

It's a bit annoying to only find those during a CI pipeline run, so
let's add a hook for it. Pre-commit doesn't seem to have something
useful for duplicate line detection so let's hack up a quick script
that can do this for us, together with the other whitespace checks in
the CI so we're consistent.

Excluded from the duplicate line checks are anything "Not C" and the
"include/" directory since we don't control those files.

Part-of: <https://gitlab.freedesktop.org/libinput/libinput/-/merge_requests/1154>
This commit is contained in:
Peter Hutterer 2025-03-10 21:04:46 +10:00 committed by Marge Bot
parent 58315eb9d4
commit dd78765674
4 changed files with 115 additions and 22 deletions

View file

@ -204,17 +204,7 @@ check-whitespace:
- .fdo.ci-fairy - .fdo.ci-fairy
stage: sanity check stage: sanity check
script: script:
# remove trailing whitespaces - .gitlab-ci/whitespace-check.py $(git ls-files)
- sed -i 's/ *$//' $(git ls-files)
- git diff --exit-code || (echo "ERROR - Trailing whitespaces in patchset, please fix" && false)
- sed -i 's/\t*$//' $(git ls-files)
- git diff --exit-code || (echo "ERROR - Trailing tabs in patchset, please fix" && false)
# search for tab after space
- sed -i 's/ \t//' $(git ls-files)
- git diff --exit-code || (echo "ERROR - Tab after space in patchset, please fix" && false)
# search for duplicated empty lines
- sed -i '/^$/N;/^\n$/D' $(git ls-files src/*.{h,c} tools/*.{h,c} udev/*.c test/*.c)
- git diff --exit-code || (echo "ERROR - Duplicated empty lines, please fix" && false)
# #
# pre-commit hooks # pre-commit hooks

View file

@ -192,17 +192,7 @@ check-whitespace:
- .fdo.ci-fairy - .fdo.ci-fairy
stage: sanity check stage: sanity check
script: script:
# remove trailing whitespaces - .gitlab-ci/whitespace-check.py $(git ls-files)
- sed -i 's/ *$//' $(git ls-files)
- git diff --exit-code || (echo "ERROR - Trailing whitespaces in patchset, please fix" && false)
- sed -i 's/\t*$//' $(git ls-files)
- git diff --exit-code || (echo "ERROR - Trailing tabs in patchset, please fix" && false)
# search for tab after space
- sed -i 's/ \t//' $(git ls-files)
- git diff --exit-code || (echo "ERROR - Tab after space in patchset, please fix" && false)
# search for duplicated empty lines
- sed -i '/^$/N;/^\n$/D' $(git ls-files src/*.{h,c} tools/*.{h,c} udev/*.c test/*.c)
- git diff --exit-code || (echo "ERROR - Duplicated empty lines, please fix" && false)
# #
# pre-commit hooks # pre-commit hooks

107
.gitlab-ci/whitespace-check.py Executable file
View file

@ -0,0 +1,107 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
from pathlib import Path
from dataclasses import dataclass
import argparse
import itertools
import os
import sys
@dataclass
class WhitespaceError:
message: str
lineno: int
nlines: int = 1
column: None | int = None
ncolumns: int = 1
def test_duplicate_empty_lines(lines: list[str]) -> list[WhitespaceError]:
errors = []
for idx, (l1, l2) in enumerate(itertools.pairwise(lines)):
if not l1 and not l2:
errors.append(WhitespaceError("Duplicated empty lines", idx, nlines=2))
return errors
def test_tab_after_space(lines: list[str]) -> list[WhitespaceError]:
errors = []
for idx, l in enumerate(lines):
index = l.find(" \t")
if index > -1:
errors.append(
WhitespaceError(
"Tab after space", idx, nlines=index, column=index, ncolumns=2
)
)
return errors
def test_trailing_whitespace(lines: list[str]) -> list[WhitespaceError]:
errors = []
for idx, l in enumerate(lines):
if l.rstrip() != l:
errors.append(WhitespaceError("Trailing whitespace", idx))
return errors
def main():
parser = argparse.ArgumentParser(description="Whitespace checker script")
parser.add_argument(
"files",
metavar="FILES",
type=Path,
nargs="+",
help="The files to check",
)
args = parser.parse_args()
have_errors: bool = False
if os.isatty(sys.stderr.fileno()):
red = "\x1b[0;31m"
reset = "\x1b[0m"
else:
red = ""
reset = ""
for file in args.files:
lines = [l.rstrip("\n") for l in file.open().readlines()]
errors = []
errors.extend(test_tab_after_space(lines))
errors.extend(test_trailing_whitespace(lines))
if any(file.name.endswith(suffix) for suffix in [".c", ".h"]):
if not file.parts[0] == "include":
errors.extend(test_duplicate_empty_lines(lines))
for e in errors:
print(f"{red}ERROR: {e.message} in {file}:{reset}", file=sys.stderr)
print(f"{'-' * 72}", file=sys.stderr)
lineno = max(0, e.lineno - 5)
for idx, l in enumerate(lines[lineno : lineno + 10]):
if e.lineno <= lineno + idx < e.lineno + e.nlines:
prefix = "->"
hl = red
nohl = reset
else:
prefix = " "
hl = ""
nohl = ""
print(f"{hl}{lineno + idx:3d}: {prefix} {l.rstrip()}{nohl}")
print(f"{'-' * 72}", file=sys.stderr)
if errors:
have_errors = True
if have_errors:
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -18,3 +18,9 @@ repos:
hooks: hooks:
- id: check-commits - id: check-commits
- id: generate-template - id: generate-template
- repo: local
hooks:
- id: run-sed-script
name: Check for whitespace errors
entry: ./.gitlab-ci/whitespace-check.py
language: system