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