From ec93f4680936a602096a1e12f8f963e7c399c3e4 Mon Sep 17 00:00:00 2001 From: Timo Lotterbach Date: Thu, 28 Feb 2013 04:49:40 -0800 Subject: [PATCH] Added python scripts to check code style - check header guards - check license header in source files - check only spaces, no tabs - check line length - check indentation - check single statement on same line - check single definition on line - check curly braces on separate line - check consistent whitespace usage - check for unneccessary empty lines Each script takes the list of files to check as arguments. All scripts print a help message if no argument is provided. Scripts will print all warnings in a gcc-style format, so default error and warning parsers will detect them. Signed-off-by: Timo Lotterbach --- .gitignore | 2 + scripts/__init__.py | 0 scripts/check_all_styles.py | 94 ++++++++++++++ scripts/check_class_name.py | 74 +++++++++++ scripts/check_consistent_code_style.py | 185 ++++++++++++++++++++++++++++ scripts/check_curly_braces_alone_on_line.py | 114 +++++++++++++++++ scripts/check_empty_lines.py | 87 +++++++++++++ scripts/check_header_guards.py | 117 ++++++++++++++++++ scripts/check_indentation.py | 164 ++++++++++++++++++++++++ scripts/check_license.py | 128 +++++++++++++++++++ scripts/check_long_lines.py | 52 ++++++++ scripts/check_single_definition_on_line.py | 51 ++++++++ scripts/check_single_statement_on_line.py | 54 ++++++++ scripts/check_style.sh | 42 +++++++ scripts/check_tabbing_and_spacing.py | 73 +++++++++++ scripts/common_modules/__init__.py | 0 scripts/common_modules/common.py | 140 +++++++++++++++++++++ scripts/common_modules/config.py | 25 ++++ 18 files changed, 1402 insertions(+) create mode 100755 scripts/__init__.py create mode 100755 scripts/check_all_styles.py create mode 100755 scripts/check_class_name.py create mode 100755 scripts/check_consistent_code_style.py create mode 100755 scripts/check_curly_braces_alone_on_line.py create mode 100755 scripts/check_empty_lines.py create mode 100755 scripts/check_header_guards.py create mode 100755 scripts/check_indentation.py create mode 100755 scripts/check_license.py create mode 100755 scripts/check_long_lines.py create mode 100755 scripts/check_single_definition_on_line.py create mode 100755 scripts/check_single_statement_on_line.py create mode 100755 scripts/check_style.sh create mode 100755 scripts/check_tabbing_and_spacing.py create mode 100755 scripts/common_modules/__init__.py create mode 100755 scripts/common_modules/common.py create mode 100755 scripts/common_modules/config.py diff --git a/.gitignore b/.gitignore index f0e0e95..e2fe8ec 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ config.h config.h.1stpass git.kdev4 .kdev4 +*.pyc +*.pyo diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/scripts/check_all_styles.py b/scripts/check_all_styles.py new file mode 100755 index 0000000..c8cb972 --- /dev/null +++ b/scripts/check_all_styles.py @@ -0,0 +1,94 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +""" + Runs all enabled style checking +""" + +import sys, re, string +from common_modules.common import * + +from check_license import check_license_in_file +from check_header_guards import check_header_guards +from check_consistent_code_style import check_consistent_code_style +from check_indentation import check_indentation +from check_curly_braces_alone_on_line import check_curly_braces_alone_on_line +from check_long_lines import check_long_lines +from check_single_definition_on_line import check_single_definition_on_line +from check_single_statement_on_line import check_single_statement_on_line +from check_tabbing_and_spacing import check_tabbing_and_spacing +from check_empty_lines import check_empty_lines +from check_class_name import check_class_name + +####################################################################################################################### +#CONFIGURATION +####################################################################################################################### +CHECK_LICENSE = "ON" +CHECK_HEADER_GUARDS = "ON" +CHECK_TABBING_AND_SPACING = "ON" +CHECK_INDENTATION = "ON" +CHECK_CONSISTENT_CODE_STYLE = "ON" +CHECK_CURLY_BRACES_ALONE_ON_LINE = "ON" +CHECK_LONG_LINES = "ON" +CHECK_SINGLE_STATEMENT_ON_LINE = "ON" +CHECK_SINGLE_DEFINITION_ON_LINE = "ON" +CHECK_EMPTY_LINES = "ON" +CHECK_CLASS_NAME = "ON" +####################################################################################################################### + + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\tTakes a list of files and directories as input and runs all enabled style-checking on all files. + +\tTo enable/disable style-checking you can toggle the "ON"/"OFF" value of the configuration variables inside the file. +""" + exit(0) + + for t in targets: + if t[-2:] == ".h" or t[-4:] == ".cpp" or t[-2:] == ".c": + file_contents, clean_file_contents, file_lines, clean_file_lines = read_file(t) + + if CHECK_LICENSE == "ON": + check_license_in_file(t, file_contents) + if CHECK_HEADER_GUARDS == "ON" and t[-2:] == ".h": + check_header_guards(t, file_contents) + if CHECK_TABBING_AND_SPACING == "ON": + check_tabbing_and_spacing(t, file_lines) + if CHECK_INDENTATION == "ON": + check_indentation(t, file_contents, clean_file_contents, file_lines, clean_file_lines) + if CHECK_CONSISTENT_CODE_STYLE == "ON": + check_consistent_code_style(t, file_contents, clean_file_contents, file_lines, clean_file_lines) + if CHECK_CURLY_BRACES_ALONE_ON_LINE == "ON": + check_curly_braces_alone_on_line(t, file_contents, clean_file_contents, file_lines, clean_file_lines) + if CHECK_LONG_LINES == "ON": + check_long_lines(t, file_lines) + if CHECK_SINGLE_STATEMENT_ON_LINE == "ON": + check_single_statement_on_line(t, file_contents, clean_file_contents, file_lines, clean_file_lines) + if CHECK_SINGLE_DEFINITION_ON_LINE == "ON": + check_single_definition_on_line(t, file_contents, clean_file_contents, file_lines, clean_file_lines) + if CHECK_EMPTY_LINES == "ON": + check_empty_lines(t, file_contents, file_lines) + if CHECK_CLASS_NAME == "ON" and t[-2:] == ".h": + check_class_name(t, clean_file_contents) diff --git a/scripts/check_class_name.py b/scripts/check_class_name.py new file mode 100755 index 0000000..ef08ca6 --- /dev/null +++ b/scripts/check_class_name.py @@ -0,0 +1,74 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * + + +def check_class_name(filename, clean_file_contents): + #a class is followed by classname, and possible an inherticance + class_def_re = re.compile(r'(? 0: + good_name = good_name[good_name.rfind("/") + 1:] + + #remove .h extension + good_name = good_name[:good_name.find(".h")] + + class_found = False + + for class_match in re.finditer(class_def_re, clean_file_contents): + line_number = clean_file_contents[:class_match.end()].count("\n") + + if class_found: + log_warning(filename, line_number, "several class difinitions in file") + else: + class_found = True + + class_name_match = class_name_re.search(clean_file_contents, class_match.start()) + class_name = clean_file_contents[class_name_match.start(): class_name_match.end()] if class_name_match != None else "" + class_name = class_name.strip(" \n\r\f\t") + + if class_name != good_name: + log_warning(filename, line_number, "class name has to be identical to filename") + + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories. + +\tGives warnings if a file contains more than one class definition or if class name is not identical to file name +""" + exit(0) + + for t in targets: + if t[-2:] == ".h": + _, clean_file_contents, _, _= read_file(t) + check_class_name(t, clean_file_contents) diff --git a/scripts/check_consistent_code_style.py b/scripts/check_consistent_code_style.py new file mode 100755 index 0000000..a3aae73 --- /dev/null +++ b/scripts/check_consistent_code_style.py @@ -0,0 +1,185 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * + + + +def check_binary_operators(filename, file_contents, file_lines, clean_file_contents, clean_file_lines): + #normal binary operators must all have 0 or 1 space between operator and operands + binary_ops = ['=', r'\+', '%', r'\^', '==', '!=', '>=', '<=', '&&', r'\|\|', r'\|', '<<', '>>', r'\+=', '-=', r'\*=', '/=', '%=', '&=', r'\|=', r'\^=', '<<=', '>>=', ':', r'\?'] + #operators that are not handled + #r'\*', '&', '>', '<', '/', '-' + #access binary operators must have no space between operator and operands + binary_ops_re_text = '(' + "|".join(binary_ops) + ')' + + #re for NO space between operator and operands + nospace_re = re.compile(r"\w{0}\w".format(binary_ops_re_text)) + #re for ONE space between operator and operands + space_re = re.compile(r"\w\s{0}\s\w".format(binary_ops_re_text)) + #unbalanced is a match for binary op (\w\s*{0}\s*\w) that is neither no space (?!(\w{0}\w)) nor single space (?!(\w\s{0}\s\w)) + #this works because (?!...) does not consume strings + unbalanced_spaces_re = re.compile(r"(?!(\w{0}\w))(?!(\w\s{0}\s\w))(\w\s*{0}\s*\w)".format(binary_ops_re_text)) + + #access_ops = ['.', '->', '::'] + access_ops_re_text = r'(\.|->|::)' + bad_access_ops_re = re.compile(r"(\w\s+{0}\s*\w)|(\w\s*{0}\s+\w)".format(access_ops_re_text)) + + #check if there is general inconsistance in the file + nospace_match = re.search(nospace_re, clean_file_contents) + space_match = re.search(space_re, clean_file_contents) + + if nospace_match != None and space_match != None: + line1 = clean_file_contents[:nospace_match.start()].count("\n") + 1 + line2 = clean_file_contents[:space_match.start()].count("\n") + 1 + + log_warning(filename, line1, "inconsistent spacing between operands and binary operator. All operands in a file must have same space (0 or 1 spaces) [Reported once per file]", "lines :{0} and {1}".format(line1, line2)) + + for i in range(len(file_lines)): + line = clean_file_lines[i] + + if re.search(unbalanced_spaces_re, line) != None: + log_warning(filename, i + 1, "incorrect or unbalanced spaces between operands and binary operator", file_lines[i].strip(" ")) + + if re.search(bad_access_ops_re, line) != None: + log_warning(filename, i + 1, "unneeded space(s) around access/resolution operator", file_lines[i].strip(" ")) + +def check_semicolor(filename, file_contents, file_lines, clean_file_contents, clean_file_lines): + #check if there is extra space before semicolon, or missing space after semicolon (if there is content after it like in for loop) + space_before_re = re.compile(r"\s;"); + nospace_after_re = re.compile(r";((?! ).)"); + + for i in range(len(file_lines)): + line = clean_file_lines[i] + + if re.search(space_before_re, line) != None: + log_warning(filename, i + 1, "unneeded space(s) before semicolon", file_lines[i].strip(" ")) + + if re.search(nospace_after_re, line) != None: + log_warning(filename, i + 1, "missing space after semicolon", file_lines[i].strip(" ")) + +def check_braces_spaces(filename, file_contents, file_lines, clean_file_contents, clean_file_lines): + #check if there are spaces between between right/left brace and contents + bad_left_brace_re = re.compile(r"\(\s") + bad_right_brace_re = re.compile(r"\s\)") + + for i in range(len(file_lines)): + line = clean_file_lines[i] + + if re.search(bad_left_brace_re, line) != None: + log_warning(filename, i + 1, "unneeded space(s) after left brace", file_lines[i].strip(" ")) + + if re.search(bad_right_brace_re, line) != None: + log_warning(filename, i + 1, "unneeded space(s) before right brace", file_lines[i].strip(" ")) + +def check_commas(filename, file_contents, file_lines, clean_file_contents, clean_file_lines): + nospace_after_re = re.compile(r",((?!\s).)") + too_many_spaces_after_re = re.compile(r",\s(\s+)") + space_before_re = re.compile(r"\S(\s+),") + + for i in range(len(file_lines)): + line = clean_file_lines[i] + + if re.search(nospace_after_re, line) != None: + log_warning(filename, i + 1, "missing space after comma", file_lines[i].strip(" ")) + + if re.search(too_many_spaces_after_re, line) != None: + log_warning(filename, i + 1, "unneeded space(s) after comma", file_lines[i].strip(" ")) + + if re.search(space_before_re, line) != None: + log_warning(filename, i + 1, "unneeded space(s) before comma", file_lines[i].strip(" ")) + +def check_function_call_or_definition(filename, file_contents, file_lines, clean_file_contents, clean_file_lines): + #in bad function definition or call there exists a space (or more) between function name and left brace (the last \s+ in the expression) + #the function name must not be a if, for, switch or while + reserved_words = ['if', 'for', 'while', 'switch', 'return', 'delete'] + reserved_words_re_text = "(? 0: + for j in range(i - 1, 0 , -1): + if len(clean_file_lines[j].strip(" \t\n\f\r")): + return clean_file_lines[j] + return None + + former_line = get_former_line() + + if line.count("{"): + typedecl = re.search(typedecl_re, former_line) + typedecl_stack.append(typedecl) + + if re.match(permissible_re, line) == None : + log_warning(filename, i + 1, "other code on same line with curly brace", file_lines[i].strip(" ")) + + if line.count("}"): + typedecl = typedecl_stack.pop() + if typedecl: + if re.match(r"(\s*)\}", line) == None: + log_warning(filename, i + 1, "other code on same line with curly brace", file_lines[i].strip(" ")) + else: + if re.match(r"(\s*)\}(\s*)((while((?!;).)*)?)(;?)(\s*)(\\?)(\s*)$", line) == None: + log_warning(filename, i + 1, "other code on same line with curly brace", file_lines[i].strip(" ")) + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories + +\tGives warnings if unnecessary code exists on the same line with curly braces. +""" + exit(0) + + for t in targets: + if t[-2:] == ".h" or t[-4:] == ".cpp" or t[-2] == ".c": + file_contents, clean_file_contents, file_lines, clean_file_lines = read_file(t) + check_curly_braces_alone_on_line(t, file_contents, clean_file_contents, file_lines, clean_file_lines) diff --git a/scripts/check_empty_lines.py b/scripts/check_empty_lines.py new file mode 100755 index 0000000..e5f51f2 --- /dev/null +++ b/scripts/check_empty_lines.py @@ -0,0 +1,87 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * +from common_modules.config import G_MAX_INDENTATION_THRESHOLD + + +def check_empty_lines(filename, file_contents, file_lines): + #keep track of the current indentation + #need only to know if level of indent is zero or not + line_indent_zero = True + + def is_line_empty(l): + return len(l.strip(" \t\n\r\f")) == 0 + + #check all lines except last line + for i in range(len(file_lines) - 1): + line = file_lines[i] + former_line = file_lines[i - 1] if i > 0 else "" + next_line = file_lines[i + 1] if i < len(file_lines) - 1 else "" + + def is_next_indent_zero(): + for j in range(i + 1, len(file_lines)): + if not is_line_empty(file_lines[j]): + return re.match(r'(\s+)(\S+)', file_lines[j]) == None + return True + + next_indent_zero = is_next_indent_zero() + + #if empty line + if is_line_empty(line): + #check if line is not needed + #if the line before it was empty (two consecutive white space lines) + if is_line_empty(former_line) and not line_indent_zero and not next_indent_zero: + log_warning(filename, i + 1, "unneeded empty line (two or more consecutive empty lines)") + #if the line before it was a block beginning + elif re.search(r'\{(\s*)$', former_line): + log_warning(filename, i + 1, "unneeded empty line (block begins with empty line)") + #if the line after it is a block end + elif re.match(r'(\s*)\}', next_line): + log_warning(filename, i + 1, "unneeded empty line (block ends with empty line)") + #if non-empty line + else: + #if the line contains one or more white-space characters followed by + #one or more non white-space characters + line_indent_zero = re.match(r'(\s+)(\S+)', line) == None + + if re.search(r'\n$', file_contents) == None: + log_warning(filename, len(file_lines), "file does not end with new line") + + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories + +\tGives warnings if there are unneeded empty lines +\tIt also gives a warning if there is NO empty line at end of file (because this leads to warnings on some compilers) +""" + exit(0) + + for t in targets: + if t[-2:] == ".h" or t[-4:] == ".cpp" or t[-2] == ".c": + file_contents, _, file_lines, _ = read_file(t) + check_empty_lines(t, file_contents, file_lines) diff --git a/scripts/check_header_guards.py b/scripts/check_header_guards.py new file mode 100755 index 0000000..0dbb7d5 --- /dev/null +++ b/scripts/check_header_guards.py @@ -0,0 +1,117 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * + +def check_header_guards(filename, file_contents): + #remove strings + string_re = re.compile(r'"((?!((? 0: + good_name = good_name[good_name.rfind("/") + 1:] + + #remove .h extension + good_name = good_name[:good_name.find(".h")] + + #good name is all uppercase + good_name = good_name.upper() + + #a good name can start with underscores, and can end with underscores, can optionally have a capital H character + good_name_re = re.compile("(_*){0}(_+H?_*)?".format(good_name)) + + return re.match(good_name_re, name) != None + + #find the ifndef guard + ifndef_re = re.compile(r"(\s*)#ifndef .*", re.MULTILINE) + ifndef_match = re.match(ifndef_re, file_contents) + if ifndef_match == None: + log_warning(filename, 1, "header file does not contain a valid #ifndef guard at the beginning") + return None + + ifndef_match_text = file_contents[:ifndef_match.end()] + ifndef_match_text = ifndef_match_text.strip(" \n\r\f\t") + splitted_match_text = ifndef_match_text.split(" ") + ifndef_guard_name = splitted_match_text[1] if len(splitted_match_text) > 1 else "" + + if not check_good_name(ifndef_guard_name): + log_warning(filename, 1, "#ifndef {0} guard name does not follow convention, name has to contain file name in uppercase".format(ifndef_guard_name)) + return None + + file_contents = file_contents[ifndef_match.end():] + + #find the define guard + define_re = re.compile(r"(\s*)#define .*") + define_match = re.match(define_re, file_contents) + if define_match == None: + log_warning(filename, 1, "header file does not contain a #define guard at the beginning that matches #ifndef {0}".format(ifndef_guard_name)) + return None + + define_match_text = file_contents[:define_match.end()] + define_match_text = define_match_text.strip(" \n\r\f\t") + splitted_match_text = define_match_text.split(" ") + define_guard_name = splitted_match_text[1] if len(splitted_match_text) > 1 else "" + + if define_guard_name != ifndef_guard_name: + log_warning(filename, 1, "header guard names do not match :(#ifndef {0}) and (#define {1})".format(ifndef_guard_name, define_guard_name)) + + #find the endif guard + endif_re = re.compile(r"#endif.*(\s*)$") + endif_match = re.search(endif_re, file_contents) + if endif_match == None: + log_warning(filename, 1, "header file does not end with #endif at last line") + return None + + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories + +\tGives warnings if header guards does not exist in header files. Header files must have a #ifndef and #define guards directly after the license. +\tThe names of the header guards must match together and match with the name of the file in uppercase letters. +""" + exit(0) + + for t in targets: + if t[-2:] == ".h": + file_contents, _, _, _ = read_file(t) + check_header_guards(t, file_contents) diff --git a/scripts/check_indentation.py b/scripts/check_indentation.py new file mode 100755 index 0000000..4ae6c83 --- /dev/null +++ b/scripts/check_indentation.py @@ -0,0 +1,164 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * +from common_modules.config import G_MAX_INDENTATION_THRESHOLD + +def check_indentation(filename, file_contents, clean_file_contents, file_lines, clean_file_lines): + #It is important to keep track of the changes made to the indent level because + #curly braces of namespaces do not affect indentation, while curly braces of + #other statements like if, for and while increase/decease indentation + indent_levels_stack = [0] + indent_level = 0 + parentheses_count = 0 + + def is_incomplete_statement(line, former_line): + incomplete_statement = False + #include statements not preceded by a semicolon in previous line + incomplete_statement |= former_line != None and re.search(re.compile(r";(\s*)$"), former_line) == None + #exclude lines containing block beginnings and ends + incomplete_statement &= re.search(re.compile(r"(\{|\})(\s*)$"), line) == None + #exclude statements preceded by block beginnings and ends + incomplete_statement &= former_line != None and re.search(re.compile(r"(\{|\})(\s*)$"), former_line) == None + #exclude preprocessor directives (they dunt have to end with a ; for example) + incomplete_statement &= re.match(re.compile(r"(\s*)#"), line) == None + #exclude statements preceded by preprocessor directives that do not end with a slash \ + incomplete_statement &= former_line != None and re.match(re.compile(r"(\s*)#((?!\/).)*\/$"), former_line) == None + + + #filter out the case where a line is preceeded by a label statement + fl_label_statement = False + if former_line != None: + #case statements -> case id : expr that does not begin with a : (to avoid matching with :: operator) + fl_label_statement = re.match(r'(\s*)case(\s*)((?!:).)*(\s*):(?!:)(\s*)$', former_line) != None + #label statements -> label : expr that does not begin with a : (to avoid matching with :: operator) + fl_label_statement |= re.match(r'(\s*)(\w+)(\s*):(?!:)(\s*)$', former_line) != None + + incomplete_statement &= not fl_label_statement + + #include statements that are contained inside parentheses + incomplete_statement |= parentheses_count > 0 + + return incomplete_statement + + def is_label_statement(line): + label_statement = False + if line != None: + #case statements -> case id : expr that does not begin with a : (to avoid matching with :: operator) + label_statement = re.match(r'(\s*)case(\s*)((?!:).)*(\s*):(?!:)', line) != None + #label statements -> label : expr that does not begin with a : (to avoid matching with :: operator) + label_statement |= re.match(r'(\s*)(\w+)(\s*):(?!:)', line) != None + return label_statement + + def is_preprocessor_directive(line): + return re.match(r"(\s*)#", line) != None + + def is_macro_body(former_line): + return former_line != None and re.search(r"\\$", former_line) != None + + for i in range(len(clean_file_lines)): + line = clean_file_lines[i] + line_indent = 0 + + #a loop is to account for cases where there are several braces on same line + for _ in range(0, line.count("}") - line.count("{")): + indent_level = indent_levels_stack.pop() + + #check correct line indentation + found_match = re.search("(?! )", line) + if found_match: + line_indent = found_match.start() / 4.0 + + def get_former_line(): + if i > 0: + for j in range(i - 1, 0 , -1): + if len(clean_file_lines[j].strip(" \t\n\f\r")): + return clean_file_lines[j] + return "" + + former_line = get_former_line() + + incomplete_statement = is_incomplete_statement(line, former_line) + label_statement = is_label_statement(line) + fl_label_statement = is_label_statement(former_line) + preprocessor_directive = is_preprocessor_directive(line) + macro_body = is_macro_body(former_line) + + if len(line.strip(" \t\n\r")) > 0: + if macro_body: + #do not check anything, just ignore line + None + elif preprocessor_directive: + #warning if not at 0 level indentation + if line_indent != 0: + log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" ")) + elif label_statement: + #warning if label statement has one level less indentation than the normal indentation level + if line_indent != indent_level - 1: + log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" ")) + elif (not incomplete_statement) and line_indent != indent_level: + #warning if a complete statement (NON incomplete statement) is not at the normal level of indentation + log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" ")) + elif incomplete_statement: + if fl_label_statement and line_indent != indent_level: + #warning if preceeded by a label statement but not exactly at normal indentation + log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" ")) + elif (not fl_label_statement ) and line_indent < indent_level: + #warning if incomplete statement and indentation is less than normal indentation + log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" ")) + + #a loop is to account for cases where there are several braces on same line + for _ in range(0, line.count("{") - line.count("}")): + indent_levels_stack.append(indent_level) + #if there is no namesapce or extern declaration before curly brace: increase indentation + #if re.match(r"(\s*)((namespace(\s+\w+)?(\s*)\{)|(extern(((?!\{).)*)\{))", line) == None and re.match(r"(\s*)(namespace|extern)", former_line) == None: + #if re.match(r"(\s*)(namespace)", line) == None and re.match(r"(\s*)namespace", former_line) == None: + namespace_or_extern_re_text = r'\s*(namespace|(extern((\s*\S*\s*)?)(\s*)($|\{)))' + namespace_or_extern_re = re.compile(namespace_or_extern_re_text) + + if re.match(namespace_or_extern_re, line) == None and re.match(namespace_or_extern_re, former_line) == None: + indent_level += 1 + + #warning if indentation level is too much + if indent_level == G_MAX_INDENTATION_THRESHOLD + 1: + log_warning(filename, i + 1, "code block has too much indentation [maximum level of indentation is {0}]".format(G_MAX_INDENTATION_THRESHOLD),) + + parentheses_count += line.count("(") - line.count(")") + + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories + +\tGives warnings if lines (except multi-line comments and macros) do not have correct indentation. +\tIt also checks if code blocks have too much indentation (too deep). Code should have a maximum of {0} indentation levels. +""".format(G_MAX_INDENTATION_THRESHOLD) + exit(0) + + for t in targets: + if t[-2:] == ".h" or t[-4:] == ".cpp" or t[-2] == ".c": + file_contents, clean_file_contents, file_lines, clean_file_lines = read_file(t) + check_indentation(t, file_contents, clean_file_contents, file_lines, clean_file_lines) diff --git a/scripts/check_license.py b/scripts/check_license.py new file mode 100755 index 0000000..340c31b --- /dev/null +++ b/scripts/check_license.py @@ -0,0 +1,128 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * + +BMW_LICENSE_TEMPLATE = '''/*************************************************************************** +* +* +* 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. +* +****************************************************************************/''' + + +def clean_comment_chars(s): + """ + Removes comment characters from a string + """ + s = string.replace(s, "/", "") + s = string.replace(s, "*", "") + s = string.replace(s, "#", "") + return s + +def make_license_re(license_text): + """ + Makes a regular expression for every line in the license, this would match the license + text tolerating extra spaces + """ + license_lines = license_text.split("\n") + license_lines_re = {} + for i in range(len(license_lines)): + license_line = license_lines[i] + re_text = clean_comment_chars(license_line) + re_text = re_text.strip(" \n\t\r\f") + re_text = string.replace(re_text, "(", "\(") + re_text = string.replace(re_text, ")", "\)") + re_text = string.replace(re_text, " ", "(\s+)") + re_text = string.replace(re_text, "\n", "(\s*)") + re_text = string.replace(re_text, "\t", "(\s+)") + re_text = string.replace(re_text, ".", "\.") + + re_text = string.replace(re_text, "[YYYY]", r"(\d{4})(((-(\d{4}))|(((\s*),(\s*)\d{4})+))?)") + + if len(re_text) > 0: + re_text = "(\s*)" + re_text + "(\s*)" + current_text = "" + while current_text != re_text: + current_text = re_text + re_text = string.replace(re_text, "(\s*)(\s*)", "(\s*)") + re_text = string.replace(re_text, "(\s+)(\s+)", "(\s+)") + re_text = string.replace(re_text, "(\s*)(\s+)", "(\s+)") + + + license_lines_re[i] = re_text + + return license_lines_re + + +def check_license_in_file(filename, file_contents): + clean_file_contents = clean_comment_chars(file_contents) + license_text = BMW_LICENSE_TEMPLATE + + license_lines = license_text.split("\n") + + license_re = make_license_re(license_text) + + #search for the first line of the license in the target file + line_re = re.compile(license_re.values()[0]) + found_match = line_re.search(clean_file_contents) + + if found_match: + clean_file_contents = clean_file_contents[found_match.start():] + + #check license as it is + for (line_num, line_re_text) in license_re.items(): + line_re = re.compile(line_re_text) + found_match = line_re.match(clean_file_contents) + + if found_match: + clean_file_contents = clean_file_contents[found_match.end():] + else: + log_warning(filename, 1, "license does not match at", license_lines[line_num]) + return None + + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories. + +\tGives warnings if the file does not contain a valid license text. It does not check if Copyright statements are included. +""" + exit(0) + + for t in targets: + file_contents, _, _, _ = read_file(t) + check_license_in_file(t, file_contents) diff --git a/scripts/check_long_lines.py b/scripts/check_long_lines.py new file mode 100755 index 0000000..63da64d --- /dev/null +++ b/scripts/check_long_lines.py @@ -0,0 +1,52 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * +from common_modules.config import G_MAX_LINE_LENGTH_THRESHOLD + + +def check_long_lines(filename, file_lines): + string_re = re.compile(r'"((?!((? G_MAX_LINE_LENGTH_THRESHOLD and re.search(string_re, line) == None: + log_warning(filename, i + 1, "line too long, {0} characters [maximum is {1}]".format(len(line), G_MAX_LINE_LENGTH_THRESHOLD)) + + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories. + +\tGives warnings if there are too long lines for readability. A line can have a maximum of {0} characters. +""".format(G_MAX_LINE_LENGTH_THRESHOLD) + exit(0) + + for t in targets: + _, _, file_lines, _ = read_file(t) + check_long_lines(t, file_lines) diff --git a/scripts/check_single_definition_on_line.py b/scripts/check_single_definition_on_line.py new file mode 100755 index 0000000..13cdacf --- /dev/null +++ b/scripts/check_single_definition_on_line.py @@ -0,0 +1,51 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * + +def check_single_definition_on_line(filename, file_contents, clean_file_contents, file_lines, clean_file_lines): + #variable name can be preceeded by * for pointers or & for references, can be followed by an assignment + var_decl_re_text = r"(((\**)|(&?))(\s*)(\w+)((=((?![,{]).)*)?))" + #types can be in namespaces or nested classes + no_template_type_re_text = r"\w+(::\w+)*" + simple_several_defs_re = re.compile(r"^(\s*){0}(\s+){1}((\s*),(\s*){1})+;".format(no_template_type_re_text, var_decl_re_text), re.MULTILINE) + + for match in re.finditer(simple_several_defs_re, clean_file_contents): + line_number = clean_file_contents.count("\n", 0, match.end()) + log_warning(filename, line_number + 1, "several definitions on same line", file_lines[line_number].strip(" \t\r\n")) + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories. + +\tGives warnings if a line contains several variable definitions (This does not include lines that contain template or array declarations). +""" + exit(0) + + for t in targets: + if t[-2:] == ".h" or t[-4:] == ".cpp" or t[-2] == ".c": + file_contents, clean_file_contents, file_lines, clean_file_lines = read_file(t) + check_single_definition_on_line(t, file_contents, clean_file_contents, file_lines, clean_file_lines) diff --git a/scripts/check_single_statement_on_line.py b/scripts/check_single_statement_on_line.py new file mode 100755 index 0000000..7096e13 --- /dev/null +++ b/scripts/check_single_statement_on_line.py @@ -0,0 +1,54 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * + +def check_single_statement_on_line(filename, file_contents, clean_file_contents, file_lines, clean_file_lines): + open_parantheses_count = 0 + + for i in range(len(file_lines)): + line = clean_file_lines[i] + + open_parantheses_count += line.count("(") - line.count(")") + + #check if there are several semicolons on the line and that is not in the middle of a for statement + if line.count(";") > 1 and open_parantheses_count == 0: + #check that there is NO right parentheses after semicolon + if line.rfind(";") > line.rfind(")"): + log_warning(filename, i + 1, "several statements on same line", file_lines[i].strip(" ")) + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories. + +\tGives warnings if a line contains several statements. +""" + exit(0) + + for t in targets: + if t[-2:] == ".h" or t[-4:] == ".cpp" or t[-2] == ".c": + file_contents, clean_file_contents, file_lines, clean_file_lines = read_file(t) + check_single_statement_on_line(t, file_contents, clean_file_contents, file_lines, clean_file_lines) diff --git a/scripts/check_style.sh b/scripts/check_style.sh new file mode 100755 index 0000000..e3c579e --- /dev/null +++ b/scripts/check_style.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + + + +DIRECTORY=$(cd `dirname $0` && pwd) +cd $DIRECTORY +cd .. + +echo $PWD +echo "----------------------------------------" + +TEMP_FILENAME=".check_style_temp.`date +%s%N`" + +TARGET_FILES="`find -iname *.cpp -or -iname *.h -or -iname *.c | grep -vi \/tests\/ | grep -vi \/test\/ | grep -vi 3rdParty | grep -vi \/LayerManagerExamples\/ | grep -vi \config\/`" + +for t_file in $TARGET_FILES +do + python scripts/check_all_styles.py $t_file | tee $TEMP_FILENAME -a +done + +LINES=`cat $TEMP_FILENAME | wc -l` + +echo -e "\nTotal number of warnings: $LINES" +rm $TEMP_FILENAME \ No newline at end of file diff --git a/scripts/check_tabbing_and_spacing.py b/scripts/check_tabbing_and_spacing.py new file mode 100755 index 0000000..eb04529 --- /dev/null +++ b/scripts/check_tabbing_and_spacing.py @@ -0,0 +1,73 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +import sys, re, string +from common_modules.common import * + +def check_tabs_no_spaces(filename, file_lines): + for i in range(len(file_lines)): + if file_lines[i].count("\t") > 0: + log_warning(filename, i + 1, "tab character (\\t) found, use 4 spaces instead", file_lines[i].strip(" ")) + +def check_correct_space_count(filename, file_lines): + in_multiline_comment = False + for i in range(len(file_lines)): + if in_multiline_comment: + if file_lines[i].count("*/") > 0: + in_multiline_comment = file_lines[i].count("/*") > 0 and file_lines[i].index("/*") > file_lines[i].index("*/") + else: + in_multiline_comment = file_lines[i].count("/*") > 0 + + found_match = re.search("(?! )", file_lines[i]) + if found_match: + space_count = found_match.start() + if space_count % 4 != 0: + log_warning(filename, i + 1, "number of spaces at beginning of line must be divisible by 4") + + +def check_no_spacing_line_end(filename, file_lines): + for i in range(len(file_lines)): + if re.search(" $", file_lines[i]): + log_warning(filename, i + 1, "unneeded space(s) at end of line") + +def check_tabbing_and_spacing(filename, file_lines): + check_tabs_no_spaces(filename, file_lines) + check_correct_space_count(filename, file_lines) + check_no_spacing_line_end(filename, file_lines) + + +if __name__ == "__main__": + targets = sys.argv[1:] + targets = get_all_files(targets) + + if len(targets) == 0: + print """ +\t**** No input provided *** +\tTakes a list of files/directories as input and performs specific style checking on all files/directories. + +\tGives warnings if a line contains unneeded spaces at end of line, contains tab characters (\\t) +\tor if spaces at line beginning are not divisible by 4. +""" + exit(0) + + for t in targets: + if t[-2:] == ".h" or t[-4:] == ".cpp" or t[-2] == ".c": + _, _, file_lines, _ = read_file(t) + check_tabbing_and_spacing(t, file_lines) diff --git a/scripts/common_modules/__init__.py b/scripts/common_modules/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/scripts/common_modules/common.py b/scripts/common_modules/common.py new file mode 100755 index 0000000..70d42b8 --- /dev/null +++ b/scripts/common_modules/common.py @@ -0,0 +1,140 @@ +#!/usr/bin/python + +########################################################################### +# +# Copyright 2013 BMW Car IT GmbH +# +# 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. +# +########################################################################### + +""" + Contains utility functions used by other modules +""" + +import sys, re, string, os +import config + +def clean_string_from_regex(s, regex, marker): + """ + Removes all occurances of the passed regex from a string s, and puts instances of marker at the beginnings and ends + of remved substring + """ + + result = s + found_match = re.search(regex, result) + + while found_match != None: + matched_string = result[found_match.start(): found_match.end()] + #replace by x...x where the ... is the \n chars in the matched string (to keep number of lines constant) + result = result[:found_match.start()] + marker + "\n" * matched_string.count("\n") + marker + result[found_match.end():] + + found_match = re.search(regex, result) + + return result + +def get_clean_file_contents(file_contents): + """ + Removes strings, C style and C++ comments style comments and macros from file_contents + """ + + clean_file_contents = file_contents + + #remove all strings + #a string is something that starts with a (") and ends with a ("), can have any character inside it except a (") that is not preceded by a slash (\) that is not preceded by another slash + #this means a string can contains a \\ escape sequence + #a string can contain a \" escape sequence + #a string can end with \\", but it does not end with \" + string_re = re.compile(r'"((?!((?