diff --git a/test/lint/lint-whitespace.py b/test/lint/lint-whitespace.py new file mode 100755 index 0000000000..7e2b8565a5 --- /dev/null +++ b/test/lint/lint-whitespace.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2017-2022 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +# +# Check for new lines in diff that introduce trailing whitespace or +# tab characters instead of spaces. + +# We can't run this check unless we know the commit range for the PR. + +import argparse +import os +import re +import sys + +from subprocess import check_output + +EXCLUDED_DIRS = ["depends/patches/", + "contrib/guix/patches/", + "src/leveldb/", + "src/crc32c/", + "src/secp256k1/", + "src/minisketch/", + "src/univalue/", + "src/dashbls/", + "src/immer/", + "src/util/expected.h", + "doc/release-notes/", + "src/qt/locale"] + +def parse_args(): + """Parse command line arguments.""" + parser = argparse.ArgumentParser( + description=""" + Check for new lines in diff that introduce trailing whitespace + or tab characters instead of spaces in unstaged changes, the + previous n commits, or a commit-range. + """, + epilog=f""" + You can manually set the commit-range with the COMMIT_RANGE + environment variable (e.g. "COMMIT_RANGE='47ba2c3...ee50c9e' + {sys.argv[0]}"). Defaults to current merge base when neither + prev-commits nor the environment variable is set. + """) + + parser.add_argument("--prev-commits", "-p", required=False, help="The previous n commits to check") + + return parser.parse_args() + + +def report_diff(selection): + filename = "" + seen = False + seenln = False + + print("The following changes were suspected:") + + for line in selection: + if re.match(r"^diff", line): + filename = line + seen = False + elif re.match(r"^@@", line): + linenumber = line + seenln = False + else: + if not seen: + # The first time a file is seen with trailing whitespace or a tab character, we print the + # filename (preceded by a newline). + print("") + print(filename) + seen = True + if not seenln: + print(linenumber) + seenln = True + print(line) + + +def get_diff(commit_range, check_only_code): + exclude_args = [":(exclude)" + dir for dir in EXCLUDED_DIRS] + + if check_only_code: + what_files = ["*.cpp", "*.h", "*.md", "*.py", "*.sh"] + else: + what_files = ["."] + + diff = check_output(["git", "diff", "-U0", commit_range, "--"] + what_files + exclude_args, universal_newlines=True, encoding="utf8") + + return diff + + +def main(): + args = parse_args() + + if not os.getenv("COMMIT_RANGE"): + if args.prev_commits: + commit_range = "HEAD~" + args.prev_commits + "...HEAD" + else: + # This assumes that the target branch of the pull request will be develop. + merge_base = check_output(["git", "merge-base", "HEAD", "develop"], universal_newlines=True, encoding="utf8").rstrip("\n") + commit_range = merge_base + "..HEAD" + else: + commit_range = os.getenv("COMMIT_RANGE") + + whitespace_selection = [] + tab_selection = [] + + # Check if trailing whitespace was found in the diff. + for line in get_diff(commit_range, check_only_code=False).splitlines(): + if re.match(r"^(diff --git|\@@|^\+.*\s+$)", line): + whitespace_selection.append(line) + + whitespace_additions = [i for i in whitespace_selection if i.startswith("+")] + + # Check if tab characters were found in the diff. + for line in get_diff(commit_range, check_only_code=True).splitlines(): + if re.match(r"^(diff --git|\@@|^\+.*\t)", line): + tab_selection.append(line) + + tab_additions = [i for i in tab_selection if i.startswith("+")] + + ret = 0 + + if len(whitespace_additions) > 0: + print("This diff appears to have added new lines with trailing whitespace.") + report_diff(whitespace_selection) + ret = 1 + + if len(tab_additions) > 0: + print("This diff appears to have added new lines with tab characters instead of spaces.") + report_diff(tab_selection) + ret = 1 + + sys.exit(ret) + + +if __name__ == "__main__": + main() diff --git a/test/lint/lint-whitespace.sh b/test/lint/lint-whitespace.sh deleted file mode 100755 index 29579f5161..0000000000 --- a/test/lint/lint-whitespace.sh +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env bash -# -# Copyright (c) 2017-2020 The Bitcoin Core developers -# Distributed under the MIT software license, see the accompanying -# file COPYING or http://www.opensource.org/licenses/mit-license.php. -# -# Check for new lines in diff that introduce trailing whitespace. - -# We can't run this check unless we know the commit range for the PR. - -export LC_ALL=C -while getopts "?" opt; do - case $opt in - ?) - echo "Usage: $0 [N]" - echo " COMMIT_RANGE='' $0" - echo " $0 -?" - echo "Checks unstaged changes, the previous N commits, or a commit range." - echo "COMMIT_RANGE='47ba2c3...ee50c9e' $0" - exit 0 - ;; - esac -done - -if [ -z "${COMMIT_RANGE}" ]; then - if [ -n "$1" ]; then - COMMIT_RANGE="HEAD~$1...HEAD" - else - # This assumes that the target branch of the pull request will be develop. - MERGE_BASE=$(git merge-base HEAD develop) - COMMIT_RANGE="$MERGE_BASE..HEAD" - fi -fi - -showdiff() { - if ! git diff -U0 "${COMMIT_RANGE}" -- "." ":(exclude)depends/patches/" ":(exclude)contrib/guix/patches/" ":(exclude)src/dashbls/" ":(exclude)src/util/expected.h" ":(exclude)src/immer/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/minisketch/" ":(exclude)src/univalue/" ":(exclude)doc/release-notes/"; then - echo "Failed to get a diff" - exit 1 - fi -} - -showcodediff() { - if ! git diff -U0 "${COMMIT_RANGE}" -- *.cpp *.h *.md *.py *.sh ":(exclude)src/dashbls/" ":(exclude)src/util/expected.h" ":(exclude)src/immer/" ":(exclude)src/leveldb/" ":(exclude)src/crc32c/" ":(exclude)src/secp256k1/" ":(exclude)src/minisketch/" ":(exclude)src/univalue/" ":(exclude)doc/release-notes/"; then - echo "Failed to get a diff" - exit 1 - fi -} - -RET=0 - -# Check if trailing whitespace was found in the diff. -if showdiff | grep -E -q '^\+.*\s+$'; then - echo "This diff appears to have added new lines with trailing whitespace." - echo "The following changes were suspected:" - FILENAME="" - SEEN=0 - SEENLN=0 - while read -r line; do - if [[ "$line" =~ ^diff ]]; then - FILENAME="$line" - SEEN=0 - elif [[ "$line" =~ ^@@ ]]; then - LINENUMBER="$line" - SEENLN=0 - else - if [ "$SEEN" -eq 0 ]; then - # The first time a file is seen with trailing whitespace, we print the - # filename (preceded by a newline). - echo - echo "$FILENAME" - SEEN=1 - fi - if [ "$SEENLN" -eq 0 ]; then - echo "$LINENUMBER" - SEENLN=1 - fi - echo "$line" - fi - done < <(showdiff | grep -E '^(diff --git |@@|\+.*\s+$)') - RET=1 -fi - -# Check if tab characters were found in the diff. -if showcodediff | perl -nle '$MATCH++ if m{^\+.*\t}; END{exit 1 unless $MATCH>0}' > /dev/null; then - echo "This diff appears to have added new lines with tab characters instead of spaces." - echo "The following changes were suspected:" - FILENAME="" - SEEN=0 - SEENLN=0 - while read -r line; do - if [[ "$line" =~ ^diff ]]; then - FILENAME="$line" - SEEN=0 - elif [[ "$line" =~ ^@@ ]]; then - LINENUMBER="$line" - SEENLN=0 - else - if [ "$SEEN" -eq 0 ]; then - # The first time a file is seen with a tab character, we print the - # filename (preceded by a newline). - echo - echo "$FILENAME" - SEEN=1 - fi - if [ "$SEENLN" -eq 0 ]; then - echo "$LINENUMBER" - SEENLN=1 - fi - echo "$line" - fi - done < <(showcodediff | perl -nle 'print if m{^(diff --git |@@|\+.*\t)}') - RET=1 -fi - -exit $RET