svn commit: r334355 - in user/asomers: . style9
Alan Somers
asomers at FreeBSD.org
Tue May 29 21:37:04 UTC 2018
Author: asomers
Date: Tue May 29 21:37:02 2018
New Revision: 334355
URL: https://svnweb.freebsd.org/changeset/base/334355
Log:
user/asomers/style9: automatic style verifier
This python script can automatically detect many common style(9) violations.
Sponsored by: Spectra Logic Corp
Added:
user/asomers/
user/asomers/style9/
user/asomers/style9/badstyle.c (contents, props changed)
user/asomers/style9/style9.py (contents, props changed)
Added: user/asomers/style9/badstyle.c
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ user/asomers/style9/badstyle.c Tue May 29 21:37:02 2018 (r334355)
@@ -0,0 +1,73 @@
+/*
+ * this is a comment
+ * with text on the last line */
+
+/* func on same line as type; OK in a prototype */
+int foo();
+
+
+/* func on same line as type */
+int foo(){
+}
+static const struct blargle *myfunc(int x, char *y, struct foo *f){
+}
+/* this one is really long */
+static int zfs_close(vnode_t *vp, int flag, int count, offset_t offset,
+ cred_t *cr, caller_context_t *ct)
+
+/* Omit the space after some keywords */
+int
+foo(){
+ if(1) {
+ while(7){
+ if (foo){
+ }
+ }
+ }
+}
+
+// A C++ style comment
+int foo; //C++ style inline comment
+
+
+/* Solo brace after control block */
+long
+bar(){
+ if (x)
+ {
+ 1;
+ }
+}
+
+/* Empty loop with space before the semicolon */
+float
+baz()
+{
+ for (i=0; i<10; i++) ;
+}
+
+
+/* bad inline function */
+inline static int zero(void);
+
+/* long control block without braces around single statement body */
+if (this
+ && that
+ && the other)
+ do_stuff();
+
+
+/* Return statement without parens around the argument */
+int
+foo()
+{
+ return 0;
+}
+
+/* Void return statement without parens. This is ok */
+void
+voidfoo()
+{
+ return ;
+}
+
Added: user/asomers/style9/style9.py
==============================================================================
--- /dev/null 00:00:00 1970 (empty, because file is newly added)
+++ user/asomers/style9/style9.py Tue May 29 21:37:02 2018 (r334355)
@@ -0,0 +1,352 @@
+#! /usr/bin/env python
+import optparse
+import re
+import string
+import sys
+
+class Line(str):
+ """String that carries a line number with it"""
+ @property
+ def lineNumber(self):
+ "Line number where this line first occurred in the file"
+ try:
+ return self._lineNumber
+ except AttributeError:
+ return -1
+
+ @lineNumber.setter
+ def lineNumber(self, value):
+ self._lineNumber = value
+
+
+class FileScanner(object):
+ """Scans a file for a regex"""
+ antipattern = ''
+ strip_comments = False
+ flags = 0
+ def __init__(self):
+ self._reobj = re.compile(self.pattern, self.flags)
+ if self.antipattern:
+ self.__antipattern_obj = re.compile(self.antipattern, self.flags)
+ self.__comment_reobj = re.compile(
+ r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
+ re.DOTALL | re.MULTILINE
+ )
+
+ def _readlines(self, filename):
+ """Return an iterable that will yield each line in file, stripping
+ comments if self.strip_comments is set"""
+ f = open(filename, 'r')
+ if self.strip_comments:
+ reader = self._strip_comments(f.read()).split('\n')
+ else:
+ reader = f
+ n = 1
+ for l in reader:
+ line = Line(l)
+ line.lineNumber = n
+ yield line
+ n += 1
+
+ def __replacer(self, match):
+ s = match.group(0)
+ if s.startswith('/'):
+ return "" + "\n" * s.count('\n')
+ else:
+ return s
+
+ def _strip_comments(self, text):
+ #Thanks to MizardX on stackoverflow.com
+ return re.sub(self.__comment_reobj, self.__replacer, text)
+
+ def finditer(self, filename):
+ """Returns a generator yielding every matching line in the file"""
+ f = self._readlines(filename)
+ for line in f:
+ mo = self._reobj.search(line)
+ if mo:
+ if not self.antipattern or not self.__antipattern_obj.search(line):
+ yield line
+
+ def has_match(self, filename):
+ """Returns True iff at least one line in the file matches the pattern"""
+ f = self._readlines(filename)
+ for line in f:
+ mo = self._reobj.search(line)
+ if mo:
+ if not self.antipattern or not self.__antipattern_obj.search(line):
+ return True
+ break
+ return False
+
+
+class Attribute(FileScanner):
+ """Use of __attribute__ outside of compiler-specific macro"""
+ pattern = '__attribute__'
+
+
+class BraceSolo(FileScanner):
+ """Puts the opening brace of a control statement on its own line"""
+ strip_comments = True
+ pattern = r'^\s+{\s*$'
+
+
+class BraceWithoutSpace(FileScanner):
+ """Omits a space before the { of a control block"""
+ strip_comments = True
+ pattern = r'[^a-zA-Z0-9_](if|while|for|switch)(\(|[^a-zA-Z0-9_;(]\S*\s*\().*\){'
+
+
+class CppComment(FileScanner):
+ """C++ style comments"""
+ # Assume that if the first non-whitespace character of a line is '*', then
+ # the line is probably a continuation of a C-style comment
+ pattern = r'//'
+ antipattern = r'^\s*/?\*'
+
+
+class EmptyLoopSemicolon(FileScanner):
+ """Puts space before the ; of a loop with no body"""
+ strip_comments = True
+ pattern = r'[^a-zA-Z0-9_](while|for)\s*\(.*\)\s+;'
+
+
+class FuncInDeclaration(FileScanner):
+ """Calls a function in a variable declaration"""
+ strip_comments = True
+ pattern = r'^(\s+[a-zA-Z0-9_]+){2,}\s*=.*[a-zA-Z0-9_]\('
+
+
+class FuncOnSameLine(FileScanner):
+ """Function names on the same line as their return types in a definition"""
+ strip_comments = True
+ flags = re.MULTILINE | re.DOTALL
+ pattern = r'^[a-zA-Z0-9_][a-zA-Z0-9_ \t\r\f\v*]+[ \t\r\f\v]+\*?[a-zA-Z0-9_]+\([^)]*\)[{\t\r\f\v]*$'
+
+ def finditer(self, filename):
+ """Returns a generator yielding every matching line in the file"""
+ file_as_str = self._strip_comments(open(filename, 'r').read())
+ for mo in re.finditer(self._reobj, file_as_str):
+ line = Line(mo.group(0))
+ line.lineNumber = file_as_str[0:mo.start(0)].count('\n') + 1
+ yield line
+
+ def has_match(self, filename):
+ """Returns True iff at least one line in the file matches the pattern"""
+ file_as_str = self._strip_comments(open(filename, 'r').read())
+ mo = self._reobj.search(file_as_str)
+ if mo:
+ return True
+ else:
+ return False
+
+
+class InlineStatic(FileScanner):
+ """Uses "inline static" instead of "static inline" """
+ strip_comments = True
+ pattern = r'inline static'
+
+
+class LongLines(FileScanner):
+ """Lines over 80 characters"""
+ def __init__(self):
+ pass
+
+ def has_match(self, filename):
+ """Returns True iff at least one line in the file matches the pattern"""
+ f = open(filename, 'r')
+ for line in f:
+ expanded_line = string.expandtabs(line, 8) #re.sub('\t', ' ', line)
+ if len(expanded_line) > 81: #allow one character for the newline
+ return True
+ break
+ return False
+
+ def finditer(self, filename):
+ """Returns a generator yielding every matching line in the file"""
+ for line in self._readlines(filename):
+ expanded_line = string.expandtabs(line, 8) #re.sub('\t', ' ', line)
+ if len(expanded_line) > 81: #allow one character for the newline
+ yield line
+
+
+class MultilineFirst(FileScanner):
+ """Multiline comments with text on the first line"""
+ pattern = r'/\*.*\S+.*[^/\n]$'
+
+
+class MultilineLast(FileScanner):
+ """Multiline comments with text on the last line"""
+ pattern = r'[a-zA-Z0-9_]+.*\*/'
+ antipattern = r'/\*'
+
+
+class ReturnParens(FileScanner):
+ """Return statements must enclose their values in parenthesis"""
+ strip_comments = True
+ pattern = r'[^a-zA-Z0-9_]return\s*[^(; \t\n]'
+
+class RequireBraces(FileScanner):
+ """Always uses braces after a control statement"""
+ strip_comments = True
+ pattern = '^((.*\s+((if|else|elseif|for|do|switch))|([^}]*\s+while))\s*\(.*)$'
+
+ def finditer(self, filename):
+ f = self._readlines(filename)
+ for line in f:
+ ctlStart = re.search(self.pattern, line)
+ if ctlStart:
+ curLine = ctlStart.group(0)
+ lParen = curLine.count('(')
+ rParen = curLine.count(')')
+ while lParen != rParen:
+ curLine = f.next()
+ lParen += curLine.count('(')
+ rParen += curLine.count(')')
+ if not '{' in curLine:
+ yield line
+
+ def has_match(self, filename):
+ while self.finditer(filename):
+ return True
+ return False
+
+
+class SpaceAfterKeyword(FileScanner):
+ """Omit a space after flow control keywords"""
+ strip_comments = True
+ pattern = r'[^a-zA-Z0-9_](if|while|for|return|switch|case)[^a-zA-Z0-9_ \t\r\f\v;]'
+
+
+class TrailingWhitespace(FileScanner):
+ """Trailing whitespace"""
+ pattern = r'[ \t\r\f\v]$'
+
+
+class UnnecessaryBraces(FileScanner):
+ """ Braces around a single line statement following a control block """
+ pattern = '\s+(if|else|elseif|for|while|do|switch)\s*\(.*{'
+ ENDPATTERN = '^\s+}'
+
+ def __init__(self):
+ self.strip_comments = True
+ self.ctlBlockLinesStack = []
+ self.ctlBlockLines = 0
+ super(UnnecessaryBraces, self).__init__()
+
+ def finditer(self, filename):
+ f = self._readlines(filename)
+ for line in f:
+ if re.search(self.pattern, line):
+ # Started a new level of control block
+ self.ctlBlockLinesStack.append(self.ctlBlockLines)
+ self.ctlBlockLines = 0
+
+ elif re.search(self.ENDPATTERN, line):
+ # Ended a control block
+ if self.ctlBlockLines == 1:
+ yield line
+ if self.ctlBlockLinesStack:
+ self.ctlBlockLines += self.ctlBlockLinesStack.pop()
+ else:
+ # Just a regular line
+ self.ctlBlockLines += 1
+
+ def has_match(self, filename):
+ while self.finditer(filename):
+ return True
+ return False
+
+def main():
+ usage = "usage: %prog [OPTIONS] FILE [FILE ...]"
+ parser = optparse.OptionParser(usage)
+ parser.add_option('-a', '--all', action="store_true", default=False,
+ help="Run all checks");
+ parser.add_option('-v', '--verbose', action="store_true", default=False,
+ help="Print matching lines as well as filenames");
+
+ parser.add_option('--attribute', action="store_true", default=False,
+ help="Check for " + Attribute.__doc__);
+ parser.add_option('--brace-solo', action="store_true", default=False,
+ help="Check for " + BraceSolo.__doc__);
+ parser.add_option('--brace-without-space', action="store_true",
+ default=False, help="Check for " + BraceWithoutSpace.__doc__);
+ parser.add_option('--cpp-comments', action="store_true", default=False,
+ help="Check for " + CppComment.__doc__);
+ parser.add_option('--empty-loop-semicolon', action="store_true",
+ default=False, help="Check for " + EmptyLoopSemicolon.__doc__);
+ parser.add_option('--func-in-declaration', action="store_true",
+ default=False, help="Check for " + FuncInDeclaration.__doc__);
+ parser.add_option('--func-on-same-line', action="store_true",
+ default=False, help="Check for " + FuncOnSameLine.__doc__);
+ parser.add_option('--inline-static', action="store_true", default=False,
+ help="Check for " + InlineStatic.__doc__);
+ parser.add_option('--long-lines', action="store_true", default=False,
+ help="Check for " + LongLines.__doc__);
+ parser.add_option('--multiline-first', action="store_true", default=False,
+ help="Check for " + MultilineFirst.__doc__);
+ parser.add_option('--multiline-last', action="store_true", default=False,
+ help="Check for " + MultilineLast.__doc__);
+ parser.add_option('--return-parens', action="store_true",
+ default=False, help="Check for " + ReturnParens.__doc__);
+ parser.add_option('--space-after-keyword', action="store_true",
+ default=False, help="Check for " + SpaceAfterKeyword.__doc__);
+ parser.add_option('--trailing-whitespace', action="store_true",
+ default=False, help="Check for " + TrailingWhitespace.__doc__);
+ parser.add_option('--unnecessary-braces', action="store_true",
+ default=False, help="Check for " + UnnecessaryBraces.__doc__);
+ parser.add_option('--require-braces', action="store_true",
+ default=False, help="Check for " + RequireBraces.__doc__);
+
+ (options, args) = parser.parse_args()
+ if len(args) < 1:
+ parser.error("Incorrect number of arguments")
+
+ checks = []
+ if options.all or options.attribute:
+ checks.append(Attribute())
+ if options.all or options.brace_solo:
+ checks.append(BraceSolo())
+ if options.all or options.brace_without_space:
+ checks.append(BraceWithoutSpace())
+ if options.all or options.cpp_comments:
+ checks.append(CppComment())
+ if options.all or options.empty_loop_semicolon:
+ checks.append(EmptyLoopSemicolon())
+ if options.all or options.func_in_declaration:
+ checks.append(FuncInDeclaration())
+ if options.all or options.inline_static:
+ checks.append(FuncOnSameLine())
+ if options.all or options.inline_static:
+ checks.append(InlineStatic())
+ if options.all or options.long_lines:
+ checks.append(LongLines())
+ if options.all or options.multiline_first:
+ checks.append(MultilineFirst())
+ if options.all or options.multiline_last:
+ checks.append(MultilineLast())
+ if options.all or options.return_parens:
+ checks.append(ReturnParens())
+ if options.all or options.space_after_keyword:
+ checks.append(SpaceAfterKeyword())
+ if options.all or options.trailing_whitespace:
+ checks.append(TrailingWhitespace())
+ if (options.all or options.unnecessary_braces) and not options.require_braces:
+ checks.append(UnnecessaryBraces())
+ if options.require_braces:
+ checks.append(RequireBraces())
+
+ for check in checks:
+ #TODO: don't print check.desc if it has no matches
+ print "*** " + check.__doc__ + " ***"
+ for filename in args:
+ if options.verbose:
+ for line in check.finditer(filename):
+ print "%s:%d:%s" % (filename, line.lineNumber, line)
+ else:
+ if check.has_match(filename):
+ print filename
+
+
+if __name__ == '__main__':
+ main()
More information about the svn-src-user
mailing list