##// END OF EJS Templates
check-code: fix sed 'i' command rule newline matching...
Kevin Bullock -
r19083:12f15e4b stable
parent child Browse files
Show More
@@ -1,478 +1,478 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, os, sys
10 import re, glob, os, sys
11 import keyword
11 import keyword
12 import optparse
12 import optparse
13
13
14 def repquote(m):
14 def repquote(m):
15 t = re.sub(r"\w", "x", m.group('text'))
15 t = re.sub(r"\w", "x", m.group('text'))
16 t = re.sub(r"[^\s\nx]", "o", t)
16 t = re.sub(r"[^\s\nx]", "o", t)
17 return m.group('quote') + t + m.group('quote')
17 return m.group('quote') + t + m.group('quote')
18
18
19 def reppython(m):
19 def reppython(m):
20 comment = m.group('comment')
20 comment = m.group('comment')
21 if comment:
21 if comment:
22 l = len(comment.rstrip())
22 l = len(comment.rstrip())
23 return "#" * l + comment[l:]
23 return "#" * l + comment[l:]
24 return repquote(m)
24 return repquote(m)
25
25
26 def repcomment(m):
26 def repcomment(m):
27 return m.group(1) + "#" * len(m.group(2))
27 return m.group(1) + "#" * len(m.group(2))
28
28
29 def repccomment(m):
29 def repccomment(m):
30 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
30 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
31 return m.group(1) + t + "*/"
31 return m.group(1) + t + "*/"
32
32
33 def repcallspaces(m):
33 def repcallspaces(m):
34 t = re.sub(r"\n\s+", "\n", m.group(2))
34 t = re.sub(r"\n\s+", "\n", m.group(2))
35 return m.group(1) + t
35 return m.group(1) + t
36
36
37 def repinclude(m):
37 def repinclude(m):
38 return m.group(1) + "<foo>"
38 return m.group(1) + "<foo>"
39
39
40 def rephere(m):
40 def rephere(m):
41 t = re.sub(r"\S", "x", m.group(2))
41 t = re.sub(r"\S", "x", m.group(2))
42 return m.group(1) + t
42 return m.group(1) + t
43
43
44
44
45 testpats = [
45 testpats = [
46 [
46 [
47 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
47 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
48 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
48 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
49 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
49 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
50 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
50 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
51 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
51 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
52 (r'echo -n', "don't use 'echo -n', use printf"),
52 (r'echo -n', "don't use 'echo -n', use printf"),
53 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
53 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
54 (r'head -c', "don't use 'head -c', use 'dd'"),
54 (r'head -c', "don't use 'head -c', use 'dd'"),
55 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
55 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
56 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
56 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
57 (r'printf.*\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
57 (r'printf.*\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
58 (r'printf.*\\x', "don't use printf \\x, use Python"),
58 (r'printf.*\\x', "don't use printf \\x, use Python"),
59 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
59 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
60 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
60 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
61 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
61 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
62 "use egrep for extended grep syntax"),
62 "use egrep for extended grep syntax"),
63 (r'/bin/', "don't use explicit paths for tools"),
63 (r'/bin/', "don't use explicit paths for tools"),
64 (r'[^\n]\Z', "no trailing newline"),
64 (r'[^\n]\Z', "no trailing newline"),
65 (r'export.*=', "don't export and assign at once"),
65 (r'export.*=', "don't export and assign at once"),
66 (r'^source\b', "don't use 'source', use '.'"),
66 (r'^source\b', "don't use 'source', use '.'"),
67 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
67 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
68 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
68 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
69 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
69 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
70 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
70 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
71 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
71 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
72 (r'^alias\b.*=', "don't use alias, use a function"),
72 (r'^alias\b.*=', "don't use alias, use a function"),
73 (r'if\s*!', "don't use '!' to negate exit status"),
73 (r'if\s*!', "don't use '!' to negate exit status"),
74 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
74 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
75 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
75 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
76 (r'^( *)\t', "don't use tabs to indent"),
76 (r'^( *)\t', "don't use tabs to indent"),
77 (r'sed .*\'(\d+|/[^/]*/)i[^\\][^\n]',
77 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
78 "put a backslash-escaped newline after sed 'i' command"),
78 "put a backslash-escaped newline after sed 'i' command"),
79 ],
79 ],
80 # warnings
80 # warnings
81 [
81 [
82 (r'^function', "don't use 'function', use old style"),
82 (r'^function', "don't use 'function', use old style"),
83 (r'^diff.*-\w*N', "don't use 'diff -N'"),
83 (r'^diff.*-\w*N', "don't use 'diff -N'"),
84 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
84 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
85 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
85 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
86 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
86 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
87 ]
87 ]
88 ]
88 ]
89
89
90 testfilters = [
90 testfilters = [
91 (r"( *)(#([^\n]*\S)?)", repcomment),
91 (r"( *)(#([^\n]*\S)?)", repcomment),
92 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
92 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
93 ]
93 ]
94
94
95 winglobmsg = "use (glob) to match Windows paths too"
95 winglobmsg = "use (glob) to match Windows paths too"
96 uprefix = r"^ \$ "
96 uprefix = r"^ \$ "
97 utestpats = [
97 utestpats = [
98 [
98 [
99 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
99 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
100 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
100 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
101 "use regex test output patterns instead of sed"),
101 "use regex test output patterns instead of sed"),
102 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
102 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
103 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
103 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
104 (uprefix + r'.*\|\| echo.*(fail|error)',
104 (uprefix + r'.*\|\| echo.*(fail|error)',
105 "explicit exit code checks unnecessary"),
105 "explicit exit code checks unnecessary"),
106 (uprefix + r'set -e', "don't use set -e"),
106 (uprefix + r'set -e', "don't use set -e"),
107 (uprefix + r'\s', "don't indent commands, use > for continued lines"),
107 (uprefix + r'\s', "don't indent commands, use > for continued lines"),
108 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
108 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
109 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
109 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
110 winglobmsg),
110 winglobmsg),
111 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
111 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg, '\$TESTTMP/unix-repo$'),
112 ],
112 ],
113 # warnings
113 # warnings
114 [
114 [
115 (r'^ [^*?/\n]* \(glob\)$',
115 (r'^ [^*?/\n]* \(glob\)$',
116 "warning: glob match with no glob character (?*/)"),
116 "warning: glob match with no glob character (?*/)"),
117 ]
117 ]
118 ]
118 ]
119
119
120 for i in [0, 1]:
120 for i in [0, 1]:
121 for p, m in testpats[i]:
121 for p, m in testpats[i]:
122 if p.startswith(r'^'):
122 if p.startswith(r'^'):
123 p = r"^ [$>] (%s)" % p[1:]
123 p = r"^ [$>] (%s)" % p[1:]
124 else:
124 else:
125 p = r"^ [$>] .*(%s)" % p
125 p = r"^ [$>] .*(%s)" % p
126 utestpats[i].append((p, m))
126 utestpats[i].append((p, m))
127
127
128 utestfilters = [
128 utestfilters = [
129 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
129 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
130 (r"( *)(#([^\n]*\S)?)", repcomment),
130 (r"( *)(#([^\n]*\S)?)", repcomment),
131 ]
131 ]
132
132
133 pypats = [
133 pypats = [
134 [
134 [
135 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
135 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
136 "tuple parameter unpacking not available in Python 3+"),
136 "tuple parameter unpacking not available in Python 3+"),
137 (r'lambda\s*\(.*,.*\)',
137 (r'lambda\s*\(.*,.*\)',
138 "tuple parameter unpacking not available in Python 3+"),
138 "tuple parameter unpacking not available in Python 3+"),
139 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
139 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
140 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
140 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
141 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
141 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
142 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
142 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
143 (r'^\s*\t', "don't use tabs"),
143 (r'^\s*\t', "don't use tabs"),
144 (r'\S;\s*\n', "semicolon"),
144 (r'\S;\s*\n', "semicolon"),
145 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
145 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
146 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
146 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
147 (r'(\w|\)),\w', "missing whitespace after ,"),
147 (r'(\w|\)),\w', "missing whitespace after ,"),
148 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
148 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
149 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
149 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
150 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
150 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
151 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
151 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
152 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
152 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
153 r'((?:\n|\1\s.*\n)+?)\1finally:',
153 r'((?:\n|\1\s.*\n)+?)\1finally:',
154 'no yield inside try/finally in Python 2.4'),
154 'no yield inside try/finally in Python 2.4'),
155 (r'.{81}', "line too long"),
155 (r'.{81}', "line too long"),
156 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
156 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
157 (r'[^\n]\Z', "no trailing newline"),
157 (r'[^\n]\Z', "no trailing newline"),
158 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
158 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
159 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
159 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
160 # "don't use underbars in identifiers"),
160 # "don't use underbars in identifiers"),
161 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
161 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
162 "don't use camelcase in identifiers"),
162 "don't use camelcase in identifiers"),
163 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
163 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
164 "linebreak after :"),
164 "linebreak after :"),
165 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
165 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
166 (r'class\s[^( \n]+\(\):',
166 (r'class\s[^( \n]+\(\):',
167 "class foo() not available in Python 2.4, use class foo(object)"),
167 "class foo() not available in Python 2.4, use class foo(object)"),
168 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
168 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
169 "Python keyword is not a function"),
169 "Python keyword is not a function"),
170 (r',]', "unneeded trailing ',' in list"),
170 (r',]', "unneeded trailing ',' in list"),
171 # (r'class\s[A-Z][^\(]*\((?!Exception)',
171 # (r'class\s[A-Z][^\(]*\((?!Exception)',
172 # "don't capitalize non-exception classes"),
172 # "don't capitalize non-exception classes"),
173 # (r'in range\(', "use xrange"),
173 # (r'in range\(', "use xrange"),
174 # (r'^\s*print\s+', "avoid using print in core and extensions"),
174 # (r'^\s*print\s+', "avoid using print in core and extensions"),
175 (r'[\x80-\xff]', "non-ASCII character literal"),
175 (r'[\x80-\xff]', "non-ASCII character literal"),
176 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
176 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
177 (r'^\s*with\s+', "with not available in Python 2.4"),
177 (r'^\s*with\s+', "with not available in Python 2.4"),
178 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
178 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
179 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
179 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
180 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
180 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
181 (r'(?<!def)\s+(any|all|format)\(',
181 (r'(?<!def)\s+(any|all|format)\(',
182 "any/all/format not available in Python 2.4"),
182 "any/all/format not available in Python 2.4"),
183 (r'(?<!def)\s+(callable)\(',
183 (r'(?<!def)\s+(callable)\(',
184 "callable not available in Python 3, use getattr(f, '__call__', None)"),
184 "callable not available in Python 3, use getattr(f, '__call__', None)"),
185 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
185 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
186 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
186 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
187 "gratuitous whitespace after Python keyword"),
187 "gratuitous whitespace after Python keyword"),
188 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
188 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
189 # (r'\s\s=', "gratuitous whitespace before ="),
189 # (r'\s\s=', "gratuitous whitespace before ="),
190 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
190 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
191 "missing whitespace around operator"),
191 "missing whitespace around operator"),
192 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
192 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
193 "missing whitespace around operator"),
193 "missing whitespace around operator"),
194 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
194 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
195 "missing whitespace around operator"),
195 "missing whitespace around operator"),
196 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
196 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
197 "wrong whitespace around ="),
197 "wrong whitespace around ="),
198 (r'raise Exception', "don't raise generic exceptions"),
198 (r'raise Exception', "don't raise generic exceptions"),
199 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
199 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
200 "don't use old-style two-argument raise, use Exception(message)"),
200 "don't use old-style two-argument raise, use Exception(message)"),
201 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
201 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
202 (r' [=!]=\s+(True|False|None)',
202 (r' [=!]=\s+(True|False|None)',
203 "comparison with singleton, use 'is' or 'is not' instead"),
203 "comparison with singleton, use 'is' or 'is not' instead"),
204 (r'^\s*(while|if) [01]:',
204 (r'^\s*(while|if) [01]:',
205 "use True/False for constant Boolean expression"),
205 "use True/False for constant Boolean expression"),
206 (r'(?:(?<!def)\s+|\()hasattr',
206 (r'(?:(?<!def)\s+|\()hasattr',
207 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
207 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
208 (r'opener\([^)]*\).read\(',
208 (r'opener\([^)]*\).read\(',
209 "use opener.read() instead"),
209 "use opener.read() instead"),
210 (r'BaseException', 'not in Python 2.4, use Exception'),
210 (r'BaseException', 'not in Python 2.4, use Exception'),
211 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
211 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
212 (r'opener\([^)]*\).write\(',
212 (r'opener\([^)]*\).write\(',
213 "use opener.write() instead"),
213 "use opener.write() instead"),
214 (r'[\s\(](open|file)\([^)]*\)\.read\(',
214 (r'[\s\(](open|file)\([^)]*\)\.read\(',
215 "use util.readfile() instead"),
215 "use util.readfile() instead"),
216 (r'[\s\(](open|file)\([^)]*\)\.write\(',
216 (r'[\s\(](open|file)\([^)]*\)\.write\(',
217 "use util.readfile() instead"),
217 "use util.readfile() instead"),
218 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
218 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
219 "always assign an opened file to a variable, and close it afterwards"),
219 "always assign an opened file to a variable, and close it afterwards"),
220 (r'[\s\(](open|file)\([^)]*\)\.',
220 (r'[\s\(](open|file)\([^)]*\)\.',
221 "always assign an opened file to a variable, and close it afterwards"),
221 "always assign an opened file to a variable, and close it afterwards"),
222 (r'(?i)descendent', "the proper spelling is descendAnt"),
222 (r'(?i)descendent', "the proper spelling is descendAnt"),
223 (r'\.debug\(\_', "don't mark debug messages for translation"),
223 (r'\.debug\(\_', "don't mark debug messages for translation"),
224 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
224 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
225 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
225 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
226 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
226 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
227 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
227 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
228 "missing _() in ui message (use () to hide false-positives)"),
228 "missing _() in ui message (use () to hide false-positives)"),
229 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
229 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
230 ],
230 ],
231 # warnings
231 # warnings
232 [
232 [
233 ]
233 ]
234 ]
234 ]
235
235
236 pyfilters = [
236 pyfilters = [
237 (r"""(?msx)(?P<comment>\#.*?$)|
237 (r"""(?msx)(?P<comment>\#.*?$)|
238 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
238 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
239 (?P<text>(([^\\]|\\.)*?))
239 (?P<text>(([^\\]|\\.)*?))
240 (?P=quote))""", reppython),
240 (?P=quote))""", reppython),
241 ]
241 ]
242
242
243 txtfilters = []
243 txtfilters = []
244
244
245 txtpats = [
245 txtpats = [
246 [
246 [
247 ('\s$', 'trailing whitespace'),
247 ('\s$', 'trailing whitespace'),
248 ],
248 ],
249 []
249 []
250 ]
250 ]
251
251
252 cpats = [
252 cpats = [
253 [
253 [
254 (r'//', "don't use //-style comments"),
254 (r'//', "don't use //-style comments"),
255 (r'^ ', "don't use spaces to indent"),
255 (r'^ ', "don't use spaces to indent"),
256 (r'\S\t', "don't use tabs except for indent"),
256 (r'\S\t', "don't use tabs except for indent"),
257 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
257 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
258 (r'.{81}', "line too long"),
258 (r'.{81}', "line too long"),
259 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
259 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
260 (r'return\(', "return is not a function"),
260 (r'return\(', "return is not a function"),
261 (r' ;', "no space before ;"),
261 (r' ;', "no space before ;"),
262 (r'\w+\* \w+', "use int *foo, not int* foo"),
262 (r'\w+\* \w+', "use int *foo, not int* foo"),
263 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
263 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
264 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
264 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
265 (r'\w,\w', "missing whitespace after ,"),
265 (r'\w,\w', "missing whitespace after ,"),
266 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
266 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
267 (r'^#\s+\w', "use #foo, not # foo"),
267 (r'^#\s+\w', "use #foo, not # foo"),
268 (r'[^\n]\Z', "no trailing newline"),
268 (r'[^\n]\Z', "no trailing newline"),
269 (r'^\s*#import\b', "use only #include in standard C code"),
269 (r'^\s*#import\b', "use only #include in standard C code"),
270 ],
270 ],
271 # warnings
271 # warnings
272 []
272 []
273 ]
273 ]
274
274
275 cfilters = [
275 cfilters = [
276 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
276 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
277 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
277 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
278 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
278 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
279 (r'(\()([^)]+\))', repcallspaces),
279 (r'(\()([^)]+\))', repcallspaces),
280 ]
280 ]
281
281
282 inutilpats = [
282 inutilpats = [
283 [
283 [
284 (r'\bui\.', "don't use ui in util"),
284 (r'\bui\.', "don't use ui in util"),
285 ],
285 ],
286 # warnings
286 # warnings
287 []
287 []
288 ]
288 ]
289
289
290 inrevlogpats = [
290 inrevlogpats = [
291 [
291 [
292 (r'\brepo\.', "don't use repo in revlog"),
292 (r'\brepo\.', "don't use repo in revlog"),
293 ],
293 ],
294 # warnings
294 # warnings
295 []
295 []
296 ]
296 ]
297
297
298 checks = [
298 checks = [
299 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
299 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
300 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
300 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
301 ('c', r'.*\.c$', cfilters, cpats),
301 ('c', r'.*\.c$', cfilters, cpats),
302 ('unified test', r'.*\.t$', utestfilters, utestpats),
302 ('unified test', r'.*\.t$', utestfilters, utestpats),
303 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
303 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
304 inrevlogpats),
304 inrevlogpats),
305 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
305 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
306 inutilpats),
306 inutilpats),
307 ('txt', r'.*\.txt$', txtfilters, txtpats),
307 ('txt', r'.*\.txt$', txtfilters, txtpats),
308 ]
308 ]
309
309
310 class norepeatlogger(object):
310 class norepeatlogger(object):
311 def __init__(self):
311 def __init__(self):
312 self._lastseen = None
312 self._lastseen = None
313
313
314 def log(self, fname, lineno, line, msg, blame):
314 def log(self, fname, lineno, line, msg, blame):
315 """print error related a to given line of a given file.
315 """print error related a to given line of a given file.
316
316
317 The faulty line will also be printed but only once in the case
317 The faulty line will also be printed but only once in the case
318 of multiple errors.
318 of multiple errors.
319
319
320 :fname: filename
320 :fname: filename
321 :lineno: line number
321 :lineno: line number
322 :line: actual content of the line
322 :line: actual content of the line
323 :msg: error message
323 :msg: error message
324 """
324 """
325 msgid = fname, lineno, line
325 msgid = fname, lineno, line
326 if msgid != self._lastseen:
326 if msgid != self._lastseen:
327 if blame:
327 if blame:
328 print "%s:%d (%s):" % (fname, lineno, blame)
328 print "%s:%d (%s):" % (fname, lineno, blame)
329 else:
329 else:
330 print "%s:%d:" % (fname, lineno)
330 print "%s:%d:" % (fname, lineno)
331 print " > %s" % line
331 print " > %s" % line
332 self._lastseen = msgid
332 self._lastseen = msgid
333 print " " + msg
333 print " " + msg
334
334
335 _defaultlogger = norepeatlogger()
335 _defaultlogger = norepeatlogger()
336
336
337 def getblame(f):
337 def getblame(f):
338 lines = []
338 lines = []
339 for l in os.popen('hg annotate -un %s' % f):
339 for l in os.popen('hg annotate -un %s' % f):
340 start, line = l.split(':', 1)
340 start, line = l.split(':', 1)
341 user, rev = start.split()
341 user, rev = start.split()
342 lines.append((line[1:-1], user, rev))
342 lines.append((line[1:-1], user, rev))
343 return lines
343 return lines
344
344
345 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
345 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
346 blame=False, debug=False, lineno=True):
346 blame=False, debug=False, lineno=True):
347 """checks style and portability of a given file
347 """checks style and portability of a given file
348
348
349 :f: filepath
349 :f: filepath
350 :logfunc: function used to report error
350 :logfunc: function used to report error
351 logfunc(filename, linenumber, linecontent, errormessage)
351 logfunc(filename, linenumber, linecontent, errormessage)
352 :maxerr: number of error to display before aborting.
352 :maxerr: number of error to display before aborting.
353 Set to false (default) to report all errors
353 Set to false (default) to report all errors
354
354
355 return True if no error is found, False otherwise.
355 return True if no error is found, False otherwise.
356 """
356 """
357 blamecache = None
357 blamecache = None
358 result = True
358 result = True
359 for name, match, filters, pats in checks:
359 for name, match, filters, pats in checks:
360 if debug:
360 if debug:
361 print name, f
361 print name, f
362 fc = 0
362 fc = 0
363 if not re.match(match, f):
363 if not re.match(match, f):
364 if debug:
364 if debug:
365 print "Skipping %s for %s it doesn't match %s" % (
365 print "Skipping %s for %s it doesn't match %s" % (
366 name, match, f)
366 name, match, f)
367 continue
367 continue
368 fp = open(f)
368 fp = open(f)
369 pre = post = fp.read()
369 pre = post = fp.read()
370 fp.close()
370 fp.close()
371 if "no-" + "check-code" in pre:
371 if "no-" + "check-code" in pre:
372 if debug:
372 if debug:
373 print "Skipping %s for %s it has no- and check-code" % (
373 print "Skipping %s for %s it has no- and check-code" % (
374 name, f)
374 name, f)
375 break
375 break
376 for p, r in filters:
376 for p, r in filters:
377 post = re.sub(p, r, post)
377 post = re.sub(p, r, post)
378 if warnings:
378 if warnings:
379 pats = pats[0] + pats[1]
379 pats = pats[0] + pats[1]
380 else:
380 else:
381 pats = pats[0]
381 pats = pats[0]
382 # print post # uncomment to show filtered version
382 # print post # uncomment to show filtered version
383
383
384 if debug:
384 if debug:
385 print "Checking %s for %s" % (name, f)
385 print "Checking %s for %s" % (name, f)
386
386
387 prelines = None
387 prelines = None
388 errors = []
388 errors = []
389 for pat in pats:
389 for pat in pats:
390 if len(pat) == 3:
390 if len(pat) == 3:
391 p, msg, ignore = pat
391 p, msg, ignore = pat
392 else:
392 else:
393 p, msg = pat
393 p, msg = pat
394 ignore = None
394 ignore = None
395
395
396 # fix-up regexes for multi-line searches
396 # fix-up regexes for multi-line searches
397 po = p
397 po = p
398 # \s doesn't match \n
398 # \s doesn't match \n
399 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
399 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
400 # [^...] doesn't match newline
400 # [^...] doesn't match newline
401 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
401 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
402
402
403 #print po, '=>', p
403 #print po, '=>', p
404
404
405 pos = 0
405 pos = 0
406 n = 0
406 n = 0
407 for m in re.finditer(p, post, re.MULTILINE):
407 for m in re.finditer(p, post, re.MULTILINE):
408 if prelines is None:
408 if prelines is None:
409 prelines = pre.splitlines()
409 prelines = pre.splitlines()
410 postlines = post.splitlines(True)
410 postlines = post.splitlines(True)
411
411
412 start = m.start()
412 start = m.start()
413 while n < len(postlines):
413 while n < len(postlines):
414 step = len(postlines[n])
414 step = len(postlines[n])
415 if pos + step > start:
415 if pos + step > start:
416 break
416 break
417 pos += step
417 pos += step
418 n += 1
418 n += 1
419 l = prelines[n]
419 l = prelines[n]
420
420
421 if "check-code" + "-ignore" in l:
421 if "check-code" + "-ignore" in l:
422 if debug:
422 if debug:
423 print "Skipping %s for %s:%s (check-code -ignore)" % (
423 print "Skipping %s for %s:%s (check-code -ignore)" % (
424 name, f, n)
424 name, f, n)
425 continue
425 continue
426 elif ignore and re.search(ignore, l, re.MULTILINE):
426 elif ignore and re.search(ignore, l, re.MULTILINE):
427 continue
427 continue
428 bd = ""
428 bd = ""
429 if blame:
429 if blame:
430 bd = 'working directory'
430 bd = 'working directory'
431 if not blamecache:
431 if not blamecache:
432 blamecache = getblame(f)
432 blamecache = getblame(f)
433 if n < len(blamecache):
433 if n < len(blamecache):
434 bl, bu, br = blamecache[n]
434 bl, bu, br = blamecache[n]
435 if bl == l:
435 if bl == l:
436 bd = '%s@%s' % (bu, br)
436 bd = '%s@%s' % (bu, br)
437 errors.append((f, lineno and n + 1, l, msg, bd))
437 errors.append((f, lineno and n + 1, l, msg, bd))
438 result = False
438 result = False
439
439
440 errors.sort()
440 errors.sort()
441 for e in errors:
441 for e in errors:
442 logfunc(*e)
442 logfunc(*e)
443 fc += 1
443 fc += 1
444 if maxerr and fc >= maxerr:
444 if maxerr and fc >= maxerr:
445 print " (too many errors, giving up)"
445 print " (too many errors, giving up)"
446 break
446 break
447
447
448 return result
448 return result
449
449
450 if __name__ == "__main__":
450 if __name__ == "__main__":
451 parser = optparse.OptionParser("%prog [options] [files]")
451 parser = optparse.OptionParser("%prog [options] [files]")
452 parser.add_option("-w", "--warnings", action="store_true",
452 parser.add_option("-w", "--warnings", action="store_true",
453 help="include warning-level checks")
453 help="include warning-level checks")
454 parser.add_option("-p", "--per-file", type="int",
454 parser.add_option("-p", "--per-file", type="int",
455 help="max warnings per file")
455 help="max warnings per file")
456 parser.add_option("-b", "--blame", action="store_true",
456 parser.add_option("-b", "--blame", action="store_true",
457 help="use annotate to generate blame info")
457 help="use annotate to generate blame info")
458 parser.add_option("", "--debug", action="store_true",
458 parser.add_option("", "--debug", action="store_true",
459 help="show debug information")
459 help="show debug information")
460 parser.add_option("", "--nolineno", action="store_false",
460 parser.add_option("", "--nolineno", action="store_false",
461 dest='lineno', help="don't show line numbers")
461 dest='lineno', help="don't show line numbers")
462
462
463 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
463 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
464 lineno=True)
464 lineno=True)
465 (options, args) = parser.parse_args()
465 (options, args) = parser.parse_args()
466
466
467 if len(args) == 0:
467 if len(args) == 0:
468 check = glob.glob("*")
468 check = glob.glob("*")
469 else:
469 else:
470 check = args
470 check = args
471
471
472 ret = 0
472 ret = 0
473 for f in check:
473 for f in check:
474 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
474 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
475 blame=options.blame, debug=options.debug,
475 blame=options.blame, debug=options.debug,
476 lineno=options.lineno):
476 lineno=options.lineno):
477 ret = 1
477 ret = 1
478 sys.exit(ret)
478 sys.exit(ret)
General Comments 0
You need to be logged in to leave comments. Login now