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