summaryrefslogtreecommitdiff
path: root/tools/cstyle.py
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2015-05-09 13:40:35 -0400
committerPaul Davis <paul@linuxaudiosystems.com>2015-05-09 13:42:39 -0400
commitd6a6444b90576c5d8149c74725eae3f4c7737571 (patch)
treec0425cb21f5a83814c805d7151bc6aa573077aaa /tools/cstyle.py
parent13f34f392248e6457d1afed010fcdcbfda890328 (diff)
C/C++ style checking script from erik de castro-lopo, for use in client-side pre-commit hook
Diffstat (limited to 'tools/cstyle.py')
-rw-r--r--tools/cstyle.py245
1 files changed, 245 insertions, 0 deletions
diff --git a/tools/cstyle.py b/tools/cstyle.py
new file mode 100644
index 0000000000..f78c089724
--- /dev/null
+++ b/tools/cstyle.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python -tt
+#
+# Copyright (C) 2005-2012 Erik de Castro Lopo <erikd@mega-nerd.com>
+#
+# Released under the 2 clause BSD license.
+
+"""
+This program checks C code for compliance to coding standards used in
+libsndfile and other projects I run.
+"""
+
+import re
+import sys
+
+
+class Preprocessor:
+ """
+ Preprocess lines of C code to make it easier for the CStyleChecker class to
+ test for correctness. Preprocessing works on a single line at a time but
+ maintains state between consecutive lines so it can preprocessess multi-line
+ comments.
+ Preprocessing involves:
+ - Strip C++ style comments from a line.
+ - Strip C comments from a series of lines. When a C comment starts and
+ ends on the same line it will be replaced with 'comment'.
+ - Replace arbitrary C strings with the zero length string.
+ - Replace '#define f(x)' with '#define f (c)' (The C #define requires that
+ there be no space between defined macro name and the open paren of the
+ argument list).
+ Used by the CStyleChecker class.
+ """
+ def __init__ (self):
+ self.comment_nest = 0
+ self.leading_space_re = re.compile ('^(\t+| )')
+ self.trailing_space_re = re.compile ('(\t+| )$')
+ self.define_hack_re = re.compile ("(#\s*define\s+[a-zA-Z0-9_]+)\(")
+
+ def comment_nesting (self):
+ """
+ Return the currect comment nesting. At the start and end of the file,
+ this value should be zero. Inside C comments it should be 1 or
+ (possibly) more.
+ """
+ return self.comment_nest
+
+ def __call__ (self, line):
+ """
+ Strip the provided line of C and C++ comments. Stripping of multi-line
+ C comments works as expected.
+ """
+
+ line = self.define_hack_re.sub (r'\1 (', line)
+
+ line = self.process_strings (line)
+
+ # Strip C++ style comments.
+ if self.comment_nest == 0:
+ line = re.sub ("( |\t*)//.*", '', line)
+
+ # Strip C style comments.
+ open_comment = line.find ('/*')
+ close_comment = line.find ('*/')
+
+ if self.comment_nest > 0 and close_comment < 0:
+ # Inside a comment block that does not close on this line.
+ return ""
+
+ if open_comment >= 0 and close_comment < 0:
+ # A comment begins on this line but doesn't close on this line.
+ self.comment_nest += 1
+ return self.trailing_space_re.sub ('', line [:open_comment])
+
+ if open_comment < 0 and close_comment >= 0:
+ # Currently open comment ends on this line.
+ self.comment_nest -= 1
+ return self.trailing_space_re.sub ('', line [close_comment + 2:])
+
+ if open_comment >= 0 and close_comment > 0 and self.comment_nest == 0:
+ # Comment begins and ends on this line. Replace it with 'comment'
+ # so we don't need to check whitespace before and after the comment
+ # we're removing.
+ newline = line [:open_comment] + "comment" + line [close_comment + 2:]
+ return self.__call__ (newline)
+
+ return line
+
+ def process_strings (self, line):
+ """
+ Given a line of C code, return a string where all literal C strings have
+ been replaced with the empty string literal "".
+ """
+ for k in range (0, len (line)):
+ if line [k] == '"':
+ start = k
+ for k in range (start + 1, len (line)):
+ if line [k] == '"' and line [k - 1] != '\\':
+ return line [:start + 1] + '"' + self.process_strings (line [k + 1:])
+ return line
+
+
+class CStyleChecker:
+ """
+ A class for checking the whitespace and layout of a C code.
+ """
+ def __init__ (self, debug):
+ self.debug = debug
+ self.filename = None
+ self.error_count = 0
+ self.line_num = 1
+ self.orig_line = ''
+ self.trailing_newline_re = re.compile ('[\r\n]+$')
+ self.indent_re = re.compile ("^\s*")
+ self.last_line_indent = ""
+ self.last_line_indent_curly = False
+ self.re_checks = \
+ [ ( re.compile ("^ "), "leading space as indentation instead of tab - use tabs to indent, spaces to align" )
+ , ( re.compile ("{[^\s]"), "missing space after open brace" )
+ , ( re.compile ("[^\s]}"), "missing space before close brace" )
+ , ( re.compile ("[ \t]+$"), "contains trailing whitespace" )
+
+ , ( re.compile (",[^\s\n]"), "missing space after comma" )
+ , ( re.compile (";[a-zA-Z0-9]"), "missing space after semi-colon" )
+ , ( re.compile ("=[^\s\"'=]"), "missing space after assignment" )
+
+ # Open and close parenthesis.
+ , ( re.compile ("[^\s\(\[\*&']\("), "missing space before open parenthesis" )
+ , ( re.compile ("\)(-[^>]|[^,'\s\n\)\]-])"), "missing space after close parenthesis" )
+ , ( re.compile ("\( [^;]"), "space after open parenthesis" )
+ , ( re.compile ("[^;] \)"), "space before close parenthesis" )
+
+ # Open and close square brace.
+ , ( re.compile ("\[ "), "space after open square brace" )
+ , ( re.compile (" \]"), "space before close square brace" )
+
+ # Space around operators.
+ , ( re.compile ("[^\s][\*/%+-][=][^\s]"), "missing space around opassign" )
+ , ( re.compile ("[^\s][<>!=^/][=]{1,2}[^\s]"), "missing space around comparison" )
+
+ # Parens around single argument to return.
+ , ( re.compile ("\s+return\s+\([a-zA-Z0-9_]+\)\s+;"), "parens around return value" )
+ ]
+
+
+ def get_error_count (self):
+ """
+ Return the current error count for this CStyleChecker object.
+ """
+ return self.error_count
+
+ def check_files (self, files):
+ """
+ Run the style checker on all the specified files.
+ """
+ for filename in files:
+ self.check_file (filename)
+
+ def check_file (self, filename):
+ """
+ Run the style checker on the specified file.
+ """
+ self.filename = filename
+ try:
+ cfile = open (filename, "r")
+ except IOError as e:
+ return
+
+ self.line_num = 1
+
+ preprocess = Preprocessor ()
+ while 1:
+ line = cfile.readline ()
+ if not line:
+ break
+
+ line = self.trailing_newline_re.sub ('', line)
+ self.orig_line = line
+
+ self.line_checks (preprocess (line))
+
+ self.line_num += 1
+
+ cfile.close ()
+ self.filename = None
+
+ # Check for errors finding comments.
+ if preprocess.comment_nesting () != 0:
+ print ("Weird, comments nested incorrectly.")
+ sys.exit (1)
+
+ return
+
+ def line_checks (self, line):
+ """
+ Run the style checker on provided line of text, but within the context
+ of how the line fits within the file.
+ """
+
+ indent = len (self.indent_re.search (line).group ())
+ if re.search ("^\s+}", line):
+ if not self.last_line_indent_curly and indent != self.last_line_indent:
+ None # self.error ("bad indent on close curly brace")
+ self.last_line_indent_curly = True
+ else:
+ self.last_line_indent_curly = False
+
+ # Now all the regex checks.
+ for (check_re, msg) in self.re_checks:
+ if check_re.search (line):
+ self.error (msg)
+
+ if re.search ("[a-zA-Z0-9][<>!=^/&\|]{1,2}[a-zA-Z0-9]", line):
+ if not re.search (".*#include.*[a-zA-Z0-9]/[a-zA-Z]", line):
+ self.error ("missing space around operator")
+
+ self.last_line_indent = indent
+ return
+
+ def error (self, msg):
+ """
+ Print an error message and increment the error count.
+ """
+ print ("%s (%d) : %s" % (self.filename, self.line_num, msg))
+ if self.debug:
+ print ("'" + self.orig_line + "'")
+ self.error_count += 1
+
+#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
+
+if len (sys.argv) < 1:
+ print ("Usage : yada yada")
+ sys.exit (1)
+
+# Create a new CStyleChecker object
+if sys.argv [1] == '-d' or sys.argv [1] == '--debug':
+ cstyle = CStyleChecker (True)
+ cstyle.check_files (sys.argv [2:])
+else:
+ cstyle = CStyleChecker (False)
+ cstyle.check_files (sys.argv [1:])
+
+
+if cstyle.get_error_count ():
+ sys.exit (1)
+
+sys.exit (0)