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