From dd78765674ee634adf672a2519ac53d422fb9d1e Mon Sep 17 00:00:00 2001 From: Peter Hutterer Date: Mon, 10 Mar 2025 21:04:46 +1000 Subject: [PATCH] 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: --- .gitlab-ci.yml | 12 +--- .gitlab-ci/ci.template | 12 +--- .gitlab-ci/whitespace-check.py | 107 +++++++++++++++++++++++++++++++++ .pre-commit-config.yaml | 6 ++ 4 files changed, 115 insertions(+), 22 deletions(-) create mode 100755 .gitlab-ci/whitespace-check.py diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b07155af..a5d7a260 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -204,17 +204,7 @@ check-whitespace: - .fdo.ci-fairy stage: sanity check script: - # remove trailing whitespaces - - 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) + - .gitlab-ci/whitespace-check.py $(git ls-files) # # pre-commit hooks diff --git a/.gitlab-ci/ci.template b/.gitlab-ci/ci.template index 9e7b09ca..cfc0ce46 100644 --- a/.gitlab-ci/ci.template +++ b/.gitlab-ci/ci.template @@ -192,17 +192,7 @@ check-whitespace: - .fdo.ci-fairy stage: sanity check script: - # remove trailing whitespaces - - 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) + - .gitlab-ci/whitespace-check.py $(git ls-files) # # pre-commit hooks diff --git a/.gitlab-ci/whitespace-check.py b/.gitlab-ci/whitespace-check.py new file mode 100755 index 00000000..ac21ed1f --- /dev/null +++ b/.gitlab-ci/whitespace-check.py @@ -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() diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 68f914fa..bc80fcf7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -18,3 +18,9 @@ repos: hooks: - id: check-commits - id: generate-template +- repo: local + hooks: + - id: run-sed-script + name: Check for whitespace errors + entry: ./.gitlab-ci/whitespace-check.py + language: system