From be7259b82c8987621fbc54d43a6b2e40ea7386db Mon Sep 17 00:00:00 2001 From: Hyeonggon Yoo <42.hyeyoo@gmail.com> Date: Fri, 19 Jan 2024 03:18:06 +0900 Subject: [PATCH] script: Add scripts for checking and applying coding style Make scripts/ directory and add clang-format-diff.py, check-coding-style.sh, and apply-coding-style.sh. clang-format-diff.py is the actual script that checks the coding style, while the other two are shortcut for clang-format-diff.py. You need clang-format to be installed to run it. Signed-off-by: Hyeonggon Yoo <42.hyeyoo@gmail.com> --- README.md | 3 + scripts/apply-coding-style.sh | 5 + scripts/check-coding-style.sh | 5 + scripts/clang-format-diff.py | 193 ++++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100755 scripts/apply-coding-style.sh create mode 100755 scripts/check-coding-style.sh create mode 100755 scripts/clang-format-diff.py diff --git a/README.md b/README.md index bca34dcd5..c66ca8c0d 100644 --- a/README.md +++ b/README.md @@ -18,5 +18,8 @@ $ make -j$(nproc) $ clang -fpass-plugin=`echo build/PreciseLeakSanitizer/PreciseLeakSanitizer.so` ``` +# Before committing code to the repository +To check if your code follows right coding style, run scripts/check-coding-style.sh. Or you can execute script/apply-coding-style.sh that automatically adjusts your code to follow the style. (clang-format command is required) + # Documentation - [Pseudocode for PreciseLeakSanitizer](./Documentation/pseudocode/pseudocode.md) diff --git a/scripts/apply-coding-style.sh b/scripts/apply-coding-style.sh new file mode 100755 index 000000000..adb5f020c --- /dev/null +++ b/scripts/apply-coding-style.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +REPO_ROOT_DIR=$(git rev-parse --show-toplevel) +CLANG_FORMAT_DIFF=$REPO_ROOT_DIR/scripts/clang-format-diff.py +git diff -U0 --no-color main HEAD | python $CLANG_FORMAT_DIFF -p1 -i diff --git a/scripts/check-coding-style.sh b/scripts/check-coding-style.sh new file mode 100755 index 000000000..56e27c863 --- /dev/null +++ b/scripts/check-coding-style.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +REPO_ROOT_DIR=$(git rev-parse --show-toplevel) +CLANG_FORMAT_DIFF=$REPO_ROOT_DIR/scripts/clang-format-diff.py +git diff -U0 --no-color main HEAD | python $CLANG_FORMAT_DIFF -p1 diff --git a/scripts/clang-format-diff.py b/scripts/clang-format-diff.py new file mode 100755 index 000000000..0a2c24743 --- /dev/null +++ b/scripts/clang-format-diff.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# +# ===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ===------------------------------------------------------------------------===# + +""" +This script reads input from a unified diff and reformats all the changed +lines. This is useful to reformat all the lines touched by a specific patch. +Example usage for git/svn users: + + git diff -U0 --no-color --relative HEAD^ | {clang_format_diff} -p1 -i + svn diff --diff-cmd=diff -x-U0 | {clang_format_diff} -i + +It should be noted that the filename contained in the diff is used unmodified +to determine the source file to update. Users calling this script directly +should be careful to ensure that the path in the diff is correct relative to the +current working directory. +""" +from __future__ import absolute_import, division, print_function + +import argparse +import difflib +import re +import subprocess +import sys + +if sys.version_info.major >= 3: + from io import StringIO +else: + from io import BytesIO as StringIO + + +def main(): + parser = argparse.ArgumentParser( + description=__doc__.format(clang_format_diff="%(prog)s"), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + parser.add_argument( + "-i", + action="store_true", + default=False, + help="apply edits to files instead of displaying a diff", + ) + parser.add_argument( + "-p", + metavar="NUM", + default=0, + help="strip the smallest prefix containing P slashes", + ) + parser.add_argument( + "-regex", + metavar="PATTERN", + default=None, + help="custom pattern selecting file paths to reformat " + "(case sensitive, overrides -iregex)", + ) + parser.add_argument( + "-iregex", + metavar="PATTERN", + default=r".*\.(?:cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp" + r"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|s?vh?)", + help="custom pattern selecting file paths to reformat " + "(case insensitive, overridden by -regex)", + ) + parser.add_argument( + "-sort-includes", + action="store_true", + default=False, + help="let clang-format sort include blocks", + ) + parser.add_argument( + "-v", + "--verbose", + action="store_true", + help="be more verbose, ineffective without -i", + ) + parser.add_argument( + "-style", + help="formatting style to apply (LLVM, GNU, Google, Chromium, " + "Microsoft, Mozilla, WebKit)", + ) + parser.add_argument( + "-fallback-style", + help="The name of the predefined style used as a" + "fallback in case clang-format is invoked with" + "-style=file, but can not find the .clang-format" + "file to use.", + ) + parser.add_argument( + "-binary", + default="clang-format", + help="location of binary to use for clang-format", + ) + args = parser.parse_args() + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + match = re.search(r"^\+\+\+\ (.*?/){%s}(\S*)" % args.p, line) + if match: + filename = match.group(2) + if filename is None: + continue + + if args.regex is not None: + if not re.match("^%s$" % args.regex, filename): + continue + else: + if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE): + continue + + match = re.search(r"^@@.*\+(\d+)(?:,(\d+))?", line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(2): + line_count = int(match.group(2)) + # The input is something like + # + # @@ -1, +0,0 @@ + # + # which means no lines were added. + if line_count == 0: + continue + # Also format lines range if line_count is 0 in case of deleting + # surrounding statements. + end_line = start_line + if line_count != 0: + end_line += line_count - 1 + lines_by_file.setdefault(filename, []).extend( + ["-lines", str(start_line) + ":" + str(end_line)] + ) + + # Reformat files containing changes in place. + for filename, lines in lines_by_file.items(): + if args.i and args.verbose: + print("Formatting {}".format(filename)) + command = [args.binary, filename] + if args.i: + command.append("-i") + if args.sort_includes: + command.append("-sort-includes") + command.extend(lines) + if args.style: + command.extend(["-style", args.style]) + if args.fallback_style: + command.extend(["-fallback-style", args.fallback_style]) + + try: + p = subprocess.Popen( + command, + stdout=subprocess.PIPE, + stderr=None, + stdin=subprocess.PIPE, + universal_newlines=True, + ) + except OSError as e: + # Give the user more context when clang-format isn't + # found/isn't executable, etc. + raise RuntimeError( + 'Failed to run "%s" - %s"' % (" ".join(command), e.strerror) + ) + + stdout, stderr = p.communicate() + if p.returncode != 0: + sys.exit(p.returncode) + + if not args.i: + with open(filename) as f: + code = f.readlines() + formatted_code = StringIO(stdout).readlines() + diff = difflib.unified_diff( + code, + formatted_code, + filename, + filename, + "(before formatting)", + "(after formatting)", + ) + diff_string = "".join(diff) + if len(diff_string) > 0: + sys.stdout.write(diff_string) + sys.exit(1) + + +if __name__ == "__main__": + main()