diff --git a/Tools/scripts/checkappend.py b/Tools/scripts/checkappend.py new file mode 100755 index 00000000000..727d5ee38be --- /dev/null +++ b/Tools/scripts/checkappend.py @@ -0,0 +1,168 @@ +#! /usr/bin/env python + +# Released to the public domain, by Tim Peters, 28 February 2000. + +"""checkappend.py -- search for multi-argument .append() calls. + +Usage: specify one or more file or directory paths: + checkappend [-v] file_or_dir [file_or_dir] ... + +Each file_or_dir is checked for multi-argument .append() calls. When +a directory, all .py files in the directory, and recursively in its +subdirectories, are checked. + +Use -v for status msgs. Use -vv for more status msgs. + +In the absence of -v, the only output is pairs of the form + + filename(linenumber): + line containing the suspicious append + +Note that this finds multi-argument append calls regardless of whether +they're attached to list objects. If a module defines a class with an +append method that takes more than one argument, calls to that method +will be listed. + +Note that this will not find multi-argument list.append calls made via a +bound method object. For example, this is not caught: + + somelist = [] + push = somelist.append + push(1, 2, 3) +""" + +__version__ = 1, 0, 0 + +import os +import sys +import string +import getopt +import tokenize + +verbose = 0 + +def errprint(*args): + msg = string.join(args) + sys.stderr.write(msg) + sys.stderr.write("\n") + +def main(): + args = sys.argv[1:] + global verbose + try: + opts, args = getopt.getopt(sys.argv[1:], "v") + except getopt.error, msg: + errprint(msg + "\n\n" + __doc__) + return + for opt, optarg in opts: + if opt == '-v': + verbose = verbose + 1 + if not args: + errprint(__doc__) + return + for arg in args: + check(arg) + +def check(file): + if os.path.isdir(file) and not os.path.islink(file): + if verbose: + print "%s: listing directory" % `file` + names = os.listdir(file) + for name in names: + fullname = os.path.join(file, name) + if ((os.path.isdir(fullname) and + not os.path.islink(fullname)) + or os.path.normcase(name[-3:]) == ".py"): + check(fullname) + return + + try: + f = open(file) + except IOError, msg: + errprint("%s: I/O Error: %s" % (`file`, str(msg))) + return + + if verbose > 1: + print "checking", `file`, "..." + + ok = AppendChecker(file, f).run() + if verbose and ok: + print "%s: Clean bill of health." % `file` + +[FIND_DOT, + FIND_APPEND, + FIND_LPAREN, + FIND_COMMA, + FIND_STMT] = range(5) + +class AppendChecker: + def __init__(self, fname, file): + self.fname = fname + self.file = file + self.state = FIND_DOT + self.nerrors = 0 + + def run(self): + try: + tokenize.tokenize(self.file.readline, self.tokeneater) + except tokenize.TokenError, msg: + errprint("%s: Token Error: %s" % (`self.fname`, str(msg))) + self.nerrors = self.nerrors + 1 + return self.nerrors == 0 + + def tokeneater(self, type, token, start, end, line, + NEWLINE=tokenize.NEWLINE, + JUNK=(tokenize.COMMENT, tokenize.NL), + OP=tokenize.OP, + NAME=tokenize.NAME): + + state = self.state + + if type in JUNK: + pass + + elif state is FIND_DOT: + if type is OP and token == ".": + state = FIND_APPEND + + elif state is FIND_APPEND: + if type is NAME and token == "append": + self.line = line + self.lineno = start[0] + state = FIND_LPAREN + else: + state = FIND_DOT + + elif state is FIND_LPAREN: + if type is OP and token == "(": + self.level = 1 + state = FIND_COMMA + else: + state = FIND_DOT + + elif state is FIND_COMMA: + if type is OP: + if token in ("(", "{", "["): + self.level = self.level + 1 + elif token in (")", "}", "]"): + self.level = self.level - 1 + if self.level == 0: + state = FIND_DOT + elif token == "," and self.level == 1: + self.nerrors = self.nerrors + 1 + print "%s(%d):\n%s" % (self.fname, self.lineno, + self.line) + # don't gripe about this stmt again + state = FIND_STMT + + elif state is FIND_STMT: + if type is NEWLINE: + state = FIND_DOT + + else: + raise SystemError("unknown internal state '%s'" % `state`) + + self.state = state + +if __name__ == '__main__': + main()