Check-style scripts: fixes
authorTimo Lotterbach <timo.lotterbach@bmw-carit.de>
Wed, 27 Mar 2013 11:46:33 +0000 (12:46 +0100)
committerTimo Lotterbach <timo.lotterbach@bmw-carit.de>
Wed, 27 Mar 2013 13:14:10 +0000 (14:14 +0100)
-check license can now handle different license
-solved a lot of limitations for check indentation and single definitions on line
-solved some small bugs (did not introduce any new positives or negatives)

Signed-off-by: Timo Lotterbach <timo.lotterbach@bmw-carit.de>
14 files changed:
scripts/check_all_styles.py
scripts/check_class_name.py
scripts/check_consistent_code_style.py
scripts/check_curly_braces_alone_on_line.py
scripts/check_empty_lines.py
scripts/check_header_guards.py
scripts/check_indentation.py
scripts/check_license.py
scripts/check_long_lines.py
scripts/check_single_definition_on_line.py
scripts/check_single_statement_on_line.py
scripts/check_tabbing_and_spacing.py
scripts/common_modules/common.py
scripts/common_modules/config.py

index c8cb972..0806265 100755 (executable)
@@ -19,7 +19,8 @@
 ###########################################################################
 
 """
-    Runs all enabled style checking
+Runs all enabled style checking
+
 """
 
 import sys, re, string
@@ -57,7 +58,7 @@ 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.
@@ -65,11 +66,11 @@ if __name__ == "__main__":
 \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":
index ef08ca6..0acf25f 100755 (executable)
@@ -23,8 +23,13 @@ from common_modules.common import *
 
 
 def check_class_name(filename, clean_file_contents):
-    #a class is followed by classname, and possible an inherticance
+    """
+    Check that the file contains (maximum) one class that has a name identical to file name
+
+    """
+    #a class keyword is followed by classname, (and possibly an inherticance) and a left curly brace
     class_def_re = re.compile(r'(?<!\w)class(\s+)(\w+)(\s*)((:( |\w|,)*)?)(\s*)\{')
+    #this is used to catch only the class name, because the (?>= ) is not included in the match
     class_name_re = re.compile(r'(?<=((?<!\w)class))(\s+\w+)')
 
     #filename without path and extension
@@ -37,31 +42,35 @@ def check_class_name(filename, clean_file_contents):
     #remove .h extension
     good_name = good_name[:good_name.find(".h")]
 
+    #initially: no class found yet
     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 a class was found before: give warning
         if class_found:
             log_warning(filename, line_number, "several class difinitions in file")
         else:
+            #if no class was found before: set flag to True
             class_found = True
 
+            #get class name
             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 does not match file name: give warning
             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 ***
+\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
index a3aae73..cf917db 100755 (executable)
@@ -24,13 +24,23 @@ from common_modules.common import *
 
 
 def check_binary_operators(filename, file_contents, file_lines, clean_file_contents, clean_file_lines):
+    """
+    Check if spacing around binary opeartors is consistent in file
+
+    """
     #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'\?']
+    binary_ops = ['=', r'\+', '%', r'\^', '==', '!=', '>=', '<=', '&&', r'\|\|', r'\|', '<<', '>>', r'\+=', '-=', r'\*=', '/=', '%=',  '&=', r'\|=', r'\^=', '<<=', '>>=', ':', r'\?', '/']
     #operators that are not handled
-    #r'\*', '&', '>', '<', '/', '-'
+    #r'\*', '&', '>', '<', '-'
     #access binary operators must have no space between operator and operands
     binary_ops_re_text = '(' + "|".join(binary_ops) + ')'
-    
+
+    #initially: clean file contents from confusing content:
+    #remove all preprocessor directives
+    preprocessor_re = re.compile(r'#\s*\w+(\s+)(((?!\\).)+)((\\\n(((?!\\).)+))*)')
+    clean_file_contents = clean_string_from_regex(clean_file_contents, preprocessor_re, '')
+    clean_file_lines = clean_file_contents.split('\n')
+
     #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
@@ -38,69 +48,82 @@ def check_binary_operators(filename, file_contents, file_lines, clean_file_conte
     #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)
-    
+
+    #a single consistency in the file is enough to issue a warning
     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):
+    """
+    Checks that there are no spaces before semicolons, and that there is a space after it in case there is other content
+    on the same line after it
+
+    """
     #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 that there are no unnecessary spaces around parentheses
+
+    """
     #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):
+    """
+    Check that there is no space before commas, and there is exactly one space after coma
+
+    """
     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(" "))
 
@@ -108,6 +131,10 @@ def check_commas(filename, file_contents, file_lines, clean_file_contents, clean
             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):
+    """
+    Check that there is no space between function call/definition and left parenthese
+
+    """
     #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']
@@ -118,7 +145,7 @@ def check_function_call_or_definition(filename, file_contents, file_lines, clean
     #the \s+ is the fauled pattern that should be caught, because it means a space exists between function name and left parenthese
     bad_func_re_text = r"(\w+){0}(\s+)(\()".format(reserved_words_re_text)
     bad_func_re = re.compile(bad_func_re_text)
-        
+
     #function pointer
     return_type_re_text = r'\w+\s*(&|\*)*\s*'
     func_ptr_re_text = r'\s*{0}\((\w|\*|:|\s)+\)(\s*)\('.format(return_type_re_text)
@@ -126,41 +153,50 @@ def check_function_call_or_definition(filename, file_contents, file_lines, clean
     func_ptr_re = re.compile(func_ptr_re_text)
 
     macro_re = re.compile(r"(\s*)#define")
-    
+
     for i in range(len(file_lines)):
         line = clean_file_lines[i]
 
+        #check all occurances in a line (a line can contain several function calls)
         for bad_func_match in bad_func_re.finditer(line):
             #print line[bad_func_match.start():], re.match(func_ptr_re, line[bad_func_match.start():]) != None
             if re.search(func_ptr_re, line) == None and re.match(macro_re, line) == None:
                 log_warning(filename, i + 1, "unneeded space(s) between function name and left brace", file_lines[i].strip(" "))
 
-def check_space_before_braces(filename, file_contents, file_lines, clean_file_contents, clean_file_lines):
+def check_keyword_brace_space(filename, file_contents, file_lines, clean_file_contents, clean_file_lines):
+    """
+    Check that all occurances of (if, for,...etc) in file have a space between keyword and left brace or
+    that all occurances do not have a space between keyword and left brace
+
+    """
+    #the keywords to be checked
     reserved_words = ['if', 'for', 'while', 'switch', 'return']
+    #the \W makes sure the keyword is actually a keyword and not a part of a function name, e.g, if() would match but foo_if() would be filtered out
     reserved_words_re_text = r'\W(' + "|".join(reserved_words) + ')'
-    
+
     nospace_re = re.compile(r"{0}\(".format(reserved_words_re_text))
     space_re = re.compile(r"{0}\s\(".format(reserved_words_re_text))
     too_many_space_re = re.compile(r"{0}\s(\s+)\(".format(reserved_words_re_text))
-    
+
+    #check if there is a general inconsistency (a signle occurance is in the file is enough to give a warning)
     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 keyword and left brace. All braces 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(too_many_space_re, line) != None:
             log_warning(filename, i + 1, "unneeded space(s) between keyword and left brace", file_lines[i].strip(" "))
 
 def check_consistent_code_style(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
     check_function_call_or_definition(filename, file_contents, file_lines, clean_file_contents, clean_file_lines)
     check_semicolor(filename, file_contents, file_lines, clean_file_contents, clean_file_lines)
-    check_space_before_braces(filename, file_contents, file_lines, clean_file_contents, clean_file_lines)
+    check_keyword_brace_space(filename, file_contents, file_lines, clean_file_contents, clean_file_lines)
     check_braces_spaces(filename, file_contents, file_lines, clean_file_contents, clean_file_lines)
     check_commas(filename, file_contents, file_lines, clean_file_contents, clean_file_lines)
     check_binary_operators(filename, file_contents, file_lines, clean_file_contents, clean_file_lines)
@@ -169,16 +205,16 @@ def check_consistent_code_style(filename, file_contents, clean_file_contents, fi
 if __name__ == "__main__":
     targets = sys.argv[1:]
     targets = get_all_files(targets)
-    
+
     if len(targets) == 0:
         print """
-\t**** No input provided ***
+\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 is inconsistency in spacing of commas, semicolons, operands, braces, function calls or definitions.
 """
         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)
index 3e57ccc..aedbb11 100755 (executable)
@@ -22,92 +22,105 @@ import sys, re, string
 from common_modules.common import *
 
 def remove_tollerable_braces(clean_file_contents):
-    #remove array assignments
-    assignment_re = re.compile(r'=(\s*)(\{)((?!;).)*(\})(\s*);', re.DOTALL)
-    
-    no_assig_contents = clean_file_contents
-    found_match = re.search(assignment_re, no_assig_contents)
-    
-    while found_match != None:
-        matched_string = no_assig_contents[found_match.start(): found_match.end()]
-        no_assig_contents = no_assig_contents[:found_match.start()] + "\n" * matched_string.count("\n") + no_assig_contents[found_match.end():]
+    """
+    Cleans file contents from code blocks that should not be checked:
+        1- Array initialization blocks
+        2- Empty functions
 
-        found_match = re.search(assignment_re, no_assig_contents)
+    """
+    #array assignment begins with a left curly brace ({) followed by any number of characters that is not a semicolon
+    #it ends by a right curly brace (}) and a semicolon (;)
+    assignment_re = re.compile(r'=(\s*)(\{)((?!;).)*(\})(\s*);', re.DOTALL)
+    #remove array assignments from file contents
+    no_assig_contents = clean_string_from_regex(clean_file_contents, assignment_re, '')
 
     #remove braces that are used for implementing empty functions
     empty_func_re = re.compile(r'\{\s*\}')
-
-    no_empty_func_contents = no_assig_contents
-    found_match = re.search(empty_func_re, no_empty_func_contents)
-
-    while found_match != None:
-        matched_string = no_empty_func_contents[found_match.start(): found_match.end()]
-        no_empty_func_contents = no_empty_func_contents[:found_match.start()] + "\n" * matched_string.count("\n") + no_empty_func_contents[found_match.end():]
-
-        found_match = re.search(empty_func_re, no_empty_func_contents)
+    no_empty_func_contents = clean_string_from_regex(no_assig_contents, empty_func_re, '')
 
     return no_empty_func_contents
 
 def check_curly_braces_alone_on_line(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
+    """
+    Checks that there is no unnecessary code on same line with curly braces.
+
+    """
     clean_file_contents = remove_tollerable_braces(clean_file_contents)
     clean_file_lines = clean_file_contents.split('\n')
-    
+
+    #keep a stack booleans to indicate where type declarations are made
     typedecl_stack = []
-    #a type declaration contains the words struct, class , union or enum
+
+    #a type declaration contains the words struct, class, union or enum
+    #it can also have the typdef keyword in the beginning
     typedecl_re = re.compile(r"(?!<\w)(typedef)?(\s*)(struct|class|enum|union)(?!\w)")
+
     #some statements are allowed to have curly brace on same
+    #(do..while) blocks, namespaces and extern blocks
     permissible_re = re.compile(r'''(\s*)
                                 (
-                                    do| # do keyword
-                                    (namespace((?!\{).)*)| # namespace keyword 
-                                    (extern((?!\{).)*) # extern keyword
+                                    do|                     # do keyword
+                                    (namespace((?!\{).)*)|  # namespace keyword 
+                                    (extern((?!\{).)*)      # extern keyword
                                 )?
-                                (\s*)\{(\s*)
-                                (\\?)(\s*)
+                                (\s*)\{(\s*)                # open curly brace
+                                (\\?)(\s*)                  # backslash indicates a multiline macro or multiline string
                                 $''', re.VERBOSE)
 
-    
     for i in range(len(file_lines)):
         line = clean_file_lines[i]
-        
+
         def get_former_line():
+            """
+            Gets the last non-empty 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 None
-        
+            return ""
+
         former_line = get_former_line()
-        
+
         if line.count("{"):
+            #check if the line contains a type declaration
             typedecl = re.search(typedecl_re, former_line)
             typedecl_stack.append(typedecl)
-            
+
+            #make a warning if line does not follow the permissible set of expressions
             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 the right curly brace marks the end of type declaration:
+                #it is possible to have code AFTER the curly brace in case of type declarations
+                #like instantiating a structure at end of structure definition
                 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 the right curly brace does not mark the end of type declaration:
+                #the line must begin with the right curly brace, can possibly have a while expression and ends by semicolon
+                #this has to be the end of line ($) to avoid having a second statement on the same line
                 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 ***
+\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)
index e5f51f2..7bff68d 100755 (executable)
 
 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):
+    """
+    Check that there are no non-necessary empty lines
+
+    """
     #keep track of the current indentation
     #need only to know if level of indent is zero or not
     line_indent_zero = True
@@ -38,6 +41,11 @@ def check_empty_lines(filename, file_contents, file_lines):
         next_line = file_lines[i + 1] if i < len(file_lines) - 1 else ""
 
         def is_next_indent_zero():
+            """
+            Checks the indentation of the next non-empty line
+            return True if it is at zero-level indentation
+
+            """
             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
@@ -57,12 +65,13 @@ def check_empty_lines(filename, file_contents, file_lines):
             #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 non-empty line: check if line is at zero indentation
             #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 file does not end with an empty line: give warning
     if re.search(r'\n$', file_contents) == None:
         log_warning(filename, len(file_lines), "file does not end with new line")
 
@@ -73,7 +82,7 @@ if __name__ == "__main__":
 
     if len(targets) == 0:
         print """
-\t**** No input provided ***
+\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
index 0dbb7d5..bc8e857 100755 (executable)
@@ -22,6 +22,13 @@ import sys, re, string
 from common_modules.common import *
 
 def check_header_guards(filename, file_contents):
+    """
+    Check if a header files contains valid header guards:
+        1- #ifndef GUARDNAME directly after license that has a guard name identical to file name in uppercase letters, underscores at beginnings and end are tollerable
+        2- directly followed by #define that matches the guard name of #ifndef exactly
+        3- the file has to end with an #endif
+
+    """
     #remove strings
     string_re = re.compile(r'"((?!((?<!((?<!\\)\\))")).)*"', re.DOTALL)
     file_contents = clean_string_from_regex(file_contents, string_re, 'x')
@@ -29,66 +36,69 @@ def check_header_guards(filename, file_contents):
     #remove multi-line comments
     ml_comment_re = re.compile(r'(\s*)/\*((?!\*/).)*\*/', re.DOTALL)
     file_contents = clean_string_from_regex(file_contents, ml_comment_re, '')
-    
+
     #remove single line comments
     sl_comment_re = re.compile("//.*$", re.MULTILINE)
     file_contents = re.sub(sl_comment_re, '', file_contents)
-    
+
+    #remove any padding white space characters
     file_contents = file_contents.strip(" \t\n\f\r")
 
-    
     def check_good_name(name):
         #good name
         good_name = filename
-        
+
         #remove path from filename (if needed)
         if good_name.count("/") > 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
-    
+
+    #get guard name
     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
-    
+
+    #get guard name
     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 ""
-    
+
+    #check if #ifndef and #define have identical guard names
     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)
@@ -96,21 +106,20 @@ def check_header_guards(filename, file_contents):
         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 ***
+\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)
index 4ae6c83..3ec53ba 100755 (executable)
 import sys, re, string
 from common_modules.common import *
 from common_modules.config import G_MAX_INDENTATION_THRESHOLD
+from common_modules.config import G_TAB_SEQUENCE
 
-def check_indentation(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
+def get_correct_indentation_list(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]
+
+    #current indent level
     indent_level = 0
-    parentheses_count = 0
-    
-    def is_incomplete_statement(line, former_line):
-        incomplete_statement = False
+
+    #indent level for every line
+    indentation_levels = [0] * len(clean_file_lines)
+
+    #get correct indentation for every line
+    for i in range(len(clean_file_lines)):
+        line = clean_file_lines[i]
+
+        def get_former_line():
+            """
+            Gets the last previous non-empty 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 ""
+
+        #previous non-empty line
+        former_line = get_former_line()
+
+        # the next part of the code decreases the indent level
+        #
+        # the level of indent decrease when right curly braces are found
+        # in the line.
+        #
+        # if the line begins with a right curly brace then the line ITSELF
+        # gets a decreased indent level
+        #
+        # if the line has some content on the same line before the right
+        # curly brace (which is a bad practice, but nevertheless) the line
+        # ITSELF does not get a decreased level of indentation, but the
+        # following lines get a decreased level of indentation
+
+        #check if the line begins with a right curly brace
+        line_begins_with_right_brace = re.match(r"\s*\}", line) != None
+
+        #if the line DOES NOT begin with right curly brace then it has the same level
+        #of indentation as the previous line(s)
+        if not line_begins_with_right_brace:
+            indentation_levels[i] = indent_level
+
+        #this is to decrease the level of indentation in case there is a right curly brace "}"
+        #the level of indentation is decreased by popping elements from the indentation level stack
+        #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()
+
+        #if the line begins with right curly brace then it gets a decreased
+        #level of indentation
+        if line_begins_with_right_brace:
+            indentation_levels[i] = indent_level
+
+        # the next part of the code increases the indent level
+        #
+        # indent level increases when a left curly brace is found
+        #
+        # an exception to this if the curly brace is the beginning
+        # of a namespace or an extern block, in this case the indent
+        # level does not increase
+
+        # A flag is used in the very specific case of having several left
+        # curly braces after a namespace/extern block (which is a bad practice)
+        #
+        # Initially the flag is False,soo that the first curly brace would not
+        # increase indentation, after that the flag is raised (set to True)
+        # so that every new left curly brace would incraese indentation
+        flag = False
+
+        #increase the level of indentation of there are open left curly braces "{"
+        #a loop is to account for cases where there are several braces on same line
+        for _ in range(0, line.count("{") - line.count("}")):
+            #push the current level of indentation to the stack
+            indent_levels_stack.append(indent_level)
+
+            #if there is no namesapce or extern declaration before curly brace: increase indentation
+            #that is because namespaces do not increase the level of indentation
+            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 the current line contains a namespace/extern declation
+            if re.match(namespace_or_extern_re, line) != None:
+                #increase the level of indentation only if the flag is raised
+                #(if it is NOT the first curly brace on line)
+                if flag:
+                    indent_level += 1
+                else:
+                    #raise the flag (so that next curly braces would increase indentation)
+                    flag = True
+
+            #otherwise: if the previous line contains namesapce/extern declaration
+            elif re.match(namespace_or_extern_re, former_line) != None:
+                #the indent level does not increase only if the previous line does not contain
+                #a left curly brace, and if this is the first curly brace on the current line (flag is still False)
+                #if either of the conditions is false it is safe to increase the indent level
+                if re.search(r'(namespace|extern)((?!\{).)*\{', former_line) == None and not flag:
+                    flag = True
+                else:
+                    indent_level += 1
+
+            #otherwise: just increase indent level
+            else:
+                indent_level += 1
+
+    return indentation_levels
+
+def check_indentation(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
+    """
+    Checks if all lines in a file (except multi-line comments, strings and macros) have correct indentation
+
+    """
+    def is_statement_supplement(line, former_line, parentheses_count):
+        """
+        Checks if the statement is the completion of a previous statement, like arethmatic expressions broken on several lines, or for loop
+        parts on separate lines. It also includes checks that the previous statement is well-closed (ends by semicolon or curly brace)
+
+        IMPORTANT: the first line of a for loop (or an arethmatic expression...etc) broken on several lines would still not be considered
+        a statement supplement
+
+        """
+        statement_supplement = 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
+        statement_supplement |= 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
+        statement_supplement &= 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
+        statement_supplement &= 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
+        statement_supplement &= not is_preprocessor_directive(line)
         #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
-
+        statement_supplement &= 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
+        fl_label_statement = is_label_statement(former_line)
+        statement_supplement &= not fl_label_statement
 
         #include statements that are contained inside parentheses
-        incomplete_statement |= parentheses_count > 0
-        
-        return incomplete_statement
-    
+        statement_supplement |= parentheses_count > 0
+
+        return statement_supplement
+
+    def is_macro_body(former_line):
+        """
+        Checks if the line belongs to a macro body by checking that the line before it ends with a slash
+
+        """
+        return former_line != None and re.search(r"\\(\s*)$", former_line) != None
+
     def is_label_statement(line):
+        """
+        Checks if line starts with a label/case definition
+
+        """
         label_statement = False
         if line != None:
             #case statements -> case id : expr that does not begin with a : (to avoid matching with :: operator)
@@ -67,97 +192,111 @@ def check_indentation(filename, file_contents, clean_file_contents, file_lines,
             #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):
+        """
+        Checks if a line is a preprocessor directive
+
+        """
+        #preproccessor directives start with a #
         return re.match(r"(\s*)#", line) != None
 
-    def is_macro_body(former_line):
-        return former_line != None and re.search(r"\\$", former_line) != None
-    
+    #keeps count of open (non-closed) left parentheses
+    parentheses_count = 0
+
+    #configured tab character
+    tab_character = G_TAB_SEQUENCE[0]
+    tab_character_count = G_TAB_SEQUENCE[1]
+
+    #get list of correct levels of indentation for every line
+    correct_indent_levels = get_correct_indentation_list(clean_file_lines)
+
+    #indent level of current line and previous line
+    correct_il = 0
+    previous_il = 0
+
+    #check every line for correct indentation
     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
-        
+        #correct indent level of current line
+        correct_il = correct_indent_levels[i]
+
+        #actual indent level of the line
+        actual_il = 0
+
+        #get the actual line indentation
+        #this regex searches for the first character in line that is not a space
+        #if no match found this means a zero level of indentation
+        indent_match = re.search("(?!{0})".format(tab_character), line)
+        if indent_match:
+            actual_il = 1.0 * indent_match.start() / tab_character_count
+
         def get_former_line():
+            """
+            Gets the last previous non-empty 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 ""
-        
+
+        #previous non-empty line
         former_line = get_former_line()
-        
-        incomplete_statement = is_incomplete_statement(line, former_line)
+
+        #make several checks about the line
+        statement_supplement = is_statement_supplement(line, former_line, parentheses_count)
         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 at non-empty line: check if indentation is correct
         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:
+                #preproccessor directives must have 0 level indentation
+                if actual_il != 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:
+                #label statements must have one level less indentation than the normal indentation level
+                if actual_il != correct_il - 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 
+            elif (not statement_supplement) and actual_il != correct_il:
+                #a complete statement (NON supplement statement) must be at the normal level of indentation
+                log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" "))
+            elif statement_supplement and actual_il < correct_il:
+                #statement supplements must have a level of indentation at least equal to 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),)
-        
+            #warning if indentation level is too much
+            if correct_il == G_MAX_INDENTATION_THRESHOLD + 1 and previous_il < correct_il:
+                log_warning(filename, i + 1, "code block has too much indentation [maximum level of indentation is {0}]".format(G_MAX_INDENTATION_THRESHOLD),)
+
+        #update open parentheses count
         parentheses_count += line.count("(") - line.count(")")
 
+        #update previous indent level
+        previous_il = correct_il
+
 
 if __name__ == "__main__":
     targets = sys.argv[1:]
     targets = get_all_files(targets)
-    
+
     if len(targets) == 0:
         print """
-\t**** No input provided ***
+\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)
index 340c31b..7ec9ed5 100755 (executable)
 
 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.
-*
-****************************************************************************/'''
+from common_modules.config import G_LICENSE_TEMPLATES
 
 
 def clean_comment_chars(s):
     """
-        Removes comment characters from a string
+    Removes comment characters from a string
+
     """
     s = string.replace(s, "/", "")
     s = string.replace(s, "*", "")
@@ -50,79 +35,119 @@ def clean_comment_chars(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
+    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)
+        #remove white space paddings
         re_text = re_text.strip(" \n\t\r\f")
+        #replace special characters
         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, ".", "\.")
-        
+
+        #this replaces the text [YYYY] with a regex that mathces years in one of the following forms:
+        #2013 or 2000-2013 or 2000 or 2000, 2001, 2002, 2013
         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 = ""
+            #remove unneeded space matches
             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_specific_license_in_file(filename, clean_file_contents, license_text):
+    """
+    Checks if the file contains a valid license according to the license template provided
 
-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
+
+    #check that license exists without any added or removed words
     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])
+            #log_warning(filename, 1, "license does not match at", license_lines[line_num])
+            return (line_num, license_lines[line_num])
+
+    return None # success
+
+def check_license_in_file(filename, file_contents):
+    """
+    Checks if the file contains a valid license.
+    It tries to find a match inside the file with any of the licenses configured
+
+    """
+    clean_file_contents = clean_comment_chars(file_contents)
+
+    #license that had the best match with the file
+    best_match = (-1, None)
+
+    #try to match with every license
+    for license in G_LICENSE_TEMPLATES:
+        call_result = check_specific_license_in_file(filename, clean_file_contents, license)
+
+        #if match is found just return
+        if call_result == None:
             return None
 
+        #if no match found check if this license was a good candidate for the match
+        else:
+            best_match = call_result if call_result[0] > best_match[0] else best_match
+
+    #(this else clause is executed if the for loop exists naturally)
+    #if loop ended without return, this means no license matched
+    else:
+        #if no license matched at all
+        if best_match[1] == None:
+            log_warning(filename, 1, "no license found")
+
+        #get the license with the best match
+        else:
+            log_warning(filename, 1, "license does not match at", best_match[1])
 
 if __name__ == "__main__":
     targets = sys.argv[1:]
     targets = get_all_files(targets)
-    
+
     if len(targets) == 0:
         print """
-\t**** No input provided ***
+\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)
index 63da64d..636f798 100755 (executable)
@@ -24,11 +24,17 @@ from common_modules.config import G_MAX_LINE_LENGTH_THRESHOLD
 
 
 def check_long_lines(filename, file_lines):
+    """
+    Checks if a line is too long by comparing it to a (configurable) threshold. If a line contains
+    string literals it is not checked
+
+    """
+    #regex for finding strings
     string_re = re.compile(r'"((?!((?<!((?<!\\)\\))")).)*"', re.DOTALL)
-    
+
     for i in range(len(file_lines)):
         line = file_lines[i]
-        
+
         #if line is very long and DOES NOT contain strings
         if len(line) > 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))
@@ -37,16 +43,16 @@ def check_long_lines(filename, file_lines):
 if __name__ == "__main__":
     targets = sys.argv[1:]
     targets = get_all_files(targets)
-    
+
     if len(targets) == 0:
         print """
-\t**** No input provided ***
+\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)
index 13cdacf..02b709b 100755 (executable)
@@ -22,29 +22,60 @@ 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):
+    """
+    Checks if a line contains several variable definitions
+
+    """
+    #remove arrays
+    #an array regex starts with "{", ends with "}" and contains anything in between
+    #an array of arrays is removed recursively inside out
+    array_re = re.compile(r'\{[^ {};]*\}')
+    clean_file_contents = clean_string_from_regex(clean_file_contents, array_re, '')
+
+    #remove brackets
+    array_brackets_re = re.compile(r'\[((?!\[|\]|;).)*\]')
+    clean_file_contents = clean_string_from_regex(clean_file_contents, array_brackets_re, '')
+
+    #remove angle brackets
+    template_re = re.compile(r'<((?![<>;]).)*>')
+    clean_file_contents = clean_string_from_regex(clean_file_contents, template_re, '')
+
     #variable name can be preceeded by * for pointers or & for references, can be followed by an assignment
-    var_decl_re_text = r"(((\**)|(&?))(\s*)(\w+)((=((?![,{]).)*)?))"
+    var_decl_re_text = r"""(
+                                ((&?)(\*)*)                     # can start by & (for reference defs) and/or any number of * (for pointer defs)
+                                (\s*)(\w+)(\s*)                 # followed by identifier
+                                ((=((?![,;]).)*)?)              # can be followed by an assignment
+                            )"""
+
     #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):
+    type_re_text = r"\w+(::\w+)*"
+
+    #it is enough to have
+    several_defs_re_text = r"""
+                                ^(\s*){0}
+                                (\s+){1}
+                                ((\s*),(\s*){1})+;
+                            """.format(type_re_text, var_decl_re_text)
+
+    several_defs_re = re.compile(several_defs_re_text, re.MULTILINE | re.VERBOSE)
+
+    for match in re.finditer(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 ***
+\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)
index 7096e13..313c140 100755 (executable)
@@ -22,13 +22,17 @@ 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):
+    """
+    Checks if there are several statements on the same line
+
+    """
     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
@@ -38,16 +42,16 @@ def check_single_statement_on_line(filename, file_contents, clean_file_contents,
 if __name__ == "__main__":
     targets = sys.argv[1:]
     targets = get_all_files(targets)
-    
+
     if len(targets) == 0:
         print """
-\t**** No input provided ***
+\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)
index eb04529..a8d72b5 100755 (executable)
@@ -22,19 +22,30 @@ import sys, re, string
 from common_modules.common import *
 
 def check_tabs_no_spaces(filename, file_lines):
+    """
+    Checks if any tab character "\t" exists in the file
+
+    """
     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):
+    """
+    Checks that the number of spaces at the beginning of every line is divislbe by four
+
+    """
     in_multiline_comment = False
     for i in range(len(file_lines)):
         if in_multiline_comment:
+            #if end of muli-line comment
             if file_lines[i].count("*/") > 0:
+                #just make sure there is no other new multi-line comment starting on the same line (after the current mult-line comment is closed)
                 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
-            
+
+            #regex searches for the first character that is not a space character
             found_match = re.search("(?! )", file_lines[i])
             if found_match:
                 space_count = found_match.start()
@@ -43,11 +54,19 @@ def check_correct_space_count(filename, file_lines):
 
 
 def check_no_spacing_line_end(filename, file_lines):
+    """
+    Checks that lines do not end with unnecessary white space characters
+
+    """
     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):
+    """
+    Calls other functions that check general issues about tabbing and spacing
+
+    """
     check_tabs_no_spaces(filename, file_lines)
     check_correct_space_count(filename, file_lines)
     check_no_spacing_line_end(filename, file_lines)
@@ -59,14 +78,14 @@ if __name__ == "__main__":
 
     if len(targets) == 0:
         print """
-\t**** No input provided ***
+\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)
index 70d42b8..4b0520b 100755 (executable)
@@ -19,7 +19,7 @@
 ###########################################################################
 
 """
-    Contains utility functions used by other modules
+Contains utility functions used by other modules
 """
 
 import sys, re, string, os
@@ -27,10 +27,10 @@ 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
-    """
+    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)
 
@@ -45,18 +45,19 @@ def clean_string_from_regex(s, regex, marker):
 
 def get_clean_file_contents(file_contents):
     """
-        Removes strings, C style and C++ comments style comments and macros from 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'"((?!((?<!((?<!\\)\\))")).)*"', re.DOTALL)
-    
+
     #'x' is passed as a marker, to mark the beginning and of strings
     #to preserve spacing around string beginning and end
     clean_file_contents = clean_string_from_regex(clean_file_contents, string_re, 'x')
@@ -67,11 +68,11 @@ def get_clean_file_contents(file_contents):
     char_re = re.compile(r'\'((?!((?<!((?<!\\)\\))\')).)*\'')
 
     clean_file_contents = clean_string_from_regex(clean_file_contents, char_re, 'x')
-    
+
     #remove multi-line comments
     ml_comment_re = re.compile(r'(\s*)/\*((?!\*/).)*\*/', re.DOTALL)
     clean_file_contents = clean_string_from_regex(clean_file_contents, ml_comment_re, '')
-    
+
     #remove single line comments
     sl_comment_re = re.compile(r'[ \t\r\f]*//.*$', re.MULTILINE)
     clean_file_contents = re.sub(sl_comment_re, '', clean_file_contents)
@@ -80,30 +81,32 @@ def get_clean_file_contents(file_contents):
     #a macro is #define statement followed by a few statements that have a slash in the line before it
     macro_re = re.compile(r'#define(\s+)(((?!\\).)+)((\\\n(((?!\\).)+))*)')
     clean_file_contents = clean_string_from_regex(clean_file_contents, macro_re, '')
-    
+
     return clean_file_contents
 
 
 def read_file(filename):
     """
-        Reads contents of the file, returns a 4-tuple containing:
+    Reads contents of the file, returns a 4-tuple containing:
         1- raw file contents
         2- file contents without comments, strings and macros
         3- raw file contents as lines
         4- file contents without comments, strings and macros as lines
+
     """
     file_contents = open(filename).read()
     clean_file_contents = get_clean_file_contents(file_contents)
-    
+
     file_lines = file_contents.split("\n")
     clean_file_lines = clean_file_contents.split("\n")
-    
+
     return (file_contents, clean_file_contents, file_lines, clean_file_lines)
 
 
 def get_all_files(targets):
     """
-        Iterates over targets and gets names of all files
+    Iterates over targets and gets names of all files
+
     """
     filenames = []
     for t in targets:
@@ -112,29 +115,28 @@ def get_all_files(targets):
             for (path, _, files) in os.walk(t, followlinks=True):
                 for f in files:
                     filenames.append(os.path.abspath(os.path.join(path, f)))
-            
+
         #if file: process directly
         else:
             filenames.append(os.path.abspath(t))
-    
+
     return filenames
 
 def log_warning(filename, line_number, description, line_content = None):
     """
-        Prints a warning to the standard output
+    Prints a warning to the standard output
+
     """
     if line_content == None:
         print "{0}:{1}: warning: {2}".format(filename, line_number, description)
     else:
         print "{0}:{1}: warning: {2}: {3}".format(filename, line_number, description, line_content)
-    
+
     config.G_WARNING_COUNT += 1
 
 def get_warning_count():
     """
-        Returns the number of reported warnings
-    """
-    return config.G_WARNING_COUNT
-
-
+    Returns the number of reported warnings
 
+    """
+    return config.G_WARNING_COUNT
\ No newline at end of file
index 106e808..002de97 100755 (executable)
 ###########################################################################
 
 """
-    Contains configurations constants
+Contains configurations constants
+
 """
 G_MAX_INDENTATION_THRESHOLD = 6
 G_MAX_LINE_LENGTH_THRESHOLD = 140
 G_WARNING_COUNT = 0
+G_TAB_SEQUENCE = (' ', 4)
+
+
+G_LICENSE_TEMPLATES = ["""
+/***************************************************************************
+*
+*
+* 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.
+*
+****************************************************************************/
+""", """
+// Copyright [YYYY], Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+]