##// END OF EJS Templates
check-code: add check for 'source'
Yuya Nishihara -
r11210:0c008888 default
parent child Browse files
Show More
@@ -1,225 +1,226 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # check-code - a style and portability checker for Mercurial
4 4 #
5 5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 import re, glob
11 11 import optparse
12 12
13 13 def repquote(m):
14 14 t = re.sub(r"\w", "x", m.group('text'))
15 15 t = re.sub(r"[^\sx]", "o", t)
16 16 return m.group('quote') + t + m.group('quote')
17 17
18 18 def reppython(m):
19 19 comment = m.group('comment')
20 20 if comment:
21 21 return "#" * len(comment)
22 22 return repquote(m)
23 23
24 24 def repcomment(m):
25 25 return m.group(1) + "#" * len(m.group(2))
26 26
27 27 def repccomment(m):
28 28 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
29 29 return m.group(1) + t + "*/"
30 30
31 31 def repcallspaces(m):
32 32 t = re.sub(r"\n\s+", "\n", m.group(2))
33 33 return m.group(1) + t
34 34
35 35 def repinclude(m):
36 36 return m.group(1) + "<foo>"
37 37
38 38 def rephere(m):
39 39 t = re.sub(r"\S", "x", m.group(2))
40 40 return m.group(1) + t
41 41
42 42
43 43 testpats = [
44 44 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
45 45 (r'\W\$?\(\([^\)]*\)\)', "don't use (()) or $(()), use 'expr'"),
46 46 (r'^function', "don't use 'function', use old style"),
47 47 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
48 48 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
49 49 (r'^diff.*-\w*N', "don't use 'diff -N'"),
50 50 (r'(^| )wc[^|]*$', "filter wc output"),
51 51 (r'head -c', "don't use 'head -c', use 'dd'"),
52 52 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
53 53 (r'printf.*\\\d\d\d', "don't use 'printf \NNN', use Python"),
54 54 (r'printf.*\\x', "don't use printf \\x, use Python"),
55 55 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
56 56 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
57 57 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
58 58 "use egrep for extended grep syntax"),
59 59 (r'/bin/', "don't use explicit paths for tools"),
60 60 (r'\$PWD', "don't use $PWD, use `pwd`"),
61 61 (r'[^\n]\Z', "no trailing newline"),
62 62 (r'export.*=', "don't export and assign at once"),
63 63 ('^([^"\']|("[^"]*")|(\'[^\']*\'))*\\^', "^ must be quoted"),
64 (r'^source\b', "don't use 'source', use '.'"),
64 65 ]
65 66
66 67 testfilters = [
67 68 (r"( *)(#([^\n]*\S)?)", repcomment),
68 69 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
69 70 ]
70 71
71 72 pypats = [
72 73 (r'^\s*\t', "don't use tabs"),
73 74 (r'\S;\s*\n', "semicolon"),
74 75 (r'\w,\w', "missing whitespace after ,"),
75 76 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
76 77 (r'^\s+\w+=\w+[^,)]$', "missing whitespace in assignment"),
77 78 (r'.{85}', "line too long"),
78 79 (r'[^\n]\Z', "no trailing newline"),
79 80 # (r'^\s+[^_ ][^_. ]+_[^_]+\s*=', "don't use underbars in identifiers"),
80 81 # (r'\w*[a-z][A-Z]\w*\s*=', "don't use camelcase in identifiers"),
81 82 (r'^\s*(if|while|def|class|except|try)\s[^[]*:\s*[^\]#\s]+',
82 83 "linebreak after :"),
83 84 (r'class\s[^(]:', "old-style class, use class foo(object)"),
84 85 (r'^\s+del\(', "del isn't a function"),
85 86 (r'^\s+except\(', "except isn't a function"),
86 87 (r',]', "unneeded trailing ',' in list"),
87 88 # (r'class\s[A-Z][^\(]*\((?!Exception)',
88 89 # "don't capitalize non-exception classes"),
89 90 # (r'in range\(', "use xrange"),
90 91 # (r'^\s*print\s+', "avoid using print in core and extensions"),
91 92 (r'[\x80-\xff]', "non-ASCII character literal"),
92 93 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
93 94 (r'^\s*with\s+', "with not available in Python 2.4"),
94 95 (r'^\s*(any|all)\(', "any/all not available in Python 2.4"),
95 96 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
96 97 (r'([\(\[]\s\S)|(\S\s[\)\]])', "gratuitous whitespace in () or []"),
97 98 # (r'\s\s=', "gratuitous whitespace before ="),
98 99 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S', "missing whitespace around operator"),
99 100 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s', "missing whitespace around operator"),
100 101 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S', "missing whitespace around operator"),
101 102 (r'[^+=*!<>&| -](\s=|=\s)[^= ]', "wrong whitespace around ="),
102 103 (r'raise Exception', "don't raise generic exceptions"),
103 104 (r'ui\.(status|progress|write|note)\([\'\"]x',
104 105 "warning: unwrapped ui message"),
105 106 ]
106 107
107 108 pyfilters = [
108 109 (r"""(?msx)(?P<comment>\#.*?$)|
109 110 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
110 111 (?P<text>(([^\\]|\\.)*?))
111 112 (?P=quote))""", reppython),
112 113 ]
113 114
114 115 cpats = [
115 116 (r'//', "don't use //-style comments"),
116 117 (r'^ ', "don't use spaces to indent"),
117 118 (r'\S\t', "don't use tabs except for indent"),
118 119 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
119 120 (r'.{85}', "line too long"),
120 121 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
121 122 (r'return\(', "return is not a function"),
122 123 (r' ;', "no space before ;"),
123 124 (r'\w+\* \w+', "use int *foo, not int* foo"),
124 125 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
125 126 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
126 127 (r'\w,\w', "missing whitespace after ,"),
127 128 (r'\w[+/*]\w', "missing whitespace in expression"),
128 129 (r'^#\s+\w', "use #foo, not # foo"),
129 130 (r'[^\n]\Z', "no trailing newline"),
130 131 ]
131 132
132 133 cfilters = [
133 134 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
134 135 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
135 136 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
136 137 (r'(\()([^)]+\))', repcallspaces),
137 138 ]
138 139
139 140 checks = [
140 141 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
141 142 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
142 143 ('c', r'.*\.c$', cfilters, cpats),
143 144 ]
144 145
145 146 class norepeatlogger(object):
146 147 def __init__(self):
147 148 self._lastseen = None
148 149
149 150 def log(self, fname, lineno, line, msg):
150 151 """print error related a to given line of a given file.
151 152
152 153 The faulty line will also be printed but only once in the case
153 154 of multiple errors.
154 155
155 156 :fname: filename
156 157 :lineno: line number
157 158 :line: actual content of the line
158 159 :msg: error message
159 160 """
160 161 msgid = fname, lineno, line
161 162 if msgid != self._lastseen:
162 163 print "%s:%d:" % (fname, lineno)
163 164 print " > %s" % line
164 165 self._lastseen = msgid
165 166 print " " + msg
166 167
167 168 _defaultlogger = norepeatlogger()
168 169
169 170 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False):
170 171 """checks style and portability of a given file
171 172
172 173 :f: filepath
173 174 :logfunc: function used to report error
174 175 logfunc(filename, linenumber, linecontent, errormessage)
175 176 :maxerr: number of error to display before arborting.
176 177 Set to None (default) to report all errors
177 178
178 179 return True if no error is found, False otherwise.
179 180 """
180 181 result = True
181 182 for name, match, filters, pats in checks:
182 183 fc = 0
183 184 if not re.match(match, f):
184 185 continue
185 186 pre = post = open(f).read()
186 187 if "no-" + "check-code" in pre:
187 188 break
188 189 for p, r in filters:
189 190 post = re.sub(p, r, post)
190 191 # print post # uncomment to show filtered version
191 192 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
192 193 for n, l in z:
193 194 if "check-code" + "-ignore" in l[0]:
194 195 continue
195 196 for p, msg in pats:
196 197 if not warnings and msg.startswith("warning"):
197 198 continue
198 199 if re.search(p, l[1]):
199 200 logfunc(f, n + 1, l[0], msg)
200 201 fc += 1
201 202 result = False
202 203 if maxerr is not None and fc >= maxerr:
203 204 print " (too many errors, giving up)"
204 205 break
205 206 break
206 207 return result
207 208
208 209
209 210 if __name__ == "__main__":
210 211 parser = optparse.OptionParser("%prog [options] [files]")
211 212 parser.add_option("-w", "--warnings", action="store_true",
212 213 help="include warning-level checks")
213 214 parser.add_option("-p", "--per-file", type="int",
214 215 help="max warnings per file")
215 216
216 217 parser.set_defaults(per_file=15, warnings=False)
217 218 (options, args) = parser.parse_args()
218 219
219 220 if len(args) == 0:
220 221 check = glob.glob("*")
221 222 else:
222 223 check = args
223 224
224 225 for f in check:
225 226 checkfile(f, maxerr=options.per_file, warnings=options.warnings)
General Comments 0
You need to be logged in to leave comments. Login now