blob: 4c74ee57698e2c1a16f0acbb2f4b496626ad366c [file] [log] [blame]
Guido van Rossumaacf5ce2000-02-29 13:05:49 +00001#! /usr/bin/env python
2
3# Released to the public domain, by Tim Peters, 28 February 2000.
4
5"""checkappend.py -- search for multi-argument .append() calls.
6
7Usage: specify one or more file or directory paths:
8 checkappend [-v] file_or_dir [file_or_dir] ...
9
10Each file_or_dir is checked for multi-argument .append() calls. When
11a directory, all .py files in the directory, and recursively in its
12subdirectories, are checked.
13
14Use -v for status msgs. Use -vv for more status msgs.
15
16In the absence of -v, the only output is pairs of the form
17
18 filename(linenumber):
19 line containing the suspicious append
20
21Note that this finds multi-argument append calls regardless of whether
22they're attached to list objects. If a module defines a class with an
23append method that takes more than one argument, calls to that method
24will be listed.
25
26Note that this will not find multi-argument list.append calls made via a
27bound method object. For example, this is not caught:
28
29 somelist = []
30 push = somelist.append
31 push(1, 2, 3)
32"""
33
34__version__ = 1, 0, 0
35
36import os
37import sys
Guido van Rossumaacf5ce2000-02-29 13:05:49 +000038import getopt
39import tokenize
40
41verbose = 0
42
43def errprint(*args):
Walter Dörwaldaaab30e2002-09-11 20:36:02 +000044 msg = ' '.join(args)
Guido van Rossumaacf5ce2000-02-29 13:05:49 +000045 sys.stderr.write(msg)
46 sys.stderr.write("\n")
47
48def main():
49 args = sys.argv[1:]
50 global verbose
51 try:
52 opts, args = getopt.getopt(sys.argv[1:], "v")
Guido van Rossumb940e112007-01-10 16:19:56 +000053 except getopt.error as msg:
Guido van Rossum1b6d21b2000-02-29 13:08:44 +000054 errprint(str(msg) + "\n\n" + __doc__)
Guido van Rossumaacf5ce2000-02-29 13:05:49 +000055 return
56 for opt, optarg in opts:
57 if opt == '-v':
58 verbose = verbose + 1
59 if not args:
60 errprint(__doc__)
61 return
62 for arg in args:
63 check(arg)
64
65def check(file):
66 if os.path.isdir(file) and not os.path.islink(file):
67 if verbose:
Collin Winter6afaeb72007-08-03 17:06:41 +000068 print("%r: listing directory" % (file,))
Guido van Rossumaacf5ce2000-02-29 13:05:49 +000069 names = os.listdir(file)
70 for name in names:
71 fullname = os.path.join(file, name)
72 if ((os.path.isdir(fullname) and
73 not os.path.islink(fullname))
74 or os.path.normcase(name[-3:]) == ".py"):
75 check(fullname)
76 return
77
78 try:
79 f = open(file)
Guido van Rossumb940e112007-01-10 16:19:56 +000080 except IOError as msg:
Walter Dörwald70a6b492004-02-12 17:35:32 +000081 errprint("%r: I/O Error: %s" % (file, msg))
Guido van Rossumaacf5ce2000-02-29 13:05:49 +000082 return
83
84 if verbose > 1:
Collin Winter6afaeb72007-08-03 17:06:41 +000085 print("checking %r ..." % (file,))
Guido van Rossumaacf5ce2000-02-29 13:05:49 +000086
87 ok = AppendChecker(file, f).run()
88 if verbose and ok:
Collin Winter6afaeb72007-08-03 17:06:41 +000089 print("%r: Clean bill of health." % (file,))
Guido van Rossumaacf5ce2000-02-29 13:05:49 +000090
91[FIND_DOT,
92 FIND_APPEND,
93 FIND_LPAREN,
94 FIND_COMMA,
95 FIND_STMT] = range(5)
96
97class AppendChecker:
98 def __init__(self, fname, file):
99 self.fname = fname
100 self.file = file
101 self.state = FIND_DOT
102 self.nerrors = 0
103
104 def run(self):
105 try:
Trent Nelson428de652008-03-18 22:41:35 +0000106 tokens = tokenize.generate_tokens(self.file.readline)
107 for _token in tokens:
108 self.tokeneater(*_token)
Guido van Rossumb940e112007-01-10 16:19:56 +0000109 except tokenize.TokenError as msg:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000110 errprint("%r: Token Error: %s" % (self.fname, msg))
Guido van Rossumaacf5ce2000-02-29 13:05:49 +0000111 self.nerrors = self.nerrors + 1
112 return self.nerrors == 0
113
114 def tokeneater(self, type, token, start, end, line,
115 NEWLINE=tokenize.NEWLINE,
116 JUNK=(tokenize.COMMENT, tokenize.NL),
117 OP=tokenize.OP,
118 NAME=tokenize.NAME):
119
120 state = self.state
121
122 if type in JUNK:
123 pass
124
125 elif state is FIND_DOT:
126 if type is OP and token == ".":
127 state = FIND_APPEND
128
129 elif state is FIND_APPEND:
130 if type is NAME and token == "append":
131 self.line = line
132 self.lineno = start[0]
133 state = FIND_LPAREN
134 else:
135 state = FIND_DOT
136
137 elif state is FIND_LPAREN:
138 if type is OP and token == "(":
139 self.level = 1
140 state = FIND_COMMA
141 else:
142 state = FIND_DOT
143
144 elif state is FIND_COMMA:
145 if type is OP:
146 if token in ("(", "{", "["):
147 self.level = self.level + 1
148 elif token in (")", "}", "]"):
149 self.level = self.level - 1
150 if self.level == 0:
151 state = FIND_DOT
152 elif token == "," and self.level == 1:
153 self.nerrors = self.nerrors + 1
Collin Winter6afaeb72007-08-03 17:06:41 +0000154 print("%s(%d):\n%s" % (self.fname, self.lineno,
155 self.line))
Guido van Rossumaacf5ce2000-02-29 13:05:49 +0000156 # don't gripe about this stmt again
157 state = FIND_STMT
158
159 elif state is FIND_STMT:
160 if type is NEWLINE:
161 state = FIND_DOT
162
163 else:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000164 raise SystemError("unknown internal state '%r'" % (state,))
Guido van Rossumaacf5ce2000-02-29 13:05:49 +0000165
166 self.state = state
167
168if __name__ == '__main__':
169 main()