##// END OF EJS Templates
check-code: add a return value to checkfile function...
Pierre-Yves David -
r10720:fbcccf9e default
parent child Browse files
Show More
@@ -1,201 +1,206 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(2))
13 t = re.sub(r"\w", "x", m.group(2))
14 t = re.sub(r"[^\sx]", "o", t)
14 t = re.sub(r"[^\sx]", "o", t)
15 return m.group(1) + t + m.group(1)
15 return m.group(1) + t + m.group(1)
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'''(?<!")(")(([^"\n]|\\")+)"(?!")''', repquote),
99 (r'''(?<!")(")(([^"\n]|\\")+)"(?!")''', repquote),
100 (r"""(?<!')(')(([^'\n]|\\')+)'(?!')""", repquote),
100 (r"""(?<!')(')(([^'\n]|\\')+)'(?!')""", repquote),
101 (r"""(''')(([^']|\\'|'{1,2}(?!'))*)'''""", repquote),
101 (r"""(''')(([^']|\\'|'{1,2}(?!'))*)'''""", repquote),
102 (r'''(""")(([^"]|\\"|"{1,2}(?!"))*)"""''', repquote),
102 (r'''(""")(([^"]|\\"|"{1,2}(?!"))*)"""''', repquote),
103 (r"( *)(#([^\n]*\S)?)", repcomment),
103 (r"( *)(#([^\n]*\S)?)", repcomment),
104 ]
104 ]
105
105
106 cpats = [
106 cpats = [
107 (r'//', "don't use //-style comments"),
107 (r'//', "don't use //-style comments"),
108 (r'^ ', "don't use spaces to indent"),
108 (r'^ ', "don't use spaces to indent"),
109 (r'\S\t', "don't use tabs except for indent"),
109 (r'\S\t', "don't use tabs except for indent"),
110 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
110 (r'(\S\s+|^\s+)\n', "trailing whitespace"),
111 (r'.{85}', "line too long"),
111 (r'.{85}', "line too long"),
112 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
112 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
113 (r'return\(', "return is not a function"),
113 (r'return\(', "return is not a function"),
114 (r' ;', "no space before ;"),
114 (r' ;', "no space before ;"),
115 (r'\w+\* \w+', "use int *foo, not int* foo"),
115 (r'\w+\* \w+', "use int *foo, not int* foo"),
116 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
116 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
117 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
117 (r'\S+ (\+\+|--)', "use foo++, not foo ++"),
118 (r'\w,\w', "missing whitespace after ,"),
118 (r'\w,\w', "missing whitespace after ,"),
119 (r'\w[+/*]\w', "missing whitespace in expression"),
119 (r'\w[+/*]\w', "missing whitespace in expression"),
120 (r'^#\s+\w', "use #foo, not # foo"),
120 (r'^#\s+\w', "use #foo, not # foo"),
121 (r'[^\n]\Z', "no trailing newline"),
121 (r'[^\n]\Z', "no trailing newline"),
122 ]
122 ]
123
123
124 cfilters = [
124 cfilters = [
125 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
125 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
126 (r'''(?<!")(")(([^"]|\\")+"(?!"))''', repquote),
126 (r'''(?<!")(")(([^"]|\\")+"(?!"))''', repquote),
127 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
127 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
128 (r'(\()([^)]+\))', repcallspaces),
128 (r'(\()([^)]+\))', repcallspaces),
129 ]
129 ]
130
130
131 checks = [
131 checks = [
132 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
132 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
133 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
133 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
134 ('c', r'.*\.c$', cfilters, cpats),
134 ('c', r'.*\.c$', cfilters, cpats),
135 ]
135 ]
136
136
137 class norepeatlogger(object):
137 class norepeatlogger(object):
138 def __init__(self):
138 def __init__(self):
139 self._lastseen = None
139 self._lastseen = None
140
140
141 def log(self, fname, lineno, line, msg):
141 def log(self, fname, lineno, line, msg):
142 """print error related a to given line of a given file.
142 """print error related a to given line of a given file.
143
143
144 The faulty line will also be printed but only once in the case
144 The faulty line will also be printed but only once in the case
145 of multiple errors.
145 of multiple errors.
146
146
147 :fname: filename
147 :fname: filename
148 :lineno: line number
148 :lineno: line number
149 :line: actual content of the line
149 :line: actual content of the line
150 :msg: error message
150 :msg: error message
151 """
151 """
152 msgid = fname, lineno, line
152 msgid = fname, lineno, line
153 if msgid != self._lastseen:
153 if msgid != self._lastseen:
154 print "%s:%d:" % (fname, lineno)
154 print "%s:%d:" % (fname, lineno)
155 print " > %s" % line
155 print " > %s" % line
156 self._lastseen = msgid
156 self._lastseen = msgid
157 print " " + msg
157 print " " + msg
158
158
159 _defaultlogger = norepeatlogger()
159 _defaultlogger = norepeatlogger()
160
160
161 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None):
161 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None):
162 """checks style and portability of a given file
162 """checks style and portability of a given file
163
163
164 :f: filepath
164 :f: filepath
165 :logfunc: function used to report error
165 :logfunc: function used to report error
166 logfunc(filename, linenumber, linecontent, errormessage)
166 logfunc(filename, linenumber, linecontent, errormessage)
167 :maxerr: number of error to display before arborting.
167 :maxerr: number of error to display before arborting.
168 Set to None (default) to report all errors
168 Set to None (default) to report all errors
169
170 return True if no error is found, False otherwise.
169 """
171 """
172 result = True
170 for name, match, filters, pats in checks:
173 for name, match, filters, pats in checks:
171 fc = 0
174 fc = 0
172 if not re.match(match, f):
175 if not re.match(match, f):
173 continue
176 continue
174 pre = post = open(f).read()
177 pre = post = open(f).read()
175 if "no-" + "check-code" in pre:
178 if "no-" + "check-code" in pre:
176 break
179 break
177 for p, r in filters:
180 for p, r in filters:
178 post = re.sub(p, r, post)
181 post = re.sub(p, r, post)
179 # print post # uncomment to show filtered version
182 # print post # uncomment to show filtered version
180 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
183 z = enumerate(zip(pre.splitlines(), post.splitlines(True)))
181 for n, l in z:
184 for n, l in z:
182 if "check-code" + "-ignore" in l[0]:
185 if "check-code" + "-ignore" in l[0]:
183 continue
186 continue
184 for p, msg in pats:
187 for p, msg in pats:
185 if re.search(p, l[1]):
188 if re.search(p, l[1]):
186 logfunc(f, n+1, l[0], msg)
189 logfunc(f, n+1, l[0], msg)
187 fc += 1
190 fc += 1
191 result = False
188 if maxerr is not None and fc >= maxerr:
192 if maxerr is not None and fc >= maxerr:
189 print " (too many errors, giving up)"
193 print " (too many errors, giving up)"
190 break
194 break
191 break
195 break
196 return result
192
197
193
198
194 if __name__ == "__main__":
199 if __name__ == "__main__":
195 if len(sys.argv) == 1:
200 if len(sys.argv) == 1:
196 check = glob.glob("*")
201 check = glob.glob("*")
197 else:
202 else:
198 check = sys.argv[1:]
203 check = sys.argv[1:]
199
204
200 for f in check:
205 for f in check:
201 checkfile(f, maxerr=15)
206 checkfile(f, maxerr=15)
General Comments 0
You need to be logged in to leave comments. Login now