Merge "Build and package Layer Management service binaries." into tizen
[profile/ivi/layer-management.git] / scripts / check_indentation.py
1 #!/usr/bin/python
2
3 ###########################################################################
4 #
5 # Copyright 2013 BMW Car IT GmbH
6 #
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
10 #
11 #        http://www.apache.org/licenses/LICENSE-2.0
12 #
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.
18 #
19 ###########################################################################
20
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
25
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]
31
32     #current indent level
33     indent_level = 0
34
35     #indent level for every line
36     indentation_levels = [0] * len(clean_file_lines)
37
38     #get correct indentation for every line
39     for i in range(len(clean_file_lines)):
40         line = clean_file_lines[i]
41
42         def get_former_line():
43             """
44             Gets the last previous non-empty line
45
46             """
47             if i > 0:
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]
51             return ""
52
53         #previous non-empty line
54         former_line = get_former_line()
55
56         # the next part of the code decreases the indent level
57         #
58         # the level of indent decrease when right curly braces are found
59         # in the line.
60         #
61         # if the line begins with a right curly brace then the line ITSELF
62         # gets a decreased indent level
63         #
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
68
69         #check if the line begins with a right curly brace
70         line_begins_with_right_brace = re.match(r"\s*\}", line) != None
71
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
76
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()
82
83         #if the line begins with right curly brace then it gets a decreased
84         #level of indentation
85         if line_begins_with_right_brace:
86             indentation_levels[i] = indent_level
87
88         # the next part of the code increases the indent level
89         #
90         # indent level increases when a left curly brace is found
91         #
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
95
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)
98         #
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
102         flag = False
103
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)
109
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)
114
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)
119                 if flag:
120                     indent_level += 1
121                 else:
122                     #raise the flag (so that next curly braces would increase indentation)
123                     flag = True
124
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:
131                     flag = True
132                 else:
133                     indent_level += 1
134
135             #otherwise: just increase indent level
136             else:
137                 indent_level += 1
138
139     return indentation_levels
140
141 def check_indentation(filename, file_contents, clean_file_contents, file_lines, clean_file_lines):
142     """
143     Checks if all lines in a file (except multi-line comments, strings and macros) have correct indentation
144
145     """
146     def is_statement_supplement(line, former_line, parentheses_count):
147         """
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)
150
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
153
154         """
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
166
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
170
171         #include statements that are contained inside parentheses
172         statement_supplement |= parentheses_count > 0
173
174         return statement_supplement
175
176     def is_macro_body(former_line):
177         """
178         Checks if the line belongs to a macro body by checking that the line before it ends with a slash
179
180         """
181         return former_line != None and re.search(r"\\(\s*)$", former_line) != None
182
183     def is_label_statement(line):
184         """
185         Checks if line starts with a label/case definition
186
187         """
188         label_statement = False
189         if line != None:
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
195
196     def is_preprocessor_directive(line):
197         """
198         Checks if a line is a preprocessor directive
199
200         """
201         #preproccessor directives start with a #
202         return re.match(r"(\s*)#", line) != None
203
204     #keeps count of open (non-closed) left parentheses
205     parentheses_count = 0
206
207     #configured tab character
208     tab_character = G_TAB_SEQUENCE[0]
209     tab_character_count = G_TAB_SEQUENCE[1]
210
211     #get list of correct levels of indentation for every line
212     correct_indent_levels = get_correct_indentation_list(clean_file_lines)
213
214     #indent level of current line and previous line
215     correct_il = 0
216     previous_il = 0
217
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]
223
224         #actual indent level of the line
225         actual_il = 0
226
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)
231         if indent_match:
232             actual_il = 1.0 * indent_match.start() / tab_character_count
233
234         def get_former_line():
235             """
236             Gets the last previous non-empty line
237
238             """
239             if i > 0:
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]
243             return ""
244
245         #previous non-empty line
246         former_line = get_former_line()
247
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)
254
255         #if at non-empty line: check if indentation is correct
256         if len(line.strip(" \t\n\r")) > 0:
257             if macro_body:
258                 #do not check anything, just ignore line
259                 None
260             elif preprocessor_directive:
261                 #preproccessor directives must have 0 level indentation
262                 if actual_il != 0:
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(" "))
274
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),)
278
279         #update open parentheses count
280         parentheses_count += line.count("(") - line.count(")")
281
282         #update previous indent level
283         previous_il = correct_il
284
285
286 if __name__ == "__main__":
287     targets = sys.argv[1:]
288     targets = get_all_files(targets)
289
290     if len(targets) == 0:
291         print """
292 \t**** No input provided ****
293 \tTakes a list of files/directories as input and performs specific style checking on all files/directories
294
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)
298         exit(0)
299
300     for t in targets:
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)