verifiers.py (11410:e51095583654) verifiers.py (11449:4511f239d1ba)
1#!/usr/bin/env python
2#
3# Copyright (c) 2014, 2016 ARM Limited
4# All rights reserved
5#
6# The license below extends only to copyright in the software and shall
7# not be construed as granting a license to any other intellectual
8# property including but not limited to intellectual property relating
9# to a hardware implementation of the functionality of the software
10# licensed hereunder. You may use the software subject to the license
11# terms below provided that you ensure that this notice is replicated
12# unmodified and in its entirety in all distributions of the software,
13# modified or unmodified, in source code or in binary form.
14#
15# Copyright (c) 2006 The Regents of The University of Michigan
16# Copyright (c) 2007,2011 The Hewlett-Packard Development Company
17# Copyright (c) 2016 Advanced Micro Devices, Inc.
18# All rights reserved.
19#
20# Redistribution and use in source and binary forms, with or without
21# modification, are permitted provided that the following conditions are
22# met: redistributions of source code must retain the above copyright
23# notice, this list of conditions and the following disclaimer;
24# redistributions in binary form must reproduce the above copyright
25# notice, this list of conditions and the following disclaimer in the
26# documentation and/or other materials provided with the distribution;
27# neither the name of the copyright holders nor the names of its
28# contributors may be used to endorse or promote products derived from
29# this software without specific prior written permission.
30#
31# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42#
43# Authors: Nathan Binkert
44# Steve Reinhardt
45# Andreas Sandberg
46
47from abc import ABCMeta, abstractmethod
48from difflib import SequenceMatcher
49import inspect
50import os
51import re
52import sys
53
54import style
55import sort_includes
56from region import *
57from file_types import lang_type
58
59def _modified_regions(old, new):
1#!/usr/bin/env python
2#
3# Copyright (c) 2014, 2016 ARM Limited
4# All rights reserved
5#
6# The license below extends only to copyright in the software and shall
7# not be construed as granting a license to any other intellectual
8# property including but not limited to intellectual property relating
9# to a hardware implementation of the functionality of the software
10# licensed hereunder. You may use the software subject to the license
11# terms below provided that you ensure that this notice is replicated
12# unmodified and in its entirety in all distributions of the software,
13# modified or unmodified, in source code or in binary form.
14#
15# Copyright (c) 2006 The Regents of The University of Michigan
16# Copyright (c) 2007,2011 The Hewlett-Packard Development Company
17# Copyright (c) 2016 Advanced Micro Devices, Inc.
18# All rights reserved.
19#
20# Redistribution and use in source and binary forms, with or without
21# modification, are permitted provided that the following conditions are
22# met: redistributions of source code must retain the above copyright
23# notice, this list of conditions and the following disclaimer;
24# redistributions in binary form must reproduce the above copyright
25# notice, this list of conditions and the following disclaimer in the
26# documentation and/or other materials provided with the distribution;
27# neither the name of the copyright holders nor the names of its
28# contributors may be used to endorse or promote products derived from
29# this software without specific prior written permission.
30#
31# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
32# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
33# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
34# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
35# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
37# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
38# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
39# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
40# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
41# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42#
43# Authors: Nathan Binkert
44# Steve Reinhardt
45# Andreas Sandberg
46
47from abc import ABCMeta, abstractmethod
48from difflib import SequenceMatcher
49import inspect
50import os
51import re
52import sys
53
54import style
55import sort_includes
56from region import *
57from file_types import lang_type
58
59def _modified_regions(old, new):
60 m = SequenceMatcher(a=old, b=new, autojunk=False)
61
60 try:
61 m = SequenceMatcher(a=old, b=new, autojunk=False)
62 except TypeError:
63 # autojunk was introduced in Python 2.7. We need a fallback
64 # mechanism to support old Python versions.
65 m = SequenceMatcher(a=old, b=new)
62 regions = Regions()
63 for tag, i1, i2, j1, j2 in m.get_opcodes():
64 if tag != "equal":
65 regions.extend(Region(i1, i2))
66 return regions
67
68
69class Verifier(object):
70 """Base class for style verifiers
71
72 Verifiers check for style violations and optionally fix such
73 violations. Implementations should either inherit from this class
74 (Verifier) if they need to work on entire files or LineVerifier if
75 they operate on a line-by-line basis.
76
77 Subclasses must define these class attributes:
78 languages = set of strings identifying applicable languages
79 test_name = long descriptive name of test, will be used in
80 messages such as "error in <foo>" or "invalid <foo>"
81 opt_name = short name used to generate command-line options to
82 control the test (--fix-<foo>, --ignore-<foo>, etc.)
83
84 """
85
86 __metaclass__ = ABCMeta
87
88 def __init__(self, ui, opts, base=None):
89 self.ui = ui
90 self.base = base
91
92 # opt_name must be defined as a class attribute of derived classes.
93 # Check test-specific opts first as these have precedence.
94 self.opt_fix = opts.get('fix_' + self.opt_name, False)
95 self.opt_ignore = opts.get('ignore_' + self.opt_name, False)
96 self.opt_skip = opts.get('skip_' + self.opt_name, False)
97 # If no test-specific opts were set, then set based on "-all" opts.
98 if not (self.opt_fix or self.opt_ignore or self.opt_skip):
99 self.opt_fix = opts.get('fix_all', False)
100 self.opt_ignore = opts.get('ignore_all', False)
101 self.opt_skip = opts.get('skip_all', False)
102
103 def normalize_filename(self, name):
104 abs_name = os.path.abspath(name)
105 if self.base is None:
106 return abs_name
107
108 abs_base = os.path.abspath(self.base)
109 return os.path.relpath(abs_name, start=abs_base)
110
111 def open(self, filename, mode):
112 try:
113 f = file(filename, mode)
114 except OSError, msg:
115 print 'could not open file %s: %s' % (filename, msg)
116 return None
117
118 return f
119
120 def skip(self, filename):
121 # We never want to handle symlinks, so always skip them: If the location
122 # pointed to is a directory, skip it. If the location is a file inside
123 # the gem5 directory, it will be checked as a file, so symlink can be
124 # skipped. If the location is a file outside gem5, we don't want to
125 # check it anyway.
126 if os.path.islink(filename):
127 return True
128 return lang_type(filename) not in self.languages
129
130 def apply(self, filename, regions=all_regions):
131 """Possibly apply to specified regions of file 'filename'.
132
133 Verifier is skipped if --skip-<test> option was provided or if
134 file is not of an applicable type. Otherwise file is checked
135 and error messages printed. Errors are fixed or ignored if
136 the corresponding --fix-<test> or --ignore-<test> options were
137 provided. If neither, the user is prompted for an action.
138
139 Returns True to abort, False otherwise.
140 """
141 if not (self.opt_skip or self.skip(filename)):
142 errors = self.check(filename, regions)
143 if errors and not self.opt_ignore:
144 if self.opt_fix:
145 self.fix(filename, regions)
146 else:
147 result = self.ui.prompt("(a)bort, (i)gnore, or (f)ix?",
148 'aif', 'a')
149 if result == 'f':
150 self.fix(filename, regions)
151 elif result == 'a':
152 return True # abort
153
154 return False
155
156 @abstractmethod
157 def check(self, filename, regions=all_regions):
158 """Check specified regions of file 'filename'.
159
160 Line-by-line checks can simply provide a check_line() method
161 that returns True if the line is OK and False if it has an
162 error. Verifiers that need a multi-line view (like
163 SortedIncludes) must override this entire function.
164
165 Returns a count of errors (0 if none), though actual non-zero
166 count value is not currently used anywhere.
167 """
168 pass
169
170 @abstractmethod
171 def fix(self, filename, regions=all_regions):
172 """Fix specified regions of file 'filename'.
173
174 Line-by-line fixes can simply provide a fix_line() method that
175 returns the fixed line. Verifiers that need a multi-line view
176 (like SortedIncludes) must override this entire function.
177 """
178 pass
179
180class LineVerifier(Verifier):
181 def check(self, filename, regions=all_regions):
182 f = self.open(filename, 'r')
183
184 errors = 0
185 for num,line in enumerate(f):
186 if num not in regions:
187 continue
188 line = line.rstrip('\n')
189 if not self.check_line(line):
190 self.ui.write("invalid %s in %s:%d\n" % \
191 (self.test_name, filename, num + 1))
192 if self.ui.verbose:
193 self.ui.write(">>%s<<\n" % line[:-1])
194 errors += 1
195 return errors
196
197 def fix(self, filename, regions=all_regions):
198 f = self.open(filename, 'r+')
199
200 lines = list(f)
201
202 f.seek(0)
203 f.truncate()
204
205 for i,line in enumerate(lines):
206 line = line.rstrip('\n')
207 if i in regions:
208 line = self.fix_line(line)
209
210 f.write(line)
211 f.write("\n")
212 f.close()
213
214
215 @abstractmethod
216 def check_line(self, line):
217 pass
218
219 @abstractmethod
220 def fix_line(self, line):
221 pass
222
223class Whitespace(LineVerifier):
224 """Check whitespace.
225
226 Specifically:
227 - No tabs used for indent
228 - No trailing whitespace
229 """
230
231 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
232 test_name = 'whitespace'
233 opt_name = 'white'
234
235 _lead = re.compile(r'^([ \t]+)')
236 _trail = re.compile(r'([ \t]+)$')
237
238 def check_line(self, line):
239 match = Whitespace._lead.search(line)
240 if match and match.group(1).find('\t') != -1:
241 return False
242
243 match = Whitespace._trail.search(line)
244 if match:
245 return False
246
247 return True
248
249 def fix_line(self, line):
250 if Whitespace._lead.search(line):
251 newline = ''
252 for i,c in enumerate(line):
253 if c == ' ':
254 newline += ' '
255 elif c == '\t':
256 newline += ' ' * (tabsize - len(newline) % tabsize)
257 else:
258 newline += line[i:]
259 break
260
261 line = newline
262
263 return line.rstrip() + '\n'
264
265
266class SortedIncludes(Verifier):
267 """Check for proper sorting of include statements"""
268
269 languages = sort_includes.default_languages
270 test_name = 'include file order'
271 opt_name = 'include'
272
273 def __init__(self, *args, **kwargs):
274 super(SortedIncludes, self).__init__(*args, **kwargs)
275 self.sort_includes = sort_includes.SortIncludes()
276
277 def check(self, filename, regions=all_regions):
278 f = self.open(filename, 'r')
279 norm_fname = self.normalize_filename(filename)
280
281 old = [ l.rstrip('\n') for l in f.xreadlines() ]
282 f.close()
283
284 if len(old) == 0:
285 return 0
286
287 language = lang_type(filename, old[0])
288 new = list(self.sort_includes(old, norm_fname, language))
289
290 modified = _modified_regions(old, new) & regions
291
292 if modified:
293 self.ui.write("invalid sorting of includes in %s\n" % (filename))
294 if self.ui.verbose:
295 for start, end in modified.regions:
296 self.ui.write("bad region [%d, %d)\n" % (start, end))
297 return 1
298
299 return 0
300
301 def fix(self, filename, regions=all_regions):
302 f = self.open(filename, 'r+')
303
304 old = f.readlines()
305 lines = [ l.rstrip('\n') for l in old ]
306 language = lang_type(filename, lines[0])
307 sort_lines = list(self.sort_includes(lines, filename, language))
308 new = ''.join(line + '\n' for line in sort_lines)
309
310 f.seek(0)
311 f.truncate()
312
313 for i,line in enumerate(sort_lines):
314 f.write(line)
315 f.write('\n')
316 f.close()
317
318
319class ControlSpace(LineVerifier):
320 """Check for exactly one space after if/while/for"""
321
322 languages = set(('C', 'C++'))
323 test_name = 'spacing after if/while/for'
324 opt_name = 'control'
325
326 _any_control = re.compile(r'\b(if|while|for)([ \t]*)\(')
327
328 def check_line(self, line):
329 match = ControlSpace._any_control.search(line)
330 return not (match and match.group(2) != " ")
331
332 def fix_line(self, line):
333 new_line = _any_control.sub(r'\1 (', line)
334 return new_line
335
336
337class LineLength(LineVerifier):
338 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
339 test_name = 'line length'
340 opt_name = 'length'
341
342 def check_line(self, line):
343 return style.normalized_len(line) <= 79
344
345 def fix(self, filename, regions=all_regions):
346 self.ui.write("Warning: cannot automatically fix overly long lines.\n")
347
348 def fix_line(self, line):
349 pass
350
351class ControlCharacters(LineVerifier):
352 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
353 test_name = 'control character'
354 opt_name = 'ascii'
355
356 valid = ('\n', '\t')
357 invalid = "".join([chr(i) for i in range(0, 0x20) if chr(i) not in valid])
358
359 def check_line(self, line):
360 return self.fix_line(line) == line
361
362 def fix_line(self, line):
363 return line.translate(None, ControlCharacters.invalid)
364
365class BoolCompare(LineVerifier):
366 languages = set(('C', 'C++', 'python'))
367 test_name = 'boolean comparison'
368 opt_name = 'boolcomp'
369
370 regex = re.compile(r'\s*==\s*([Tt]rue|[Ff]alse)\b')
371
372 def check_line(self, line):
373 return self.regex.search(line) == None
374
375 def fix_line(self, line):
376 match = self.regex.search(line)
377 if match:
378 if match.group(1) in ('true', 'True'):
379 line = self.regex.sub('', line)
380 else:
381 self.ui.write("Warning: cannot automatically fix "
382 "comparisons with false/False.\n")
383 return line
384
385def is_verifier(cls):
386 """Determine if a class is a Verifier that can be instantiated"""
387
388 return inspect.isclass(cls) and issubclass(cls, Verifier) and \
389 not inspect.isabstract(cls)
390
391# list of all verifier classes
392all_verifiers = [ v for n, v in \
393 inspect.getmembers(sys.modules[__name__], is_verifier) ]
66 regions = Regions()
67 for tag, i1, i2, j1, j2 in m.get_opcodes():
68 if tag != "equal":
69 regions.extend(Region(i1, i2))
70 return regions
71
72
73class Verifier(object):
74 """Base class for style verifiers
75
76 Verifiers check for style violations and optionally fix such
77 violations. Implementations should either inherit from this class
78 (Verifier) if they need to work on entire files or LineVerifier if
79 they operate on a line-by-line basis.
80
81 Subclasses must define these class attributes:
82 languages = set of strings identifying applicable languages
83 test_name = long descriptive name of test, will be used in
84 messages such as "error in <foo>" or "invalid <foo>"
85 opt_name = short name used to generate command-line options to
86 control the test (--fix-<foo>, --ignore-<foo>, etc.)
87
88 """
89
90 __metaclass__ = ABCMeta
91
92 def __init__(self, ui, opts, base=None):
93 self.ui = ui
94 self.base = base
95
96 # opt_name must be defined as a class attribute of derived classes.
97 # Check test-specific opts first as these have precedence.
98 self.opt_fix = opts.get('fix_' + self.opt_name, False)
99 self.opt_ignore = opts.get('ignore_' + self.opt_name, False)
100 self.opt_skip = opts.get('skip_' + self.opt_name, False)
101 # If no test-specific opts were set, then set based on "-all" opts.
102 if not (self.opt_fix or self.opt_ignore or self.opt_skip):
103 self.opt_fix = opts.get('fix_all', False)
104 self.opt_ignore = opts.get('ignore_all', False)
105 self.opt_skip = opts.get('skip_all', False)
106
107 def normalize_filename(self, name):
108 abs_name = os.path.abspath(name)
109 if self.base is None:
110 return abs_name
111
112 abs_base = os.path.abspath(self.base)
113 return os.path.relpath(abs_name, start=abs_base)
114
115 def open(self, filename, mode):
116 try:
117 f = file(filename, mode)
118 except OSError, msg:
119 print 'could not open file %s: %s' % (filename, msg)
120 return None
121
122 return f
123
124 def skip(self, filename):
125 # We never want to handle symlinks, so always skip them: If the location
126 # pointed to is a directory, skip it. If the location is a file inside
127 # the gem5 directory, it will be checked as a file, so symlink can be
128 # skipped. If the location is a file outside gem5, we don't want to
129 # check it anyway.
130 if os.path.islink(filename):
131 return True
132 return lang_type(filename) not in self.languages
133
134 def apply(self, filename, regions=all_regions):
135 """Possibly apply to specified regions of file 'filename'.
136
137 Verifier is skipped if --skip-<test> option was provided or if
138 file is not of an applicable type. Otherwise file is checked
139 and error messages printed. Errors are fixed or ignored if
140 the corresponding --fix-<test> or --ignore-<test> options were
141 provided. If neither, the user is prompted for an action.
142
143 Returns True to abort, False otherwise.
144 """
145 if not (self.opt_skip or self.skip(filename)):
146 errors = self.check(filename, regions)
147 if errors and not self.opt_ignore:
148 if self.opt_fix:
149 self.fix(filename, regions)
150 else:
151 result = self.ui.prompt("(a)bort, (i)gnore, or (f)ix?",
152 'aif', 'a')
153 if result == 'f':
154 self.fix(filename, regions)
155 elif result == 'a':
156 return True # abort
157
158 return False
159
160 @abstractmethod
161 def check(self, filename, regions=all_regions):
162 """Check specified regions of file 'filename'.
163
164 Line-by-line checks can simply provide a check_line() method
165 that returns True if the line is OK and False if it has an
166 error. Verifiers that need a multi-line view (like
167 SortedIncludes) must override this entire function.
168
169 Returns a count of errors (0 if none), though actual non-zero
170 count value is not currently used anywhere.
171 """
172 pass
173
174 @abstractmethod
175 def fix(self, filename, regions=all_regions):
176 """Fix specified regions of file 'filename'.
177
178 Line-by-line fixes can simply provide a fix_line() method that
179 returns the fixed line. Verifiers that need a multi-line view
180 (like SortedIncludes) must override this entire function.
181 """
182 pass
183
184class LineVerifier(Verifier):
185 def check(self, filename, regions=all_regions):
186 f = self.open(filename, 'r')
187
188 errors = 0
189 for num,line in enumerate(f):
190 if num not in regions:
191 continue
192 line = line.rstrip('\n')
193 if not self.check_line(line):
194 self.ui.write("invalid %s in %s:%d\n" % \
195 (self.test_name, filename, num + 1))
196 if self.ui.verbose:
197 self.ui.write(">>%s<<\n" % line[:-1])
198 errors += 1
199 return errors
200
201 def fix(self, filename, regions=all_regions):
202 f = self.open(filename, 'r+')
203
204 lines = list(f)
205
206 f.seek(0)
207 f.truncate()
208
209 for i,line in enumerate(lines):
210 line = line.rstrip('\n')
211 if i in regions:
212 line = self.fix_line(line)
213
214 f.write(line)
215 f.write("\n")
216 f.close()
217
218
219 @abstractmethod
220 def check_line(self, line):
221 pass
222
223 @abstractmethod
224 def fix_line(self, line):
225 pass
226
227class Whitespace(LineVerifier):
228 """Check whitespace.
229
230 Specifically:
231 - No tabs used for indent
232 - No trailing whitespace
233 """
234
235 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
236 test_name = 'whitespace'
237 opt_name = 'white'
238
239 _lead = re.compile(r'^([ \t]+)')
240 _trail = re.compile(r'([ \t]+)$')
241
242 def check_line(self, line):
243 match = Whitespace._lead.search(line)
244 if match and match.group(1).find('\t') != -1:
245 return False
246
247 match = Whitespace._trail.search(line)
248 if match:
249 return False
250
251 return True
252
253 def fix_line(self, line):
254 if Whitespace._lead.search(line):
255 newline = ''
256 for i,c in enumerate(line):
257 if c == ' ':
258 newline += ' '
259 elif c == '\t':
260 newline += ' ' * (tabsize - len(newline) % tabsize)
261 else:
262 newline += line[i:]
263 break
264
265 line = newline
266
267 return line.rstrip() + '\n'
268
269
270class SortedIncludes(Verifier):
271 """Check for proper sorting of include statements"""
272
273 languages = sort_includes.default_languages
274 test_name = 'include file order'
275 opt_name = 'include'
276
277 def __init__(self, *args, **kwargs):
278 super(SortedIncludes, self).__init__(*args, **kwargs)
279 self.sort_includes = sort_includes.SortIncludes()
280
281 def check(self, filename, regions=all_regions):
282 f = self.open(filename, 'r')
283 norm_fname = self.normalize_filename(filename)
284
285 old = [ l.rstrip('\n') for l in f.xreadlines() ]
286 f.close()
287
288 if len(old) == 0:
289 return 0
290
291 language = lang_type(filename, old[0])
292 new = list(self.sort_includes(old, norm_fname, language))
293
294 modified = _modified_regions(old, new) & regions
295
296 if modified:
297 self.ui.write("invalid sorting of includes in %s\n" % (filename))
298 if self.ui.verbose:
299 for start, end in modified.regions:
300 self.ui.write("bad region [%d, %d)\n" % (start, end))
301 return 1
302
303 return 0
304
305 def fix(self, filename, regions=all_regions):
306 f = self.open(filename, 'r+')
307
308 old = f.readlines()
309 lines = [ l.rstrip('\n') for l in old ]
310 language = lang_type(filename, lines[0])
311 sort_lines = list(self.sort_includes(lines, filename, language))
312 new = ''.join(line + '\n' for line in sort_lines)
313
314 f.seek(0)
315 f.truncate()
316
317 for i,line in enumerate(sort_lines):
318 f.write(line)
319 f.write('\n')
320 f.close()
321
322
323class ControlSpace(LineVerifier):
324 """Check for exactly one space after if/while/for"""
325
326 languages = set(('C', 'C++'))
327 test_name = 'spacing after if/while/for'
328 opt_name = 'control'
329
330 _any_control = re.compile(r'\b(if|while|for)([ \t]*)\(')
331
332 def check_line(self, line):
333 match = ControlSpace._any_control.search(line)
334 return not (match and match.group(2) != " ")
335
336 def fix_line(self, line):
337 new_line = _any_control.sub(r'\1 (', line)
338 return new_line
339
340
341class LineLength(LineVerifier):
342 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
343 test_name = 'line length'
344 opt_name = 'length'
345
346 def check_line(self, line):
347 return style.normalized_len(line) <= 79
348
349 def fix(self, filename, regions=all_regions):
350 self.ui.write("Warning: cannot automatically fix overly long lines.\n")
351
352 def fix_line(self, line):
353 pass
354
355class ControlCharacters(LineVerifier):
356 languages = set(('C', 'C++', 'swig', 'python', 'asm', 'isa', 'scons'))
357 test_name = 'control character'
358 opt_name = 'ascii'
359
360 valid = ('\n', '\t')
361 invalid = "".join([chr(i) for i in range(0, 0x20) if chr(i) not in valid])
362
363 def check_line(self, line):
364 return self.fix_line(line) == line
365
366 def fix_line(self, line):
367 return line.translate(None, ControlCharacters.invalid)
368
369class BoolCompare(LineVerifier):
370 languages = set(('C', 'C++', 'python'))
371 test_name = 'boolean comparison'
372 opt_name = 'boolcomp'
373
374 regex = re.compile(r'\s*==\s*([Tt]rue|[Ff]alse)\b')
375
376 def check_line(self, line):
377 return self.regex.search(line) == None
378
379 def fix_line(self, line):
380 match = self.regex.search(line)
381 if match:
382 if match.group(1) in ('true', 'True'):
383 line = self.regex.sub('', line)
384 else:
385 self.ui.write("Warning: cannot automatically fix "
386 "comparisons with false/False.\n")
387 return line
388
389def is_verifier(cls):
390 """Determine if a class is a Verifier that can be instantiated"""
391
392 return inspect.isclass(cls) and issubclass(cls, Verifier) and \
393 not inspect.isabstract(cls)
394
395# list of all verifier classes
396all_verifiers = [ v for n, v in \
397 inspect.getmembers(sys.modules[__name__], is_verifier) ]