3 ###########################################################################
5 # Copyright 2013 BMW Car IT GmbH
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # http://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
19 ###########################################################################
21 import sys, re, string
22 from common_modules.common import *
23 from common_modules.config import G_MAX_INDENTATION_THRESHOLD
24 from common_modules.config import G_TAB_SEQUENCE
26 def get_correct_indentation_list(clean_file_lines):
27 #It is important to keep track of the changes made to the indent level because
28 #curly braces of namespaces do not affect indentation, while curly braces of
29 #other statements like if, for and while increase/decease indentation
30 indent_levels_stack = [0]
35 #indent level for every line
36 indentation_levels = [0] * len(clean_file_lines)
38 #get correct indentation for every line
39 for i in range(len(clean_file_lines)):
40 line = clean_file_lines[i]
42 def get_former_line():
44 Gets the last previous non-empty line
48 for j in range(i - 1, 0 , -1):
49 if len(clean_file_lines[j].strip(" \t\n\f\r")):
50 return clean_file_lines[j]
53 #previous non-empty line
54 former_line = get_former_line()
56 # the next part of the code decreases the indent level
58 # the level of indent decrease when right curly braces are found
61 # if the line begins with a right curly brace then the line ITSELF
62 # gets a decreased indent level
64 # if the line has some content on the same line before the right
65 # curly brace (which is a bad practice, but nevertheless) the line
66 # ITSELF does not get a decreased level of indentation, but the
67 # following lines get a decreased level of indentation
69 #check if the line begins with a right curly brace
70 line_begins_with_right_brace = re.match(r"\s*\}", line) != None
72 #if the line DOES NOT begin with right curly brace then it has the same level
73 #of indentation as the previous line(s)
74 if not line_begins_with_right_brace:
75 indentation_levels[i] = indent_level
77 #this is to decrease the level of indentation in case there is a right curly brace "}"
78 #the level of indentation is decreased by popping elements from the indentation level stack
79 #a loop is to account for cases where there are several braces on same line
80 for _ in range(0, line.count("}") - line.count("{")):
81 indent_level = indent_levels_stack.pop()
83 #if the line begins with right curly brace then it gets a decreased
85 if line_begins_with_right_brace:
86 indentation_levels[i] = indent_level
88 # the next part of the code increases the indent level
90 # indent level increases when a left curly brace is found
92 # an exception to this if the curly brace is the beginning
93 # of a namespace or an extern block, in this case the indent
94 # level does not increase
96 # A flag is used in the very specific case of having several left
97 # curly braces after a namespace/extern block (which is a bad practice)
99 # Initially the flag is False,soo that the first curly brace would not
100 # increase indentation, after that the flag is raised (set to True)
101 # so that every new left curly brace would incraese indentation
104 #increase the level of indentation of there are open left curly braces "{"
105 #a loop is to account for cases where there are several braces on same line
106 for _ in range(0, line.count("{") - line.count("}")):
107 #push the current level of indentation to the stack
108 indent_levels_stack.append(indent_level)
110 #if there is no namesapce or extern declaration before curly brace: increase indentation
111 #that is because namespaces do not increase the level of indentation
112 namespace_or_extern_re_text = r'\s*(namespace|(extern((\s*\S*\s*)?)(\s*)($|\{)))'
113 namespace_or_extern_re = re.compile(namespace_or_extern_re_text)
115 #if the current line contains a namespace/extern declation
116 if re.match(namespace_or_extern_re, line) != None:
117 #increase the level of indentation only if the flag is raised
118 #(if it is NOT the first curly brace on line)
122 #raise the flag (so that next curly braces would increase indentation)
125 #otherwise: if the previous line contains namesapce/extern declaration
126 elif re.match(namespace_or_extern_re, former_line) != None:
127 #the indent level does not increase only if the previous line does not contain
128 #a left curly brace, and if this is the first curly brace on the current line (flag is still False)
129 #if either of the conditions is false it is safe to increase the indent level
130 if re.search(r'(namespace|extern)((?!\{).)*\{', former_line) == None and not flag:
135 #otherwise: just increase indent level
139 return indentation_levels
141 def check_indentation(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
143 Checks if all lines in a file (except multi-line comments, strings and macros) have correct indentation
146 def is_statement_supplement(line, former_line, parentheses_count):
148 Checks if the statement is the completion of a previous statement, like arethmatic expressions broken on several lines, or for loop
149 parts on separate lines. It also includes checks that the previous statement is well-closed (ends by semicolon or curly brace)
151 IMPORTANT: the first line of a for loop (or an arethmatic expression...etc) broken on several lines would still not be considered
152 a statement supplement
155 statement_supplement = False
156 #include statements not preceded by a semicolon in previous line
157 statement_supplement |= former_line != None and re.search(re.compile(r";(\s*)$"), former_line) == None
158 #exclude lines containing block beginnings and ends
159 statement_supplement &= re.search(re.compile(r"(\{|\})(\s*)$"), line) == None
160 #exclude statements preceded by block beginnings and ends
161 statement_supplement &= former_line != None and re.search(re.compile(r"(\{|\})(\s*)$"), former_line) == None
162 #exclude preprocessor directives (they dunt have to end with a ; for example)
163 statement_supplement &= not is_preprocessor_directive(line)
164 #exclude statements preceded by preprocessor directives that do not end with a slash \
165 statement_supplement &= former_line != None and re.match(re.compile(r"(\s*)#((?!\/).)*\/$"), former_line) == None
167 #filter out the case where a line is preceeded by a label statement
168 fl_label_statement = is_label_statement(former_line)
169 statement_supplement &= not fl_label_statement
171 #include statements that are contained inside parentheses
172 statement_supplement |= parentheses_count > 0
174 return statement_supplement
176 def is_macro_body(former_line):
178 Checks if the line belongs to a macro body by checking that the line before it ends with a slash
181 return former_line != None and re.search(r"\\(\s*)$", former_line) != None
183 def is_label_statement(line):
185 Checks if line starts with a label/case definition
188 label_statement = False
190 #case statements -> case id : expr that does not begin with a : (to avoid matching with :: operator)
191 label_statement = re.match(r'(\s*)case(\s*)((?!:).)*(\s*):(?!:)', line) != None
192 #label statements -> label : expr that does not begin with a : (to avoid matching with :: operator)
193 label_statement |= re.match(r'(\s*)(\w+)(\s*):(?!:)', line) != None
194 return label_statement
196 def is_preprocessor_directive(line):
198 Checks if a line is a preprocessor directive
201 #preproccessor directives start with a #
202 return re.match(r"(\s*)#", line) != None
204 #keeps count of open (non-closed) left parentheses
205 parentheses_count = 0
207 #configured tab character
208 tab_character = G_TAB_SEQUENCE[0]
209 tab_character_count = G_TAB_SEQUENCE[1]
211 #get list of correct levels of indentation for every line
212 correct_indent_levels = get_correct_indentation_list(clean_file_lines)
214 #indent level of current line and previous line
218 #check every line for correct indentation
219 for i in range(len(clean_file_lines)):
220 line = clean_file_lines[i]
221 #correct indent level of current line
222 correct_il = correct_indent_levels[i]
224 #actual indent level of the line
227 #get the actual line indentation
228 #this regex searches for the first character in line that is not a space
229 #if no match found this means a zero level of indentation
230 indent_match = re.search("(?!{0})".format(tab_character), line)
232 actual_il = 1.0 * indent_match.start() / tab_character_count
234 def get_former_line():
236 Gets the last previous non-empty line
240 for j in range(i - 1, 0 , -1):
241 if len(clean_file_lines[j].strip(" \t\n\f\r")):
242 return clean_file_lines[j]
245 #previous non-empty line
246 former_line = get_former_line()
248 #make several checks about the line
249 statement_supplement = is_statement_supplement(line, former_line, parentheses_count)
250 label_statement = is_label_statement(line)
251 fl_label_statement = is_label_statement(former_line)
252 preprocessor_directive = is_preprocessor_directive(line)
253 macro_body = is_macro_body(former_line)
255 #if at non-empty line: check if indentation is correct
256 if len(line.strip(" \t\n\r")) > 0:
258 #do not check anything, just ignore line
260 elif preprocessor_directive:
261 #preproccessor directives must have 0 level indentation
263 log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" "))
264 elif label_statement:
265 #label statements must have one level less indentation than the normal indentation level
266 if actual_il != correct_il - 1:
267 log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" "))
268 elif (not statement_supplement) and actual_il != correct_il:
269 #a complete statement (NON supplement statement) must be at the normal level of indentation
270 log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" "))
271 elif statement_supplement and actual_il < correct_il:
272 #statement supplements must have a level of indentation at least equal to the normal level of indentation
273 log_warning(filename, i + 1, "incorrect indentation", file_lines[i].strip(" "))
275 #warning if indentation level is too much
276 if correct_il == G_MAX_INDENTATION_THRESHOLD + 1 and previous_il < correct_il:
277 log_warning(filename, i + 1, "code block has too much indentation [maximum level of indentation is {0}]".format(G_MAX_INDENTATION_THRESHOLD),)
279 #update open parentheses count
280 parentheses_count += line.count("(") - line.count(")")
282 #update previous indent level
283 previous_il = correct_il
286 if __name__ == "__main__":
287 targets = sys.argv[1:]
288 targets = get_all_files(targets)
290 if len(targets) == 0:
292 \t**** No input provided ****
293 \tTakes a list of files/directories as input and performs specific style checking on all files/directories
295 \tGives warnings if lines (except multi-line comments and macros) do not have correct indentation.
296 \tIt also checks if code blocks have too much indentation (too deep). Code should have a maximum of {0} indentation levels.
297 """.format(G_MAX_INDENTATION_THRESHOLD)
301 if t[-2:] == ".h" or t[-4:] == ".cpp" or t[-2] == ".c":
302 file_contents, clean_file_contents, file_lines, clean_file_lines = read_file(t)
303 check_indentation(t, file_contents, clean_file_contents, file_lines, clean_file_lines)