PageRenderTime 130ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/hooks/webkitpy/style/checker.py

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