|
1 # Copyright (C) 2009 Google Inc. All rights reserved. |
|
2 # Copyright (C) 2010 Chris Jerdonek (chris.jerdonek@gmail.com) |
|
3 # Copyright (C) 2010 ProFUSION embedded systems |
|
4 # |
|
5 # Redistribution and use in source and binary forms, with or without |
|
6 # modification, are permitted provided that the following conditions are |
|
7 # met: |
|
8 # |
|
9 # * Redistributions of source code must retain the above copyright |
|
10 # notice, this list of conditions and the following disclaimer. |
|
11 # * Redistributions in binary form must reproduce the above |
|
12 # copyright notice, this list of conditions and the following disclaimer |
|
13 # in the documentation and/or other materials provided with the |
|
14 # distribution. |
|
15 # * Neither the name of Google Inc. nor the names of its |
|
16 # contributors may be used to endorse or promote products derived from |
|
17 # this software without specific prior written permission. |
|
18 # |
|
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
|
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
|
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
|
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
|
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
|
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
|
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
|
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
30 |
|
31 """Front end of some style-checker modules.""" |
|
32 |
|
33 import logging |
|
34 import os.path |
|
35 import sys |
|
36 |
|
37 from checkers.common import categories as CommonCategories |
|
38 from checkers.common import CarriageReturnChecker |
|
39 from checkers.cpp import CppChecker |
|
40 from checkers.python import PythonChecker |
|
41 from checkers.text import TextChecker |
|
42 from error_handlers import DefaultStyleErrorHandler |
|
43 from filter import FilterConfiguration |
|
44 from optparser import ArgumentParser |
|
45 from optparser import DefaultCommandOptionValues |
|
46 from webkitpy.style_references import configure_logging as _configure_logging |
|
47 |
|
48 _log = logging.getLogger("webkitpy.style.checker") |
|
49 |
|
50 # These are default option values for the command-line option parser. |
|
51 _DEFAULT_MIN_CONFIDENCE = 1 |
|
52 _DEFAULT_OUTPUT_FORMAT = 'emacs' |
|
53 |
|
54 |
|
55 # FIXME: For style categories we will never want to have, remove them. |
|
56 # For categories for which we want to have similar functionality, |
|
57 # modify the implementation and enable them. |
|
58 # |
|
59 # Throughout this module, we use "filter rule" rather than "filter" |
|
60 # for an individual boolean filter flag like "+foo". This allows us to |
|
61 # reserve "filter" for what one gets by collectively applying all of |
|
62 # the filter rules. |
|
63 # |
|
64 # The base filter rules are the filter rules that begin the list of |
|
65 # filter rules used to check style. For example, these rules precede |
|
66 # any user-specified filter rules. Since by default all categories are |
|
67 # checked, this list should normally include only rules that begin |
|
68 # with a "-" sign. |
|
69 _BASE_FILTER_RULES = [ |
|
70 '-build/endif_comment', |
|
71 '-build/include_what_you_use', # <string> for std::string |
|
72 '-build/storage_class', # const static |
|
73 '-legal/copyright', |
|
74 '-readability/multiline_comment', |
|
75 '-readability/braces', # int foo() {}; |
|
76 '-readability/fn_size', |
|
77 '-readability/casting', |
|
78 '-readability/function', |
|
79 '-runtime/arrays', # variable length array |
|
80 '-runtime/casting', |
|
81 '-runtime/sizeof', |
|
82 '-runtime/explicit', # explicit |
|
83 '-runtime/virtual', # virtual dtor |
|
84 '-runtime/printf', |
|
85 '-runtime/threadsafe_fn', |
|
86 '-runtime/rtti', |
|
87 '-whitespace/blank_line', |
|
88 '-whitespace/end_of_line', |
|
89 '-whitespace/labels', |
|
90 # List Python pep8 categories last. |
|
91 # |
|
92 # Because much of WebKit's Python code base does not abide by the |
|
93 # PEP8 79 character limit, we ignore the 79-character-limit category |
|
94 # pep8/E501 for now. |
|
95 # |
|
96 # FIXME: Consider bringing WebKit's Python code base into conformance |
|
97 # with the 79 character limit, or some higher limit that is |
|
98 # agreeable to the WebKit project. |
|
99 '-pep8/E501', |
|
100 ] |
|
101 |
|
102 |
|
103 # The path-specific filter rules. |
|
104 # |
|
105 # This list is order sensitive. Only the first path substring match |
|
106 # is used. See the FilterConfiguration documentation in filter.py |
|
107 # for more information on this list. |
|
108 # |
|
109 # Each string appearing in this nested list should have at least |
|
110 # one associated unit test assertion. These assertions are located, |
|
111 # for example, in the test_path_rules_specifier() unit test method of |
|
112 # checker_unittest.py. |
|
113 _PATH_RULES_SPECIFIER = [ |
|
114 # Files in these directories are consumers of the WebKit |
|
115 # API and therefore do not follow the same header including |
|
116 # discipline as WebCore. |
|
117 (["WebKitTools/WebKitAPITest/", |
|
118 "WebKit/qt/QGVLauncher/"], |
|
119 ["-build/include", |
|
120 "-readability/streams"]), |
|
121 ([# The EFL APIs use EFL naming style, which includes |
|
122 # both lower-cased and camel-cased, underscore-sparated |
|
123 # values. |
|
124 "WebKit/efl/ewk/", |
|
125 # There is no clean way to avoid "yy_*" names used by flex. |
|
126 "WebCore/css/CSSParser.cpp", |
|
127 # There is no clean way to avoid "xxx_data" methods inside |
|
128 # Qt's autotests since they are called automatically by the |
|
129 # QtTest module. |
|
130 "WebKit/qt/tests/", |
|
131 "JavaScriptCore/qt/tests"], |
|
132 ["-readability/naming"]), |
|
133 ([# The GTK+ APIs use GTK+ naming style, which includes |
|
134 # lower-cased, underscore-separated values. |
|
135 # Also, GTK+ allows the use of NULL. |
|
136 "WebKit/gtk/webkit/", |
|
137 "WebKitTools/DumpRenderTree/gtk/"], |
|
138 ["-readability/naming", |
|
139 "-readability/null"]), |
|
140 ([# Header files in ForwardingHeaders have no header guards or |
|
141 # exceptional header guards (e.g., WebCore_FWD_Debugger_h). |
|
142 "/ForwardingHeaders/"], |
|
143 ["-build/header_guard"]), |
|
144 |
|
145 # For third-party Python code, keep only the following checks-- |
|
146 # |
|
147 # No tabs: to avoid having to set the SVN allow-tabs property. |
|
148 # No trailing white space: since this is easy to correct. |
|
149 # No carriage-return line endings: since this is easy to correct. |
|
150 # |
|
151 (["webkitpy/thirdparty/"], |
|
152 ["-", |
|
153 "+pep8/W191", # Tabs |
|
154 "+pep8/W291", # Trailing white space |
|
155 "+whitespace/carriage_return"]), |
|
156 ] |
|
157 |
|
158 |
|
159 _CPP_FILE_EXTENSIONS = [ |
|
160 'c', |
|
161 'cpp', |
|
162 'h', |
|
163 ] |
|
164 |
|
165 _PYTHON_FILE_EXTENSION = 'py' |
|
166 |
|
167 # FIXME: Include 'vcproj' files as text files after creating a mechanism |
|
168 # for exempting them from the carriage-return checker (since they |
|
169 # are Windows-only files). |
|
170 _TEXT_FILE_EXTENSIONS = [ |
|
171 'ac', |
|
172 'cc', |
|
173 'cgi', |
|
174 'css', |
|
175 'exp', |
|
176 'flex', |
|
177 'gyp', |
|
178 'gypi', |
|
179 'html', |
|
180 'idl', |
|
181 'in', |
|
182 'js', |
|
183 'mm', |
|
184 'php', |
|
185 'pl', |
|
186 'pm', |
|
187 'pri', |
|
188 'pro', |
|
189 'rb', |
|
190 'sh', |
|
191 'txt', |
|
192 # 'vcproj', # See FIXME above. |
|
193 'wm', |
|
194 'xhtml', |
|
195 'y', |
|
196 ] |
|
197 |
|
198 |
|
199 # Files to skip that are less obvious. |
|
200 # |
|
201 # Some files should be skipped when checking style. For example, |
|
202 # WebKit maintains some files in Mozilla style on purpose to ease |
|
203 # future merges. |
|
204 _SKIPPED_FILES_WITH_WARNING = [ |
|
205 # The Qt API and tests do not follow WebKit style. |
|
206 # They follow Qt style. :) |
|
207 "gtk2drawing.c", # WebCore/platform/gtk/gtk2drawing.c |
|
208 "gtkdrawing.h", # WebCore/platform/gtk/gtkdrawing.h |
|
209 "JavaScriptCore/qt/api/", |
|
210 "WebKit/gtk/tests/", |
|
211 "WebKit/qt/Api/", |
|
212 "WebKit/qt/tests/", |
|
213 "WebKit/qt/examples/", |
|
214 ] |
|
215 |
|
216 |
|
217 # Files to skip that are more common or obvious. |
|
218 # |
|
219 # This list should be in addition to files with FileType.NONE. Files |
|
220 # with FileType.NONE are automatically skipped without warning. |
|
221 _SKIPPED_FILES_WITHOUT_WARNING = [ |
|
222 "LayoutTests/", |
|
223 ] |
|
224 |
|
225 |
|
226 # The maximum number of errors to report per file, per category. |
|
227 # If a category is not a key, then it has no maximum. |
|
228 _MAX_REPORTS_PER_CATEGORY = { |
|
229 "whitespace/carriage_return": 1 |
|
230 } |
|
231 |
|
232 |
|
233 def _all_categories(): |
|
234 """Return the set of all categories used by check-webkit-style.""" |
|
235 # Take the union across all checkers. |
|
236 categories = CommonCategories.union(CppChecker.categories) |
|
237 |
|
238 # FIXME: Consider adding all of the pep8 categories. Since they |
|
239 # are not too meaningful for documentation purposes, for |
|
240 # now we add only the categories needed for the unit tests |
|
241 # (which validate the consistency of the configuration |
|
242 # settings against the known categories, etc). |
|
243 categories = categories.union(["pep8/W191", "pep8/W291", "pep8/E501"]) |
|
244 |
|
245 return categories |
|
246 |
|
247 |
|
248 def _check_webkit_style_defaults(): |
|
249 """Return the default command-line options for check-webkit-style.""" |
|
250 return DefaultCommandOptionValues(min_confidence=_DEFAULT_MIN_CONFIDENCE, |
|
251 output_format=_DEFAULT_OUTPUT_FORMAT) |
|
252 |
|
253 |
|
254 # This function assists in optparser not having to import from checker. |
|
255 def check_webkit_style_parser(): |
|
256 all_categories = _all_categories() |
|
257 default_options = _check_webkit_style_defaults() |
|
258 return ArgumentParser(all_categories=all_categories, |
|
259 base_filter_rules=_BASE_FILTER_RULES, |
|
260 default_options=default_options) |
|
261 |
|
262 |
|
263 def check_webkit_style_configuration(options): |
|
264 """Return a StyleProcessorConfiguration instance for check-webkit-style. |
|
265 |
|
266 Args: |
|
267 options: A CommandOptionValues instance. |
|
268 |
|
269 """ |
|
270 filter_configuration = FilterConfiguration( |
|
271 base_rules=_BASE_FILTER_RULES, |
|
272 path_specific=_PATH_RULES_SPECIFIER, |
|
273 user_rules=options.filter_rules) |
|
274 |
|
275 return StyleProcessorConfiguration(filter_configuration=filter_configuration, |
|
276 max_reports_per_category=_MAX_REPORTS_PER_CATEGORY, |
|
277 min_confidence=options.min_confidence, |
|
278 output_format=options.output_format, |
|
279 stderr_write=sys.stderr.write) |
|
280 |
|
281 |
|
282 def _create_log_handlers(stream): |
|
283 """Create and return a default list of logging.Handler instances. |
|
284 |
|
285 Format WARNING messages and above to display the logging level, and |
|
286 messages strictly below WARNING not to display it. |
|
287 |
|
288 Args: |
|
289 stream: See the configure_logging() docstring. |
|
290 |
|
291 """ |
|
292 # Handles logging.WARNING and above. |
|
293 error_handler = logging.StreamHandler(stream) |
|
294 error_handler.setLevel(logging.WARNING) |
|
295 formatter = logging.Formatter("%(levelname)s: %(message)s") |
|
296 error_handler.setFormatter(formatter) |
|
297 |
|
298 # Create a logging.Filter instance that only accepts messages |
|
299 # below WARNING (i.e. filters out anything WARNING or above). |
|
300 non_error_filter = logging.Filter() |
|
301 # The filter method accepts a logging.LogRecord instance. |
|
302 non_error_filter.filter = lambda record: record.levelno < logging.WARNING |
|
303 |
|
304 non_error_handler = logging.StreamHandler(stream) |
|
305 non_error_handler.addFilter(non_error_filter) |
|
306 formatter = logging.Formatter("%(message)s") |
|
307 non_error_handler.setFormatter(formatter) |
|
308 |
|
309 return [error_handler, non_error_handler] |
|
310 |
|
311 |
|
312 def _create_debug_log_handlers(stream): |
|
313 """Create and return a list of logging.Handler instances for debugging. |
|
314 |
|
315 Args: |
|
316 stream: See the configure_logging() docstring. |
|
317 |
|
318 """ |
|
319 handler = logging.StreamHandler(stream) |
|
320 formatter = logging.Formatter("%(name)s: %(levelname)-8s %(message)s") |
|
321 handler.setFormatter(formatter) |
|
322 |
|
323 return [handler] |
|
324 |
|
325 |
|
326 def configure_logging(stream, logger=None, is_verbose=False): |
|
327 """Configure logging, and return the list of handlers added. |
|
328 |
|
329 Returns: |
|
330 A list of references to the logging handlers added to the root |
|
331 logger. This allows the caller to later remove the handlers |
|
332 using logger.removeHandler. This is useful primarily during unit |
|
333 testing where the caller may want to configure logging temporarily |
|
334 and then undo the configuring. |
|
335 |
|
336 Args: |
|
337 stream: A file-like object to which to log. The stream must |
|
338 define an "encoding" data attribute, or else logging |
|
339 raises an error. |
|
340 logger: A logging.logger instance to configure. This parameter |
|
341 should be used only in unit tests. Defaults to the |
|
342 root logger. |
|
343 is_verbose: A boolean value of whether logging should be verbose. |
|
344 |
|
345 """ |
|
346 # If the stream does not define an "encoding" data attribute, the |
|
347 # logging module can throw an error like the following: |
|
348 # |
|
349 # Traceback (most recent call last): |
|
350 # File "/System/Library/Frameworks/Python.framework/Versions/2.6/... |
|
351 # lib/python2.6/logging/__init__.py", line 761, in emit |
|
352 # self.stream.write(fs % msg.encode(self.stream.encoding)) |
|
353 # LookupError: unknown encoding: unknown |
|
354 if logger is None: |
|
355 logger = logging.getLogger() |
|
356 |
|
357 if is_verbose: |
|
358 logging_level = logging.DEBUG |
|
359 handlers = _create_debug_log_handlers(stream) |
|
360 else: |
|
361 logging_level = logging.INFO |
|
362 handlers = _create_log_handlers(stream) |
|
363 |
|
364 handlers = _configure_logging(logging_level=logging_level, logger=logger, |
|
365 handlers=handlers) |
|
366 |
|
367 return handlers |
|
368 |
|
369 |
|
370 # Enum-like idiom |
|
371 class FileType: |
|
372 |
|
373 NONE = 0 # FileType.NONE evaluates to False. |
|
374 # Alphabetize remaining types |
|
375 CPP = 1 |
|
376 PYTHON = 2 |
|
377 TEXT = 3 |
|
378 |
|
379 |
|
380 class CheckerDispatcher(object): |
|
381 |
|
382 """Supports determining whether and how to check style, based on path.""" |
|
383 |
|
384 def _file_extension(self, file_path): |
|
385 """Return the file extension without the leading dot.""" |
|
386 return os.path.splitext(file_path)[1].lstrip(".") |
|
387 |
|
388 def should_skip_with_warning(self, file_path): |
|
389 """Return whether the given file should be skipped with a warning.""" |
|
390 for skipped_file in _SKIPPED_FILES_WITH_WARNING: |
|
391 if file_path.find(skipped_file) >= 0: |
|
392 return True |
|
393 return False |
|
394 |
|
395 def should_skip_without_warning(self, file_path): |
|
396 """Return whether the given file should be skipped without a warning.""" |
|
397 if not self._file_type(file_path): # FileType.NONE. |
|
398 return True |
|
399 # Since "LayoutTests" is in _SKIPPED_FILES_WITHOUT_WARNING, make |
|
400 # an exception to prevent files like "LayoutTests/ChangeLog" and |
|
401 # "LayoutTests/ChangeLog-2009-06-16" from being skipped. |
|
402 # |
|
403 # FIXME: Figure out a good way to avoid having to add special logic |
|
404 # for this special case. |
|
405 if os.path.basename(file_path).startswith('ChangeLog'): |
|
406 return False |
|
407 for skipped_file in _SKIPPED_FILES_WITHOUT_WARNING: |
|
408 if file_path.find(skipped_file) >= 0: |
|
409 return True |
|
410 return False |
|
411 |
|
412 def _file_type(self, file_path): |
|
413 """Return the file type corresponding to the given file.""" |
|
414 file_extension = self._file_extension(file_path) |
|
415 |
|
416 if (file_extension in _CPP_FILE_EXTENSIONS) or (file_path == '-'): |
|
417 # FIXME: Do something about the comment below and the issue it |
|
418 # raises since cpp_style already relies on the extension. |
|
419 # |
|
420 # Treat stdin as C++. Since the extension is unknown when |
|
421 # reading from stdin, cpp_style tests should not rely on |
|
422 # the extension. |
|
423 return FileType.CPP |
|
424 elif file_extension == _PYTHON_FILE_EXTENSION: |
|
425 return FileType.PYTHON |
|
426 elif (os.path.basename(file_path).startswith('ChangeLog') or |
|
427 (not file_extension and "WebKitTools/Scripts/" in file_path) or |
|
428 file_extension in _TEXT_FILE_EXTENSIONS): |
|
429 return FileType.TEXT |
|
430 else: |
|
431 return FileType.NONE |
|
432 |
|
433 def _create_checker(self, file_type, file_path, handle_style_error, |
|
434 min_confidence): |
|
435 """Instantiate and return a style checker based on file type.""" |
|
436 if file_type == FileType.NONE: |
|
437 checker = None |
|
438 elif file_type == FileType.CPP: |
|
439 file_extension = self._file_extension(file_path) |
|
440 checker = CppChecker(file_path, file_extension, |
|
441 handle_style_error, min_confidence) |
|
442 elif file_type == FileType.PYTHON: |
|
443 checker = PythonChecker(file_path, handle_style_error) |
|
444 elif file_type == FileType.TEXT: |
|
445 checker = TextChecker(file_path, handle_style_error) |
|
446 else: |
|
447 raise ValueError('Invalid file type "%(file_type)s": the only valid file types ' |
|
448 "are %(NONE)s, %(CPP)s, and %(TEXT)s." |
|
449 % {"file_type": file_type, |
|
450 "NONE": FileType.NONE, |
|
451 "CPP": FileType.CPP, |
|
452 "TEXT": FileType.TEXT}) |
|
453 |
|
454 return checker |
|
455 |
|
456 def dispatch(self, file_path, handle_style_error, min_confidence): |
|
457 """Instantiate and return a style checker based on file path.""" |
|
458 file_type = self._file_type(file_path) |
|
459 |
|
460 checker = self._create_checker(file_type, |
|
461 file_path, |
|
462 handle_style_error, |
|
463 min_confidence) |
|
464 return checker |
|
465 |
|
466 |
|
467 # FIXME: Remove the stderr_write attribute from this class and replace |
|
468 # its use with calls to a logging module logger. |
|
469 class StyleProcessorConfiguration(object): |
|
470 |
|
471 """Stores configuration values for the StyleProcessor class. |
|
472 |
|
473 Attributes: |
|
474 min_confidence: An integer between 1 and 5 inclusive that is the |
|
475 minimum confidence level of style errors to report. |
|
476 |
|
477 max_reports_per_category: The maximum number of errors to report |
|
478 per category, per file. |
|
479 |
|
480 stderr_write: A function that takes a string as a parameter and |
|
481 serves as stderr.write. |
|
482 |
|
483 """ |
|
484 |
|
485 def __init__(self, |
|
486 filter_configuration, |
|
487 max_reports_per_category, |
|
488 min_confidence, |
|
489 output_format, |
|
490 stderr_write): |
|
491 """Create a StyleProcessorConfiguration instance. |
|
492 |
|
493 Args: |
|
494 filter_configuration: A FilterConfiguration instance. The default |
|
495 is the "empty" filter configuration, which |
|
496 means that all errors should be checked. |
|
497 |
|
498 max_reports_per_category: The maximum number of errors to report |
|
499 per category, per file. |
|
500 |
|
501 min_confidence: An integer between 1 and 5 inclusive that is the |
|
502 minimum confidence level of style errors to report. |
|
503 The default is 1, which reports all style errors. |
|
504 |
|
505 output_format: A string that is the output format. The supported |
|
506 output formats are "emacs" which emacs can parse |
|
507 and "vs7" which Microsoft Visual Studio 7 can parse. |
|
508 |
|
509 stderr_write: A function that takes a string as a parameter and |
|
510 serves as stderr.write. |
|
511 |
|
512 """ |
|
513 self._filter_configuration = filter_configuration |
|
514 self._output_format = output_format |
|
515 |
|
516 self.max_reports_per_category = max_reports_per_category |
|
517 self.min_confidence = min_confidence |
|
518 self.stderr_write = stderr_write |
|
519 |
|
520 def is_reportable(self, category, confidence_in_error, file_path): |
|
521 """Return whether an error is reportable. |
|
522 |
|
523 An error is reportable if both the confidence in the error is |
|
524 at least the minimum confidence level and the current filter |
|
525 says the category should be checked for the given path. |
|
526 |
|
527 Args: |
|
528 category: A string that is a style category. |
|
529 confidence_in_error: An integer between 1 and 5 inclusive that is |
|
530 the application's confidence in the error. |
|
531 A higher number means greater confidence. |
|
532 file_path: The path of the file being checked |
|
533 |
|
534 """ |
|
535 if confidence_in_error < self.min_confidence: |
|
536 return False |
|
537 |
|
538 return self._filter_configuration.should_check(category, file_path) |
|
539 |
|
540 def write_style_error(self, |
|
541 category, |
|
542 confidence_in_error, |
|
543 file_path, |
|
544 line_number, |
|
545 message): |
|
546 """Write a style error to the configured stderr.""" |
|
547 if self._output_format == 'vs7': |
|
548 format_string = "%s(%s): %s [%s] [%d]\n" |
|
549 else: |
|
550 format_string = "%s:%s: %s [%s] [%d]\n" |
|
551 |
|
552 self.stderr_write(format_string % (file_path, |
|
553 line_number, |
|
554 message, |
|
555 category, |
|
556 confidence_in_error)) |
|
557 |
|
558 |
|
559 class ProcessorBase(object): |
|
560 |
|
561 """The base class for processors of lists of lines.""" |
|
562 |
|
563 def should_process(self, file_path): |
|
564 """Return whether the file at file_path should be processed. |
|
565 |
|
566 The TextFileReader class calls this method prior to reading in |
|
567 the lines of a file. Use this method, for example, to prevent |
|
568 the style checker from reading binary files into memory. |
|
569 |
|
570 """ |
|
571 raise NotImplementedError('Subclasses should implement.') |
|
572 |
|
573 def process(self, lines, file_path, **kwargs): |
|
574 """Process lines of text read from a file. |
|
575 |
|
576 Args: |
|
577 lines: A list of lines of text to process. |
|
578 file_path: The path from which the lines were read. |
|
579 **kwargs: This argument signifies that the process() method of |
|
580 subclasses of ProcessorBase may support additional |
|
581 keyword arguments. |
|
582 For example, a style checker's check() method |
|
583 may support a "reportable_lines" parameter that represents |
|
584 the line numbers of the lines for which style errors |
|
585 should be reported. |
|
586 |
|
587 """ |
|
588 raise NotImplementedError('Subclasses should implement.') |
|
589 |
|
590 |
|
591 class StyleProcessor(ProcessorBase): |
|
592 |
|
593 """A ProcessorBase for checking style. |
|
594 |
|
595 Attributes: |
|
596 error_count: An integer that is the total number of reported |
|
597 errors for the lifetime of this instance. |
|
598 |
|
599 """ |
|
600 |
|
601 def __init__(self, configuration, mock_dispatcher=None, |
|
602 mock_increment_error_count=None, |
|
603 mock_carriage_checker_class=None): |
|
604 """Create an instance. |
|
605 |
|
606 Args: |
|
607 configuration: A StyleProcessorConfiguration instance. |
|
608 mock_dispatcher: A mock CheckerDispatcher instance. This |
|
609 parameter is for unit testing. Defaults to a |
|
610 CheckerDispatcher instance. |
|
611 mock_increment_error_count: A mock error-count incrementer. |
|
612 mock_carriage_checker_class: A mock class for checking and |
|
613 transforming carriage returns. |
|
614 This parameter is for unit testing. |
|
615 Defaults to CarriageReturnChecker. |
|
616 |
|
617 """ |
|
618 if mock_dispatcher is None: |
|
619 dispatcher = CheckerDispatcher() |
|
620 else: |
|
621 dispatcher = mock_dispatcher |
|
622 |
|
623 if mock_increment_error_count is None: |
|
624 # The following blank line is present to avoid flagging by pep8.py. |
|
625 |
|
626 def increment_error_count(): |
|
627 """Increment the total count of reported errors.""" |
|
628 self.error_count += 1 |
|
629 else: |
|
630 increment_error_count = mock_increment_error_count |
|
631 |
|
632 if mock_carriage_checker_class is None: |
|
633 # This needs to be a class rather than an instance since the |
|
634 # process() method instantiates one using parameters. |
|
635 carriage_checker_class = CarriageReturnChecker |
|
636 else: |
|
637 carriage_checker_class = mock_carriage_checker_class |
|
638 |
|
639 self.error_count = 0 |
|
640 |
|
641 self._carriage_checker_class = carriage_checker_class |
|
642 self._configuration = configuration |
|
643 self._dispatcher = dispatcher |
|
644 self._increment_error_count = increment_error_count |
|
645 |
|
646 def should_process(self, file_path): |
|
647 """Return whether the file should be checked for style.""" |
|
648 if self._dispatcher.should_skip_without_warning(file_path): |
|
649 return False |
|
650 if self._dispatcher.should_skip_with_warning(file_path): |
|
651 _log.warn('File exempt from style guide. Skipping: "%s"' |
|
652 % file_path) |
|
653 return False |
|
654 return True |
|
655 |
|
656 def process(self, lines, file_path, line_numbers=None): |
|
657 """Check the given lines for style. |
|
658 |
|
659 Arguments: |
|
660 lines: A list of all lines in the file to check. |
|
661 file_path: The path of the file to process. If possible, the path |
|
662 should be relative to the source root. Otherwise, |
|
663 path-specific logic may not behave as expected. |
|
664 line_numbers: A list of line numbers of the lines for which |
|
665 style errors should be reported, or None if errors |
|
666 for all lines should be reported. When not None, this |
|
667 list normally contains the line numbers corresponding |
|
668 to the modified lines of a patch. |
|
669 |
|
670 """ |
|
671 _log.debug("Checking style: " + file_path) |
|
672 |
|
673 style_error_handler = DefaultStyleErrorHandler( |
|
674 configuration=self._configuration, |
|
675 file_path=file_path, |
|
676 increment_error_count=self._increment_error_count, |
|
677 line_numbers=line_numbers) |
|
678 |
|
679 carriage_checker = self._carriage_checker_class(style_error_handler) |
|
680 |
|
681 # FIXME: We should probably use the SVN "eol-style" property |
|
682 # or a white list to decide whether or not to do |
|
683 # the carriage-return check. Originally, we did the |
|
684 # check only if (os.linesep != '\r\n'). |
|
685 # |
|
686 # Check for and remove trailing carriage returns ("\r"). |
|
687 lines = carriage_checker.check(lines) |
|
688 |
|
689 min_confidence = self._configuration.min_confidence |
|
690 checker = self._dispatcher.dispatch(file_path, |
|
691 style_error_handler, |
|
692 min_confidence) |
|
693 |
|
694 if checker is None: |
|
695 raise AssertionError("File should not be checked: '%s'" % file_path) |
|
696 |
|
697 _log.debug("Using class: " + checker.__class__.__name__) |
|
698 |
|
699 checker.check(lines) |