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