replace ::cast_dynamic() with relevant ActionManager::get_*_action() calls
[ardour.git] / tools / cstyle.py
1 #!/usr/bin/python -tt
2 #
3 # Copyright (C) 2005-2012 Erik de Castro Lopo <erikd@mega-nerd.com>
4 #
5 # Released under the 2 clause BSD license.
6
7 """
8 This program checks C code for compliance to coding standards used in
9 libsndfile and other projects I run.
10 """
11
12 import re
13 import sys
14
15
16 class Preprocessor:
17         """
18         Preprocess lines of C code to make it easier for the CStyleChecker class to
19         test for correctness. Preprocessing works on a single line at a time but
20         maintains state between consecutive lines so it can preprocessess multi-line
21         comments.
22         Preprocessing involves:
23           - Strip C++ style comments from a line.
24           - Strip C comments from a series of lines. When a C comment starts and
25             ends on the same line it will be replaced with 'comment'.
26           - Replace arbitrary C strings with the zero length string.
27           - Replace '#define f(x)' with '#define f (c)' (The C #define requires that
28             there be no space between defined macro name and the open paren of the
29             argument list).
30         Used by the CStyleChecker class.
31         """
32         def __init__ (self):
33                 self.comment_nest = 0
34                 self.leading_space_re = re.compile ('^(\t+| )')
35                 self.trailing_space_re = re.compile ('(\t+| )$')
36                 self.define_hack_re = re.compile ("(#\s*define\s+[a-zA-Z0-9_]+)\(")
37
38         def comment_nesting (self):
39                 """
40                 Return the currect comment nesting. At the start and end of the file,
41                 this value should be zero. Inside C comments it should be 1 or
42                 (possibly) more.
43                 """
44                 return self.comment_nest
45
46         def __call__ (self, line):
47                 """
48                 Strip the provided line of C and C++ comments. Stripping of multi-line
49                 C comments works as expected.
50                 """
51
52                 line = self.define_hack_re.sub (r'\1 (', line)
53
54                 line = self.process_strings (line)
55
56                 # Strip C++ style comments.
57                 if self.comment_nest == 0:
58                         line = re.sub ("( |\t*)//.*", '', line)
59
60                 # Strip C style comments.
61                 open_comment = line.find ('/*')
62                 close_comment = line.find ('*/')
63
64                 if self.comment_nest > 0 and close_comment < 0:
65                         # Inside a comment block that does not close on this line.
66                         return ""
67
68                 if open_comment >= 0 and close_comment < 0:
69                         # A comment begins on this line but doesn't close on this line.
70                         self.comment_nest += 1
71                         return self.trailing_space_re.sub ('', line [:open_comment])
72
73                 if open_comment < 0 and close_comment >= 0:
74                         # Currently open comment ends on this line.
75                         self.comment_nest -= 1
76                         return self.trailing_space_re.sub ('', line [close_comment + 2:])
77
78                 if open_comment >= 0 and close_comment > 0 and self.comment_nest == 0:
79                         # Comment begins and ends on this line. Replace it with 'comment'
80                         # so we don't need to check whitespace before and after the comment
81                         # we're removing.
82                         newline = line [:open_comment] + "comment" + line [close_comment + 2:]
83                         return self.__call__ (newline)
84
85                 return line
86
87         def process_strings (self, line):
88                 """
89                 Given a line of C code, return a string where all literal C strings have
90                 been replaced with the empty string literal "".
91                 """
92                 for k in range (0, len (line)):
93                         if line [k] == '"':
94                                 start = k
95                                 for k in range (start + 1, len (line)):
96                                         if line [k] == '"' and line [k - 1] != '\\':
97                                                 return line [:start + 1] + '"' + self.process_strings (line [k + 1:])
98                 return line
99
100
101 class CStyleChecker:
102         """
103         A class for checking the whitespace and layout of a C code.
104         """
105         def __init__ (self, debug):
106                 self.debug = debug
107                 self.filename = None
108                 self.error_count = 0
109                 self.line_num = 1
110                 self.orig_line = ''
111                 self.trailing_newline_re = re.compile ('[\r\n]+$')
112                 self.indent_re = re.compile ("^\s*")
113                 self.last_line_indent = ""
114                 self.last_line_indent_curly = False
115                 self.error_checks = \
116                         [ ( re.compile ("^ "),          "leading space as indentation instead of tab - use tabs to indent, spaces to align" )
117                         ]                                                                                                                                       
118                 self.warning_checks = \
119                         [   ( re.compile ("{[^\s]"),      "missing space after open brace" )
120                           , ( re.compile ("[^\s]}"),      "missing space before close brace" )
121                           , ( re.compile ("^[ \t]+$"),     "empty line contains whitespace" )
122                           , ( re.compile ("[^\s][ \t]+$"),     "contains trailing whitespace" )
123
124                           , ( re.compile (",[^\s\n]"),            "missing space after comma" )
125                           , ( re.compile (";[a-zA-Z0-9]"),        "missing space after semi-colon" )
126                           , ( re.compile ("=[^\s\"'=]"),          "missing space after assignment" )
127                           
128                           # Open and close parenthesis.
129                           , ( re.compile ("[^_\s\(\[\*&']\("),             "missing space before open parenthesis" )
130                           , ( re.compile ("\)(-[^>]|[^;,'\s\n\)\]-])"),    "missing space after close parenthesis" )
131                           , ( re.compile ("\( [^;]"),                     "space after open parenthesis" )
132                           , ( re.compile ("[^;] \)"),                     "space before close parenthesis" )
133
134                           # Open and close square brace.
135                           , ( re.compile ("\[ "),                                 "space after open square brace" )
136                           , ( re.compile (" \]"),                                 "space before close square brace" )
137
138                           # Space around operators.
139                           , ( re.compile ("[^\s][\*/%+-][=][^\s]"),               "missing space around opassign" )
140                           , ( re.compile ("[^\s][<>!=^/][=]{1,2}[^\s]"),  "missing space around comparison" )
141
142                           # Parens around single argument to return.
143                           , ( re.compile ("\s+return\s+\([a-zA-Z0-9_]+\)\s+;"),   "parens around return value" )
144                         ]                                                                                                                                       
145
146                 
147
148         def get_error_count (self):
149                 """
150                 Return the current error count for this CStyleChecker object.
151                 """
152                 return self.error_count
153
154         def check_files (self, files):
155                 """
156                 Run the style checker on all the specified files.
157                 """
158                 for filename in files:
159                         self.check_file (filename)
160
161         def check_file (self, filename):
162                 """
163                 Run the style checker on the specified file.
164                 """
165                 self.filename = filename
166                 try:
167                         cfile = open (filename, "r")
168                 except IOError as e:
169                         return
170
171                 self.line_num = 1
172
173                 preprocess = Preprocessor ()
174                 while 1:
175                         line = cfile.readline ()
176                         if not line:
177                                 break
178
179                         line = self.trailing_newline_re.sub ('', line)
180                         self.orig_line = line
181
182                         self.line_checks (preprocess (line))
183
184                         self.line_num += 1
185
186                 cfile.close ()
187                 self.filename = None
188
189                 # Check for errors finding comments.
190                 if preprocess.comment_nesting () != 0:
191                         print ("Weird, comments nested incorrectly.")
192                         sys.exit (1)
193
194                 return
195
196         def line_checks (self, line):
197                 """
198                 Run the style checker on provided line of text, but within the context
199                 of how the line fits within the file.
200                 """
201
202                 indent = len (self.indent_re.search (line).group ())
203                 if re.search ("^\s+}", line):
204                         if not self.last_line_indent_curly and indent != self.last_line_indent:
205                                 None    # self.error ("bad indent on close curly brace")
206                         self.last_line_indent_curly = True
207                 else:
208                         self.last_line_indent_curly = False
209
210                 # Now all the stylistic warnings regex checks.
211                 for (check_re, msg) in self.warning_checks:
212                         if check_re.search (line):
213                                 self.warning (msg)
214
215                 # Now all the stylistic error regex checks.
216                 for (check_re, msg) in self.error_checks:
217                         if check_re.search (line):
218                                 self.error (msg)
219
220                                 
221                 if re.search ("[a-zA-Z0-9_][<>!=^/&\|]{1,2}[a-zA-Z0-9_]", line):
222                         # ignore #include <foo.h> and C++ templates with indirection/pointer/reference operators
223                         if not re.search (".*#include.*[a-zA-Z0-9]/[a-zA-Z]", line) and not re.search ("[a-zA-Z0-9_]>[&\*]*\s", line):
224                                 self.error ("missing space around operator")
225
226                 self.last_line_indent = indent
227                 return
228
229         def error (self, msg):
230                 """
231                 Print an error message and increment the error count.
232                 """
233                 print ("%s (%d) : STYLE ERROR %s" % (self.filename, self.line_num, msg))
234                 if self.debug:
235                         print ("'" + self.orig_line + "'")
236                 self.error_count += 1
237
238         def warning (self, msg):
239                 """
240                 Print a warning message and increment the error count.
241                 """
242                 print ("%s (%d) : STYLE WARNING %s" % (self.filename, self.line_num, msg))
243                 if self.debug:
244                         print ("'" + self.orig_line + "'")
245                 
246 #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
247
248 if len (sys.argv) < 1:
249         print ("Usage : yada yada")
250         sys.exit (1)
251
252 # Create a new CStyleChecker object
253 if sys.argv [1] == '-d' or sys.argv [1] == '--debug':
254         cstyle = CStyleChecker (True)
255         cstyle.check_files (sys.argv [2:])
256 else:
257         cstyle = CStyleChecker (False)
258         cstyle.check_files (sys.argv [1:])
259
260
261 if cstyle.get_error_count ():
262         sys.exit (1)
263
264 sys.exit (0)