]> git.proxmox.com Git - ovs.git/blob - utilities/checkpatch.py
checkpatch: common print_line
[ovs.git] / utilities / checkpatch.py
1 #!/usr/bin/env python
2 # Copyright (c) 2016 Red Hat, Inc.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 from __future__ import print_function
16
17 import email
18 import getopt
19 import re
20 import sys
21
22 __errors = 0
23 __warnings = 0
24 print_file_name = None
25 checking_file = False
26
27
28 def print_file():
29 global print_file_name
30 if print_file_name:
31 print("In file %s" % print_file_name)
32 print_file_name = None
33
34
35 def print_error(message, lineno=None):
36 global __errors
37 print_file()
38 if lineno is not None:
39 print("E(%d): %s" % (lineno, message))
40 else:
41 print("E: %s" % (message))
42
43 __errors = __errors + 1
44
45
46 def print_warning(message, lineno=None):
47 global __warnings
48 print_file()
49 if lineno:
50 print("W(%d): %s" % (lineno, message))
51 else:
52 print("W: %s" % (message))
53
54 __warnings = __warnings + 1
55
56
57 __regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*')
58 __regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*')
59 __regex_leading_with_whitespace_at_all = re.compile(r'^\s+')
60 __regex_leading_with_spaces = re.compile(r'^ +[\S]+')
61 __regex_trailing_whitespace = re.compile(r'[^\S]+$')
62 __regex_single_line_feed = re.compile(r'^\f$')
63 __regex_for_if_missing_whitespace = re.compile(r' +(if|for|while)[\(]')
64 __regex_for_if_too_much_whitespace = re.compile(r' +(if|for|while) +[\(]')
65 __regex_for_if_parens_whitespace = \
66 re.compile(r' +(if|for|while) \( +[\s\S]+\)')
67 __regex_is_for_if_single_line_bracket = \
68 re.compile(r'^ +(if|for|while) \(.*\)')
69 __regex_ends_with_bracket = \
70 re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
71 __regex_ptr_declaration_missing_whitespace = re.compile(r'[a-zA-Z0-9]\*')
72
73 skip_leading_whitespace_check = False
74 skip_trailing_whitespace_check = False
75 skip_block_whitespace_check = False
76 skip_signoff_check = False
77
78 # Don't enforce character limit on files that include these characters in their
79 # name, as they may have legitimate reasons to have longer lines.
80 #
81 # Python isn't checked as flake8 performs these checks during build.
82 line_length_blacklist = ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch',
83 '.py']
84
85
86 def is_subtracted_line(line):
87 """Returns TRUE if the line in question has been removed."""
88 return __regex_subtracted_line.search(line) is not None
89
90
91 def is_added_line(line):
92 """Returns TRUE if the line in question is an added line.
93 """
94 global checking_file
95 return __regex_added_line.search(line) is not None or checking_file
96
97
98 def added_line(line):
99 """Returns the line formatted properly by removing diff syntax"""
100 global checking_file
101 if not checking_file:
102 return line[1:]
103 return line
104
105
106 def leading_whitespace_is_spaces(line):
107 """Returns TRUE if the leading whitespace in added lines is spaces
108 """
109 if skip_leading_whitespace_check:
110 return True
111 if (__regex_leading_with_whitespace_at_all.search(line) is not None and
112 __regex_single_line_feed.search(line) is None):
113 return __regex_leading_with_spaces.search(line) is not None
114
115 return True
116
117
118 def trailing_whitespace_or_crlf(line):
119 """Returns TRUE if the trailing characters is whitespace
120 """
121 if skip_trailing_whitespace_check:
122 return False
123 return (__regex_trailing_whitespace.search(line) is not None and
124 __regex_single_line_feed.search(line) is None)
125
126
127 def if_and_for_whitespace_checks(line):
128 """Return TRUE if there is appropriate whitespace after if, for, while
129 """
130 if skip_block_whitespace_check:
131 return True
132 if (__regex_for_if_missing_whitespace.search(line) is not None or
133 __regex_for_if_too_much_whitespace.search(line) is not None or
134 __regex_for_if_parens_whitespace.search(line)):
135 return False
136 return True
137
138
139 def if_and_for_end_with_bracket_check(line):
140 """Return TRUE if there is not a bracket at the end of an if, for, while
141 block which fits on a single line ie: 'if (foo)'"""
142
143 def balanced_parens(line):
144 """This is a rather naive counter - it won't deal with quotes"""
145 balance = 0
146 for letter in line:
147 if letter == '(':
148 balance += 1
149 elif letter == ')':
150 balance -= 1
151 return balance == 0
152
153 if __regex_is_for_if_single_line_bracket.search(line) is not None:
154 if not balanced_parens(line):
155 return True
156 if __regex_ends_with_bracket.search(line) is None:
157 return False
158 return True
159
160
161 def pointer_whitespace_check(line):
162 """Return TRUE if there is no space between a pointer name and the
163 asterisk that denotes this is a apionter type, ie: 'struct foo*'"""
164 return __regex_ptr_declaration_missing_whitespace.search(line) is not None
165
166
167 def line_length_check(line):
168 """Return TRUE if the line length is too long"""
169 if len(line) > 79:
170 return True
171 return False
172
173
174 checks = [
175 {'regex': None,
176 'match_name':
177 lambda x: not any([fmt in x for fmt in line_length_blacklist]),
178 'check': lambda x: line_length_check(x),
179 'print':
180 lambda x: print_warning("Line is greater than 79-characters long", x)}
181 ]
182
183
184 def get_file_type_checks(filename):
185 """Returns the list of checks for a file based on matching the filename
186 against regex."""
187 global checks
188 checkList = []
189 for check in checks:
190 if check['regex'] is None and check['match_name'] is None:
191 checkList.append(check)
192 if check['regex'] is not None and \
193 re.compile(check['regex']).search(filename) is not None:
194 checkList.append(check)
195 elif check['match_name'] is not None and check['match_name'](filename):
196 checkList.append(check)
197 return checkList
198
199
200 def run_checks(current_file, line, lineno):
201 """Runs the various checks for the particular line. This will take
202 filename into account."""
203 print_line = False
204 for check in get_file_type_checks(current_file):
205 if check['check'](line):
206 check['print'](lineno)
207 print_line = True
208
209 if print_line:
210 print("\n%s\n" % line)
211
212
213 def ovs_checkpatch_parse(text):
214 global print_file_name
215 lineno = 0
216 signatures = []
217 co_authors = []
218 parse = 0
219 current_file = ''
220 previous_file = ''
221 scissors = re.compile(r'^[\w]*---[\w]*')
222 hunks = re.compile('^(---|\+\+\+) (\S+)')
223 hunk_differences = re.compile(
224 r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
225 is_signature = re.compile(r'((\s*Signed-off-by: )(.*))$',
226 re.I | re.M | re.S)
227 is_co_author = re.compile(r'(\s*(Co-authored-by: )(.*))$',
228 re.I | re.M | re.S)
229
230 for line in text.decode().split('\n'):
231 if current_file != previous_file:
232 previous_file = current_file
233
234 lineno = lineno + 1
235 if len(line) <= 0:
236 continue
237
238 if checking_file:
239 parse = 2
240
241 if parse == 1:
242 match = hunks.match(line)
243 if match:
244 parse = parse + 1
245 current_file = match.group(2)
246 print_file_name = current_file
247 continue
248 elif parse == 0:
249 if scissors.match(line):
250 parse = parse + 1
251 if not skip_signoff_check:
252 if len(signatures) == 0:
253 print_error("No signatures found.")
254 elif len(signatures) != 1 + len(co_authors):
255 print_error("Too many signoffs; "
256 "are you missing Co-authored-by lines?")
257 if not set(co_authors) <= set(signatures):
258 print_error("Co-authored-by/Signed-off-by corruption")
259 elif is_signature.match(line):
260 m = is_signature.match(line)
261 signatures.append(m.group(3))
262 elif is_co_author.match(line):
263 m = is_co_author.match(line)
264 co_authors.append(m.group(3))
265 elif parse == 2:
266 newfile = hunks.match(line)
267 if newfile:
268 current_file = newfile.group(2)
269 print_file_name = current_file
270 continue
271 reset_line_number = hunk_differences.match(line)
272 if reset_line_number:
273 lineno = int(reset_line_number.group(3))
274 if lineno < 0:
275 lineno = -1 * lineno
276 lineno -= 1
277 if is_subtracted_line(line):
278 lineno -= 1
279 if not is_added_line(line):
280 continue
281
282 cmp_line = added_line(line)
283
284 # Skip files which have /datapath in them, since they are
285 # linux or windows coding standards
286 if '/datapath' in current_file:
287 continue
288 if (not current_file.endswith('.mk') and
289 not leading_whitespace_is_spaces(cmp_line)):
290 print_warning("Line has non-spaces leading whitespace",
291 lineno)
292 run_checks(current_file, cmp_line, lineno)
293 if trailing_whitespace_or_crlf(cmp_line):
294 print_warning("Line has trailing whitespace", lineno)
295 if not if_and_for_whitespace_checks(cmp_line):
296 print_error("Improper whitespace around control block",
297 lineno)
298 if not if_and_for_end_with_bracket_check(cmp_line):
299 print_error("Inappropriate bracing around statement", lineno)
300 if pointer_whitespace_check(cmp_line):
301 print_error("Inappropriate spacing in pointer declaration",
302 lineno)
303 if __errors or __warnings:
304 return -1
305 return 0
306
307
308 def usage():
309 print("Open vSwitch checkpatch.py")
310 print("Checks a patch for trivial mistakes.")
311 print("usage:")
312 print("%s [options] [patch file]" % sys.argv[0])
313 print("options:")
314 print("-h|--help\t\t\t\tThis help message")
315 print("-b|--skip-block-whitespace\t"
316 "Skips the if/while/for whitespace tests")
317 print("-f|--check-file\t\t\tCheck a file instead of a patchfile.")
318 print("-l|--skip-leading-whitespace\t"
319 "Skips the leading whitespace test")
320 print("-s|--skip-signoff-lines\t"
321 "Do not emit an error if no Signed-off-by line is present")
322 print("-t|--skip-trailing-whitespace\t"
323 "Skips the trailing whitespace test")
324
325
326 def ovs_checkpatch_file(filename):
327 global __warnings, __errors, checking_file
328 try:
329 mail = email.message_from_file(open(filename, 'r'))
330 except:
331 print_error("Unable to parse file '%s'. Is it a patch?" % filename)
332 return -1
333
334 for part in mail.walk():
335 if part.get_content_maintype() == 'multipart':
336 continue
337 result = ovs_checkpatch_parse(part.get_payload(decode=True))
338 if result < 0:
339 print("Warnings: %d, Errors: %d" % (__warnings, __errors))
340 return result
341
342
343 if __name__ == '__main__':
344 try:
345 optlist, args = getopt.getopt(sys.argv[1:], 'bhlstf',
346 ["check-file",
347 "help",
348 "skip-block-whitespace",
349 "skip-leading-whitespace",
350 "skip-signoff-lines",
351 "skip-trailing-whitespace"])
352 except:
353 print("Unknown option encountered. Please rerun with -h for help.")
354 sys.exit(-1)
355
356 for o, a in optlist:
357 if o in ("-h", "--help"):
358 usage()
359 sys.exit(0)
360 elif o in ("-b", "--skip-block-whitespace"):
361 skip_block_whitespace_check = True
362 elif o in ("-l", "--skip-leading-whitespace"):
363 skip_leading_whitespace_check = True
364 elif o in ("-s", "--skip-signoff-lines"):
365 skip_signoff_check = True
366 elif o in ("-t", "--skip-trailing-whitespace"):
367 skip_trailing_whitespace_check = True
368 elif o in ("-f", "--check-file"):
369 checking_file = True
370 else:
371 print("Unknown option '%s'" % o)
372 sys.exit(-1)
373 try:
374 filename = args[0]
375 except:
376 if sys.stdin.isatty():
377 usage()
378 sys.exit(-1)
379 sys.exit(ovs_checkpatch_parse(sys.stdin.read()))
380 sys.exit(ovs_checkpatch_file(filename))