]> git.proxmox.com Git - ovs.git/blob - utilities/checkpatch.py
checkpatch: Check for pointer whitespace.
[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 is '(':
148 balance += 1
149 elif letter is ')':
150 balance -= 1
151 return balance is 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 ovs_checkpatch_parse(text):
168 global print_file_name
169 lineno = 0
170 signatures = []
171 co_authors = []
172 parse = 0
173 current_file = ''
174 previous_file = ''
175 scissors = re.compile(r'^[\w]*---[\w]*')
176 hunks = re.compile('^(---|\+\+\+) (\S+)')
177 hunk_differences = re.compile(
178 r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
179 is_signature = re.compile(r'((\s*Signed-off-by: )(.*))$',
180 re.I | re.M | re.S)
181 is_co_author = re.compile(r'(\s*(Co-authored-by: )(.*))$',
182 re.I | re.M | re.S)
183 skip_line_length_check = False
184
185 for line in text.split('\n'):
186 if current_file != previous_file:
187 previous_file = current_file
188 if any([fmt in current_file for fmt in line_length_blacklist]):
189 skip_line_length_check = True
190 else:
191 skip_line_length_check = False
192
193 lineno = lineno + 1
194 if len(line) <= 0:
195 continue
196
197 if checking_file:
198 parse = 2
199
200 if parse == 1:
201 match = hunks.match(line)
202 if match:
203 parse = parse + 1
204 current_file = match.group(2)
205 print_file_name = current_file
206 continue
207 elif parse == 0:
208 if scissors.match(line):
209 parse = parse + 1
210 if not skip_signoff_check:
211 if len(signatures) == 0:
212 print_error("No signatures found.")
213 elif len(signatures) != 1 + len(co_authors):
214 print_error("Too many signoffs; "
215 "are you missing Co-authored-by lines?")
216 if not set(co_authors) <= set(signatures):
217 print_error("Co-authored-by/Signed-off-by corruption")
218 elif is_signature.match(line):
219 m = is_signature.match(line)
220 signatures.append(m.group(3))
221 elif is_co_author.match(line):
222 m = is_co_author.match(line)
223 co_authors.append(m.group(3))
224 elif parse == 2:
225 print_line = False
226 newfile = hunks.match(line)
227 if newfile:
228 current_file = newfile.group(2)
229 print_file_name = current_file
230 continue
231 reset_line_number = hunk_differences.match(line)
232 if reset_line_number:
233 lineno = int(reset_line_number.group(3))
234 if lineno < 0:
235 lineno = -1 * lineno
236 lineno -= 1
237 if is_subtracted_line(line):
238 lineno -= 1
239 if not is_added_line(line):
240 continue
241
242 cmp_line = added_line(line)
243
244 # Skip files which have /datapath in them, since they are
245 # linux or windows coding standards
246 if '/datapath' in current_file:
247 continue
248 if (not current_file.endswith('.mk') and
249 not leading_whitespace_is_spaces(cmp_line)):
250 print_line = True
251 print_warning("Line has non-spaces leading whitespace",
252 lineno)
253 if trailing_whitespace_or_crlf(cmp_line):
254 print_line = True
255 print_warning("Line has trailing whitespace", lineno)
256 if len(cmp_line) > 79 and not skip_line_length_check:
257 print_line = True
258 print_warning("Line is greater than 79-characters long",
259 lineno)
260 if not if_and_for_whitespace_checks(cmp_line):
261 print_line = True
262 print_error("Improper whitespace around control block",
263 lineno)
264 if not if_and_for_end_with_bracket_check(cmp_line):
265 print_line = True
266 print_error("Inappropriate bracing around statement", lineno)
267 if pointer_whitespace_check(cmp_line):
268 print_line = True
269 print_error("Inappropriate spacing in pointer declaration",
270 lineno)
271 if print_line:
272 print("\n%s\n" % line)
273 if __errors or __warnings:
274 return -1
275 return 0
276
277
278 def usage():
279 print("Open vSwitch checkpatch.py")
280 print("Checks a patch for trivial mistakes.")
281 print("usage:")
282 print("%s [options] [patch file]" % sys.argv[0])
283 print("options:")
284 print("-h|--help\t\t\t\tThis help message")
285 print("-b|--skip-block-whitespace\t"
286 "Skips the if/while/for whitespace tests")
287 print("-f|--check-file\t\t\tCheck a file instead of a patchfile.")
288 print("-l|--skip-leading-whitespace\t"
289 "Skips the leading whitespace test")
290 print("-s|--skip-signoff-lines\t"
291 "Do not emit an error if no Signed-off-by line is present")
292 print("-t|--skip-trailing-whitespace\t"
293 "Skips the trailing whitespace test")
294
295
296 def ovs_checkpatch_file(filename):
297 global __warnings, __errors, checking_file
298 try:
299 mail = email.message_from_file(open(filename, 'r'))
300 except:
301 print_error("Unable to parse file '%s'. Is it a patch?" % filename)
302 return -1
303
304 for part in mail.walk():
305 if part.get_content_maintype() == 'multipart':
306 continue
307 result = ovs_checkpatch_parse(part.get_payload(decode=True))
308 if result < 0:
309 print("Warnings: %d, Errors: %d" % (__warnings, __errors))
310 return result
311
312
313 if __name__ == '__main__':
314 try:
315 optlist, args = getopt.getopt(sys.argv[1:], 'bhlstf',
316 ["check-file",
317 "help",
318 "skip-block-whitespace",
319 "skip-leading-whitespace",
320 "skip-signoff-lines",
321 "skip-trailing-whitespace"])
322 except:
323 print("Unknown option encountered. Please rerun with -h for help.")
324 sys.exit(-1)
325
326 for o, a in optlist:
327 if o in ("-h", "--help"):
328 usage()
329 sys.exit(0)
330 elif o in ("-b", "--skip-block-whitespace"):
331 skip_block_whitespace_check = True
332 elif o in ("-l", "--skip-leading-whitespace"):
333 skip_leading_whitespace_check = True
334 elif o in ("-s", "--skip-signoff-lines"):
335 skip_signoff_check = True
336 elif o in ("-t", "--skip-trailing-whitespace"):
337 skip_trailing_whitespace_check = True
338 elif o in ("-f", "--check-file"):
339 checking_file = True
340 else:
341 print("Unknown option '%s'" % o)
342 sys.exit(-1)
343 try:
344 filename = args[0]
345 except:
346 if sys.stdin.isatty():
347 usage()
348 sys.exit(-1)
349 sys.exit(ovs_checkpatch_parse(sys.stdin.read()))
350 sys.exit(ovs_checkpatch_file(filename))