2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-22 09:58:01 +00:00
ovs/utilities/checkpatch.py

339 lines
12 KiB
Python
Raw Normal View History

#!/usr/bin/env python
# Copyright (c) 2016 Red Hat, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import print_function
import email
import getopt
import re
import sys
__errors = 0
__warnings = 0
print_file_name = None
checking_file = False
def print_file():
global print_file_name
if print_file_name:
print("In file %s" % print_file_name)
print_file_name = None
def print_error(message, lineno=None):
global __errors
print_file()
if lineno is not None:
print("E(%d): %s" % (lineno, message))
else:
print("E: %s" % (message))
__errors = __errors + 1
def print_warning(message, lineno=None):
global __warnings
print_file()
if lineno:
print("W(%d): %s" % (lineno, message))
else:
print("W: %s" % (message))
__warnings = __warnings + 1
__regex_added_line = re.compile(r'^\+{1,2}[^\+][\w\W]*')
__regex_subtracted_line = re.compile(r'^\-{1,2}[^\-][\w\W]*')
__regex_leading_with_whitespace_at_all = re.compile(r'^\s+')
__regex_leading_with_spaces = re.compile(r'^ +[\S]+')
__regex_trailing_whitespace = re.compile(r'[^\S]+$')
__regex_single_line_feed = re.compile(r'^\f$')
__regex_for_if_missing_whitespace = re.compile(r' +(if|for|while)[\(]')
__regex_for_if_too_much_whitespace = re.compile(r' +(if|for|while) +[\(]')
__regex_for_if_parens_whitespace = \
re.compile(r' +(if|for|while) \( +[\s\S]+\)')
__regex_is_for_if_single_line_bracket = \
re.compile(r'^ +(if|for|while) \(.*\)')
__regex_ends_with_bracket = \
re.compile(r'[^\s]\) {(\s+/\*[\s\Sa-zA-Z0-9\.,\?\*/+-]*)?$')
skip_leading_whitespace_check = False
skip_trailing_whitespace_check = False
skip_block_whitespace_check = False
skip_signoff_check = False
# Don't enforce character limit on files that include these characters in their
# name, as they may have legitimate reasons to have longer lines.
#
# Python isn't checked as flake8 performs these checks during build.
line_length_blacklist = ['.am', '.at', 'etc', '.in', '.m4', '.mk', '.patch',
'.py']
def is_subtracted_line(line):
"""Returns TRUE if the line in question has been removed."""
return __regex_subtracted_line.search(line) is not None
def is_added_line(line):
"""Returns TRUE if the line in question is an added line.
"""
global checking_file
return __regex_added_line.search(line) is not None or checking_file
def added_line(line):
"""Returns the line formatted properly by removing diff syntax"""
global checking_file
if not checking_file:
return line[1:]
return line
def leading_whitespace_is_spaces(line):
"""Returns TRUE if the leading whitespace in added lines is spaces
"""
if skip_leading_whitespace_check:
return True
if (__regex_leading_with_whitespace_at_all.search(line) is not None and
__regex_single_line_feed.search(line) is None):
return __regex_leading_with_spaces.search(line) is not None
return True
def trailing_whitespace_or_crlf(line):
"""Returns TRUE if the trailing characters is whitespace
"""
if skip_trailing_whitespace_check:
return False
return (__regex_trailing_whitespace.search(line) is not None and
__regex_single_line_feed.search(line) is None)
def if_and_for_whitespace_checks(line):
"""Return TRUE if there is appropriate whitespace after if, for, while
"""
if skip_block_whitespace_check:
return True
if (__regex_for_if_missing_whitespace.search(line) is not None or
__regex_for_if_too_much_whitespace.search(line) is not None or
__regex_for_if_parens_whitespace.search(line)):
return False
return True
def if_and_for_end_with_bracket_check(line):
"""Return TRUE if there is not a bracket at the end of an if, for, while
block which fits on a single line ie: 'if (foo)'"""
def balanced_parens(line):
"""This is a rather naive counter - it won't deal with quotes"""
balance = 0
for letter in line:
if letter is '(':
balance += 1
elif letter is ')':
balance -= 1
return balance is 0
if __regex_is_for_if_single_line_bracket.search(line) is not None:
if not balanced_parens(line):
return True
if __regex_ends_with_bracket.search(line) is None:
return False
return True
def ovs_checkpatch_parse(text):
global print_file_name
lineno = 0
signatures = []
co_authors = []
parse = 0
current_file = ''
previous_file = ''
scissors = re.compile(r'^[\w]*---[\w]*')
hunks = re.compile('^(---|\+\+\+) (\S+)')
hunk_differences = re.compile(
r'^@@ ([0-9-+]+),([0-9-+]+) ([0-9-+]+),([0-9-+]+) @@')
is_signature = re.compile(r'((\s*Signed-off-by: )(.*))$',
re.I | re.M | re.S)
is_co_author = re.compile(r'(\s*(Co-authored-by: )(.*))$',
re.I | re.M | re.S)
skip_line_length_check = False
for line in text.split('\n'):
if current_file != previous_file:
previous_file = current_file
if any([fmt in current_file for fmt in line_length_blacklist]):
skip_line_length_check = True
else:
skip_line_length_check = False
lineno = lineno + 1
if len(line) <= 0:
continue
if checking_file:
parse = 2
if parse == 1:
match = hunks.match(line)
if match:
parse = parse + 1
current_file = match.group(2)
print_file_name = current_file
continue
elif parse == 0:
if scissors.match(line):
parse = parse + 1
if not skip_signoff_check:
if len(signatures) == 0:
print_error("No signatures found.")
elif len(signatures) != 1 + len(co_authors):
print_error("Too many signoffs; "
"are you missing Co-authored-by lines?")
if not set(co_authors) <= set(signatures):
print_error("Co-authored-by/Signed-off-by corruption")
elif is_signature.match(line):
m = is_signature.match(line)
signatures.append(m.group(3))
elif is_co_author.match(line):
m = is_co_author.match(line)
co_authors.append(m.group(3))
elif parse == 2:
print_line = False
newfile = hunks.match(line)
if newfile:
current_file = newfile.group(2)
print_file_name = current_file
continue
reset_line_number = hunk_differences.match(line)
if reset_line_number:
lineno = int(reset_line_number.group(3))
if lineno < 0:
lineno = -1 * lineno
lineno -= 1
if is_subtracted_line(line):
lineno -= 1
if not is_added_line(line):
continue
cmp_line = added_line(line)
# Skip files which have /datapath in them, since they are
# linux or windows coding standards
if '/datapath' in current_file:
continue
if (not current_file.endswith('.mk') and
not leading_whitespace_is_spaces(cmp_line)):
print_line = True
print_warning("Line has non-spaces leading whitespace",
lineno)
if trailing_whitespace_or_crlf(cmp_line):
print_line = True
print_warning("Line has trailing whitespace", lineno)
if len(cmp_line) > 79 and not skip_line_length_check:
print_line = True
print_warning("Line is greater than 79-characters long",
lineno)
if not if_and_for_whitespace_checks(cmp_line):
print_line = True
print_error("Improper whitespace around control block",
lineno)
if not if_and_for_end_with_bracket_check(cmp_line):
print_line = True
print_error("Inappropriate bracing around statement", lineno)
if print_line:
print("\n%s\n" % line)
if __errors or __warnings:
return -1
return 0
def usage():
print("Open vSwitch checkpatch.py")
print("Checks a patch for trivial mistakes.")
print("usage:")
print("%s [options] [patch file]" % sys.argv[0])
print("options:")
print("-h|--help\t\t\t\tThis help message")
print("-b|--skip-block-whitespace\t"
"Skips the if/while/for whitespace tests")
print("-f|--check-file\t\t\tCheck a file instead of a patchfile.")
print("-l|--skip-leading-whitespace\t"
"Skips the leading whitespace test")
print("-s|--skip-signoff-lines\t"
"Do not emit an error if no Signed-off-by line is present")
print("-t|--skip-trailing-whitespace\t"
"Skips the trailing whitespace test")
def ovs_checkpatch_file(filename):
global __warnings, __errors, checking_file
try:
mail = email.message_from_file(open(filename, 'r'))
except:
print_error("Unable to parse file '%s'. Is it a patch?" % filename)
return -1
for part in mail.walk():
if part.get_content_maintype() == 'multipart':
continue
result = ovs_checkpatch_parse(part.get_payload(decode=True))
if result < 0:
print("Warnings: %d, Errors: %d" % (__warnings, __errors))
return result
if __name__ == '__main__':
try:
optlist, args = getopt.getopt(sys.argv[1:], 'bhlstf',
["check-file",
"help",
"skip-block-whitespace",
"skip-leading-whitespace",
"skip-signoff-lines",
"skip-trailing-whitespace"])
except:
print("Unknown option encountered. Please rerun with -h for help.")
sys.exit(-1)
for o, a in optlist:
if o in ("-h", "--help"):
usage()
sys.exit(0)
elif o in ("-b", "--skip-block-whitespace"):
skip_block_whitespace_check = True
elif o in ("-l", "--skip-leading-whitespace"):
skip_leading_whitespace_check = True
elif o in ("-s", "--skip-signoff-lines"):
skip_signoff_check = True
elif o in ("-t", "--skip-trailing-whitespace"):
skip_trailing_whitespace_check = True
elif o in ("-f", "--check-file"):
checking_file = True
else:
print("Unknown option '%s'" % o)
sys.exit(-1)
try:
filename = args[0]
except:
if sys.stdin.isatty():
usage()
sys.exit(-1)
sys.exit(ovs_checkpatch_parse(sys.stdin.read()))
sys.exit(ovs_checkpatch_file(filename))