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