Run DCE after a LoopFlatten test to reduce spurious output [nfc]
[llvm-project.git] / clang / tools / clang-format / clang-format-diff.py
blob324ef5b7f6b35f62ff08e55c85c548e96209caae
1 #!/usr/bin/env python3
3 # ===- clang-format-diff.py - ClangFormat Diff Reformatter ----*- python -*--===#
5 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6 # See https://llvm.org/LICENSE.txt for license information.
7 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
9 # ===------------------------------------------------------------------------===#
11 """
12 This script reads input from a unified diff and reformats all the changed
13 lines. This is useful to reformat all the lines touched by a specific patch.
14 Example usage for git/svn users:
16 git diff -U0 --no-color --relative HEAD^ | clang-format-diff.py -p1 -i
17 svn diff --diff-cmd=diff -x-U0 | clang-format-diff.py -i
19 It should be noted that the filename contained in the diff is used unmodified
20 to determine the source file to update. Users calling this script directly
21 should be careful to ensure that the path in the diff is correct relative to the
22 current working directory.
23 """
24 from __future__ import absolute_import, division, print_function
26 import argparse
27 import difflib
28 import re
29 import subprocess
30 import sys
32 if sys.version_info.major >= 3:
33 from io import StringIO
34 else:
35 from io import BytesIO as StringIO
38 def main():
39 parser = argparse.ArgumentParser(
40 description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
42 parser.add_argument(
43 "-i",
44 action="store_true",
45 default=False,
46 help="apply edits to files instead of displaying a diff",
48 parser.add_argument(
49 "-p",
50 metavar="NUM",
51 default=0,
52 help="strip the smallest prefix containing P slashes",
54 parser.add_argument(
55 "-regex",
56 metavar="PATTERN",
57 default=None,
58 help="custom pattern selecting file paths to reformat "
59 "(case sensitive, overrides -iregex)",
61 parser.add_argument(
62 "-iregex",
63 metavar="PATTERN",
64 default=r".*\.(?:cpp|cc|c\+\+|cxx|cppm|ccm|cxxm|c\+\+m|c|cl|h|hh|hpp"
65 r"|hxx|m|mm|inc|js|ts|proto|protodevel|java|cs|json|s?vh?)",
66 help="custom pattern selecting file paths to reformat "
67 "(case insensitive, overridden by -regex)",
69 parser.add_argument(
70 "-sort-includes",
71 action="store_true",
72 default=False,
73 help="let clang-format sort include blocks",
75 parser.add_argument(
76 "-v",
77 "--verbose",
78 action="store_true",
79 help="be more verbose, ineffective without -i",
81 parser.add_argument(
82 "-style",
83 help="formatting style to apply (LLVM, GNU, Google, Chromium, "
84 "Microsoft, Mozilla, WebKit)",
86 parser.add_argument(
87 "-fallback-style",
88 help="The name of the predefined style used as a"
89 "fallback in case clang-format is invoked with"
90 "-style=file, but can not find the .clang-format"
91 "file to use.",
93 parser.add_argument(
94 "-binary",
95 default="clang-format",
96 help="location of binary to use for clang-format",
98 args = parser.parse_args()
100 # Extract changed lines for each file.
101 filename = None
102 lines_by_file = {}
103 for line in sys.stdin:
104 match = re.search(r"^\+\+\+\ (.*?/){%s}(\S*)" % args.p, line)
105 if match:
106 filename = match.group(2)
107 if filename is None:
108 continue
110 if args.regex is not None:
111 if not re.match("^%s$" % args.regex, filename):
112 continue
113 else:
114 if not re.match("^%s$" % args.iregex, filename, re.IGNORECASE):
115 continue
117 match = re.search(r"^@@.*\+(\d+)(?:,(\d+))?", line)
118 if match:
119 start_line = int(match.group(1))
120 line_count = 1
121 if match.group(2):
122 line_count = int(match.group(2))
123 # The input is something like
125 # @@ -1, +0,0 @@
127 # which means no lines were added.
128 if line_count == 0:
129 continue
130 # Also format lines range if line_count is 0 in case of deleting
131 # surrounding statements.
132 end_line = start_line
133 if line_count != 0:
134 end_line += line_count - 1
135 lines_by_file.setdefault(filename, []).extend(
136 ["-lines", str(start_line) + ":" + str(end_line)]
139 # Reformat files containing changes in place.
140 for filename, lines in lines_by_file.items():
141 if args.i and args.verbose:
142 print("Formatting {}".format(filename))
143 command = [args.binary, filename]
144 if args.i:
145 command.append("-i")
146 if args.sort_includes:
147 command.append("-sort-includes")
148 command.extend(lines)
149 if args.style:
150 command.extend(["-style", args.style])
151 if args.fallback_style:
152 command.extend(["-fallback-style", args.fallback_style])
154 try:
155 p = subprocess.Popen(
156 command,
157 stdout=subprocess.PIPE,
158 stderr=None,
159 stdin=subprocess.PIPE,
160 universal_newlines=True,
162 except OSError as e:
163 # Give the user more context when clang-format isn't
164 # found/isn't executable, etc.
165 raise RuntimeError(
166 'Failed to run "%s" - %s"' % (" ".join(command), e.strerror)
169 stdout, stderr = p.communicate()
170 if p.returncode != 0:
171 sys.exit(p.returncode)
173 if not args.i:
174 with open(filename) as f:
175 code = f.readlines()
176 formatted_code = StringIO(stdout).readlines()
177 diff = difflib.unified_diff(
178 code,
179 formatted_code,
180 filename,
181 filename,
182 "(before formatting)",
183 "(after formatting)",
185 diff_string = "".join(diff)
186 if len(diff_string) > 0:
187 sys.stdout.write(diff_string)
190 if __name__ == "__main__":
191 main()