OLD | NEW |
1 # Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). |
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | 2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # | 3 # |
4 # This program is free software; you can redistribute it and/or modify it under | 4 # This program is free software; you can redistribute it and/or modify it under |
5 # the terms of the GNU General Public License as published by the Free Software | 5 # the terms of the GNU General Public License as published by the Free Software |
6 # Foundation; either version 2 of the License, or (at your option) any later | 6 # Foundation; either version 2 of the License, or (at your option) any later |
7 # version. | 7 # version. |
8 # | 8 # |
9 # This program is distributed in the hope that it will be useful, but WITHOUT | 9 # This program is distributed in the hope that it will be useful, but WITHOUT |
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details | 11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details |
12 # | 12 # |
13 # You should have received a copy of the GNU General Public License along with | 13 # You should have received a copy of the GNU General Public License along with |
14 # this program; if not, write to the Free Software Foundation, Inc., | 14 # this program; if not, write to the Free Software Foundation, Inc., |
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 """ %prog [options] module_or_package | 16 """ %prog [options] module_or_package |
17 | 17 |
18 Check that a module satisfies a coding standard (and more !). | 18 Check that a module satisfies a coding standard (and more !). |
19 | 19 |
20 %prog --help | 20 %prog --help |
21 | 21 |
22 Display this help message and exit. | 22 Display this help message and exit. |
23 | 23 |
24 %prog --help-msg <msg-id>[,<msg-id>] | 24 %prog --help-msg <msg-id>[,<msg-id>] |
25 | 25 |
26 Display help messages about given message identifiers and exit. | 26 Display help messages about given message identifiers and exit. |
27 """ | 27 """ |
28 from __future__ import print_function | 28 from __future__ import print_function |
29 | 29 |
30 # import this first to avoid builtin namespace pollution | 30 import collections |
31 from pylint.checkers import utils #pylint: disable=unused-import | 31 import contextlib |
32 | 32 import itertools |
33 import sys | 33 import operator |
34 import os | 34 import os |
35 import tokenize | |
36 from collections import defaultdict | |
37 from contextlib import contextmanager | |
38 from operator import attrgetter | |
39 from warnings import warn | |
40 from itertools import chain | |
41 try: | 35 try: |
42 import multiprocessing | 36 import multiprocessing |
43 except ImportError: | 37 except ImportError: |
44 multiprocessing = None | 38 multiprocessing = None |
| 39 import sys |
| 40 import tokenize |
| 41 import warnings |
45 | 42 |
| 43 import astroid |
| 44 from astroid.__pkginfo__ import version as astroid_version |
| 45 from astroid import modutils |
| 46 from logilab.common import configuration |
| 47 from logilab.common import optik_ext |
| 48 from logilab.common import interface |
| 49 from logilab.common import textutils |
| 50 from logilab.common import ureports |
| 51 from logilab.common.__pkginfo__ import version as common_version |
46 import six | 52 import six |
47 from logilab.common.configuration import ( | |
48 UnsupportedAction, OptionsManagerMixIn) | |
49 from logilab.common.optik_ext import check_csv | |
50 from logilab.common.interface import implements | |
51 from logilab.common.textutils import splitstrip, unquote | |
52 from logilab.common.ureports import Table, Text, Section | |
53 from logilab.common.__pkginfo__ import version as common_version | |
54 from astroid import MANAGER, AstroidBuildingException | |
55 from astroid.__pkginfo__ import version as astroid_version | |
56 from astroid.modutils import load_module_from_name, get_module_part | |
57 | 53 |
58 from pylint.utils import ( | 54 from pylint import checkers |
59 MSG_TYPES, OPTION_RGX, | 55 from pylint import interfaces |
60 PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn, | 56 from pylint import reporters |
61 MessagesStore, FileState, EmptyReport, | 57 from pylint import utils |
62 expand_modules, tokenize_module, Message) | |
63 from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker, CONFI
DENCE_LEVELS | |
64 from pylint.checkers import (BaseTokenChecker, | |
65 table_lines_from_stats, | |
66 initialize as checkers_initialize) | |
67 from pylint.reporters import initialize as reporters_initialize, CollectingRepor
ter | |
68 from pylint import config | 58 from pylint import config |
69 from pylint.__pkginfo__ import version | 59 from pylint.__pkginfo__ import version |
70 | 60 |
71 | 61 |
| 62 MANAGER = astroid.MANAGER |
| 63 |
72 def _get_new_args(message): | 64 def _get_new_args(message): |
73 location = ( | 65 location = ( |
74 message.abspath, | 66 message.abspath, |
75 message.path, | 67 message.path, |
76 message.module, | 68 message.module, |
77 message.obj, | 69 message.obj, |
78 message.line, | 70 message.line, |
79 message.column, | 71 message.column, |
80 ) | 72 ) |
81 return ( | 73 return ( |
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
188 def _deprecated_option(shortname, opt_type): | 180 def _deprecated_option(shortname, opt_type): |
189 def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argum
ent | 181 def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argum
ent |
190 sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (op
tname,)) | 182 sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (op
tname,)) |
191 return {'short': shortname, 'help': 'DEPRECATED', 'hide': True, | 183 return {'short': shortname, 'help': 'DEPRECATED', 'hide': True, |
192 'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated
} | 184 'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated
} |
193 | 185 |
194 | 186 |
195 if multiprocessing is not None: | 187 if multiprocessing is not None: |
196 class ChildLinter(multiprocessing.Process): # pylint: disable=no-member | 188 class ChildLinter(multiprocessing.Process): # pylint: disable=no-member |
197 def run(self): | 189 def run(self): |
198 tasks_queue, results_queue, config = self._args # pylint: disable=no
-member | 190 tasks_queue, results_queue, self._config = self._args # pylint: disa
ble=no-member |
199 | 191 |
| 192 self._config["jobs"] = 1 # Child does not parallelize any further. |
| 193 |
| 194 # Run linter for received files/modules. |
200 for file_or_module in iter(tasks_queue.get, 'STOP'): | 195 for file_or_module in iter(tasks_queue.get, 'STOP'): |
201 result = self._run_linter(config, file_or_module[0]) | 196 result = self._run_linter(file_or_module[0]) |
202 try: | 197 try: |
203 results_queue.put(result) | 198 results_queue.put(result) |
204 except Exception as ex: | 199 except Exception as ex: |
205 print("internal error with sending report for module %s" % f
ile_or_module, file=sys.stderr) | 200 print("internal error with sending report for module %s" % f
ile_or_module, file=sys.stderr) |
206 print(ex, file=sys.stderr) | 201 print(ex, file=sys.stderr) |
207 results_queue.put({}) | 202 results_queue.put({}) |
208 | 203 |
209 def _run_linter(self, config, file_or_module): | 204 def _run_linter(self, file_or_module): |
210 linter = PyLinter() | 205 linter = PyLinter() |
211 | 206 |
212 # Register standard checkers. | 207 # Register standard checkers. |
213 linter.load_default_plugins() | 208 linter.load_default_plugins() |
214 # Load command line plugins. | 209 # Load command line plugins. |
215 # TODO linter.load_plugin_modules(self._plugins) | 210 # TODO linter.load_plugin_modules(self._plugins) |
216 | 211 |
217 linter.disable('pointless-except') | 212 linter.load_configuration(**self._config) |
218 linter.disable('suppressed-message') | 213 linter.set_reporter(reporters.CollectingReporter()) |
219 linter.disable('useless-suppression') | |
220 | |
221 # TODO(cpopa): the sub-linters will not know all the options | |
222 # because they are not available here, as they are patches to | |
223 # PyLinter options. The following is just a hack to handle | |
224 # just a part of the options available in the Run class. | |
225 | |
226 if 'disable_msg' in config: | |
227 # Disable everything again. We don't have access | |
228 # to the original linter though. | |
229 for msgid in config['disable_msg']: | |
230 linter.disable(msgid) | |
231 for key in set(config) - set(dict(linter.options)): | |
232 del config[key] | |
233 | |
234 config['jobs'] = 1 # Child does not parallelize any further. | |
235 linter.load_configuration(**config) | |
236 linter.set_reporter(CollectingReporter()) | |
237 | 214 |
238 # Run the checks. | 215 # Run the checks. |
239 linter.check(file_or_module) | 216 linter.check(file_or_module) |
240 | 217 |
241 msgs = [_get_new_args(m) for m in linter.reporter.messages] | 218 msgs = [_get_new_args(m) for m in linter.reporter.messages] |
242 return (file_or_module, linter.file_state.base_name, linter.current_
name, | 219 return (file_or_module, linter.file_state.base_name, linter.current_
name, |
243 msgs, linter.stats, linter.msg_status) | 220 msgs, linter.stats, linter.msg_status) |
244 | 221 |
245 | 222 |
246 class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, | 223 class PyLinter(configuration.OptionsManagerMixIn, |
247 BaseTokenChecker): | 224 utils.MessagesHandlerMixIn, |
| 225 utils.ReportsHandlerMixIn, |
| 226 checkers.BaseTokenChecker): |
248 """lint Python modules using external checkers. | 227 """lint Python modules using external checkers. |
249 | 228 |
250 This is the main checker controlling the other ones and the reports | 229 This is the main checker controlling the other ones and the reports |
251 generation. It is itself both a raw checker and an astroid checker in order | 230 generation. It is itself both a raw checker and an astroid checker in order |
252 to: | 231 to: |
253 * handle message activation / deactivation at the module level | 232 * handle message activation / deactivation at the module level |
254 * handle some basic but necessary stats'data (number of classes, methods...) | 233 * handle some basic but necessary stats'data (number of classes, methods...) |
255 | 234 |
256 IDE plugins developpers: you may have to call | 235 IDE plugins developpers: you may have to call |
257 `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want | 236 `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want |
258 to ensure the latest code version is actually checked. | 237 to ensure the latest code version is actually checked. |
259 """ | 238 """ |
260 | 239 |
261 __implements__ = (ITokenChecker,) | 240 __implements__ = (interfaces.ITokenChecker, ) |
262 | 241 |
263 name = 'master' | 242 name = 'master' |
264 priority = 0 | 243 priority = 0 |
265 level = 0 | 244 level = 0 |
266 msgs = MSGS | 245 msgs = MSGS |
267 | 246 |
268 @staticmethod | 247 @staticmethod |
269 def make_options(): | 248 def make_options(): |
270 return (('ignore', | 249 return (('ignore', |
271 {'type' : 'csv', 'metavar' : '<file>[,<file>...]', | 250 {'type' : 'csv', 'metavar' : '<file>[,<file>...]', |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
323 | 302 |
324 ('comment', | 303 ('comment', |
325 {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', | 304 {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', |
326 'group': 'Reports', 'level': 1, | 305 'group': 'Reports', 'level': 1, |
327 'help' : 'Add a comment according to your evaluation note. ' | 306 'help' : 'Add a comment according to your evaluation note. ' |
328 'This is used by the global evaluation report (RP0004
).'}), | 307 'This is used by the global evaluation report (RP0004
).'}), |
329 | 308 |
330 ('confidence', | 309 ('confidence', |
331 {'type' : 'multiple_choice', 'metavar': '<levels>', | 310 {'type' : 'multiple_choice', 'metavar': '<levels>', |
332 'default': '', | 311 'default': '', |
333 'choices': [c.name for c in CONFIDENCE_LEVELS], | 312 'choices': [c.name for c in interfaces.CONFIDENCE_LEVELS], |
334 'group': 'Messages control', | 313 'group': 'Messages control', |
335 'help' : 'Only show warnings with the listed confidence levels
.' | 314 'help' : 'Only show warnings with the listed confidence levels
.' |
336 ' Leave empty to show all. Valid levels: %s' % ( | 315 ' Leave empty to show all. Valid levels: %s' % ( |
337 ', '.join(c.name for c in CONFIDENCE_LEVELS),)}), | 316 ', '.join(c.name for c in interfaces.CONFIDENCE_L
EVELS),)}), |
338 | 317 |
339 ('enable', | 318 ('enable', |
340 {'type' : 'csv', 'metavar': '<msg ids>', | 319 {'type' : 'csv', 'metavar': '<msg ids>', |
341 'short': 'e', | 320 'short': 'e', |
342 'group': 'Messages control', | 321 'group': 'Messages control', |
343 'help' : 'Enable the message, report, category or checker with
the ' | 322 'help' : 'Enable the message, report, category or checker with
the ' |
344 'given id(s). You can either give multiple identifier
' | 323 'given id(s). You can either give multiple identifier
' |
345 'separated by comma (,) or put this option multiple t
ime. ' | 324 'separated by comma (,) or put this option multiple t
ime. ' |
346 'See also the "--disable" option for examples. '}), | 325 'See also the "--disable" option for examples. '}), |
347 | 326 |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
399 option_groups = ( | 378 option_groups = ( |
400 ('Messages control', 'Options controling analysis messages'), | 379 ('Messages control', 'Options controling analysis messages'), |
401 ('Reports', 'Options related to output formating and reporting'), | 380 ('Reports', 'Options related to output formating and reporting'), |
402 ) | 381 ) |
403 | 382 |
404 def __init__(self, options=(), reporter=None, option_groups=(), | 383 def __init__(self, options=(), reporter=None, option_groups=(), |
405 pylintrc=None): | 384 pylintrc=None): |
406 # some stuff has to be done before ancestors initialization... | 385 # some stuff has to be done before ancestors initialization... |
407 # | 386 # |
408 # messages store / checkers / reporter / astroid manager | 387 # messages store / checkers / reporter / astroid manager |
409 self.msgs_store = MessagesStore() | 388 self.msgs_store = utils.MessagesStore() |
410 self.reporter = None | 389 self.reporter = None |
411 self._reporter_name = None | 390 self._reporter_name = None |
412 self._reporters = {} | 391 self._reporters = {} |
413 self._checkers = defaultdict(list) | 392 self._checkers = collections.defaultdict(list) |
414 self._pragma_lineno = {} | 393 self._pragma_lineno = {} |
415 self._ignore_file = False | 394 self._ignore_file = False |
416 # visit variables | 395 # visit variables |
417 self.file_state = FileState() | 396 self.file_state = utils.FileState() |
418 self.current_name = None | 397 self.current_name = None |
419 self.current_file = None | 398 self.current_file = None |
420 self.stats = None | 399 self.stats = None |
421 # init options | 400 # init options |
| 401 self._external_opts = options |
422 self.options = options + PyLinter.make_options() | 402 self.options = options + PyLinter.make_options() |
423 self.option_groups = option_groups + PyLinter.option_groups | 403 self.option_groups = option_groups + PyLinter.option_groups |
424 self._options_methods = { | 404 self._options_methods = { |
425 'enable': self.enable, | 405 'enable': self.enable, |
426 'disable': self.disable} | 406 'disable': self.disable} |
427 self._bw_options_methods = {'disable-msg': self.disable, | 407 self._bw_options_methods = {'disable-msg': self.disable, |
428 'enable-msg': self.enable} | 408 'enable-msg': self.enable} |
429 full_version = '%%prog %s, \nastroid %s, common %s\nPython %s' % ( | 409 full_version = '%%prog %s, \nastroid %s, common %s\nPython %s' % ( |
430 version, astroid_version, common_version, sys.version) | 410 version, astroid_version, common_version, sys.version) |
431 OptionsManagerMixIn.__init__(self, usage=__doc__, | 411 configuration.OptionsManagerMixIn.__init__( |
432 version=full_version, | 412 self, usage=__doc__, |
433 config_file=pylintrc or config.PYLINTRC) | 413 version=full_version, |
434 MessagesHandlerMixIn.__init__(self) | 414 config_file=pylintrc or config.PYLINTRC) |
435 ReportsHandlerMixIn.__init__(self) | 415 utils.MessagesHandlerMixIn.__init__(self) |
436 BaseTokenChecker.__init__(self) | 416 utils.ReportsHandlerMixIn.__init__(self) |
| 417 checkers.BaseTokenChecker.__init__(self) |
437 # provided reports | 418 # provided reports |
438 self.reports = (('RP0001', 'Messages by category', | 419 self.reports = (('RP0001', 'Messages by category', |
439 report_total_messages_stats), | 420 report_total_messages_stats), |
440 ('RP0002', '% errors / warnings by module', | 421 ('RP0002', '% errors / warnings by module', |
441 report_messages_by_module_stats), | 422 report_messages_by_module_stats), |
442 ('RP0003', 'Messages', | 423 ('RP0003', 'Messages', |
443 report_messages_stats), | 424 report_messages_stats), |
444 ('RP0004', 'Global evaluation', | 425 ('RP0004', 'Global evaluation', |
445 self.report_evaluation), | 426 self.report_evaluation), |
446 ) | 427 ) |
447 self.register_checker(self) | 428 self.register_checker(self) |
448 self._dynamic_plugins = set() | 429 self._dynamic_plugins = set() |
449 self.load_provider_defaults() | 430 self.load_provider_defaults() |
450 if reporter: | 431 if reporter: |
451 self.set_reporter(reporter) | 432 self.set_reporter(reporter) |
452 | 433 |
453 def load_default_plugins(self): | 434 def load_default_plugins(self): |
454 checkers_initialize(self) | 435 checkers.initialize(self) |
455 reporters_initialize(self) | 436 reporters.initialize(self) |
456 # Make sure to load the default reporter, because | 437 # Make sure to load the default reporter, because |
457 # the option has been set before the plugins had been loaded. | 438 # the option has been set before the plugins had been loaded. |
458 if not self.reporter: | 439 if not self.reporter: |
459 self._load_reporter() | 440 self._load_reporter() |
460 | 441 |
461 def load_plugin_modules(self, modnames): | 442 def load_plugin_modules(self, modnames): |
462 """take a list of module names which are pylint plugins and load | 443 """take a list of module names which are pylint plugins and load |
463 and register them | 444 and register them |
464 """ | 445 """ |
465 for modname in modnames: | 446 for modname in modnames: |
466 if modname in self._dynamic_plugins: | 447 if modname in self._dynamic_plugins: |
467 continue | 448 continue |
468 self._dynamic_plugins.add(modname) | 449 self._dynamic_plugins.add(modname) |
469 module = load_module_from_name(modname) | 450 module = modutils.load_module_from_name(modname) |
470 module.register(self) | 451 module.register(self) |
471 | 452 |
472 def _load_reporter(self): | 453 def _load_reporter(self): |
473 name = self._reporter_name.lower() | 454 name = self._reporter_name.lower() |
474 if name in self._reporters: | 455 if name in self._reporters: |
475 self.set_reporter(self._reporters[name]()) | 456 self.set_reporter(self._reporters[name]()) |
476 else: | 457 else: |
477 qname = self._reporter_name | 458 qname = self._reporter_name |
478 module = load_module_from_name(get_module_part(qname)) | 459 module = modutils.load_module_from_name( |
| 460 modutils.get_module_part(qname)) |
479 class_name = qname.split('.')[-1] | 461 class_name = qname.split('.')[-1] |
480 reporter_class = getattr(module, class_name) | 462 reporter_class = getattr(module, class_name) |
481 self.set_reporter(reporter_class()) | 463 self.set_reporter(reporter_class()) |
482 | 464 |
483 def set_reporter(self, reporter): | 465 def set_reporter(self, reporter): |
484 """set the reporter used to display messages and reports""" | 466 """set the reporter used to display messages and reports""" |
485 self.reporter = reporter | 467 self.reporter = reporter |
486 reporter.linter = self | 468 reporter.linter = self |
487 | 469 |
488 def set_option(self, optname, value, action=None, optdict=None): | 470 def set_option(self, optname, value, action=None, optdict=None): |
489 """overridden from configuration.OptionsProviderMixin to handle some | 471 """overridden from configuration.OptionsProviderMixin to handle some |
490 special options | 472 special options |
491 """ | 473 """ |
492 if optname in self._options_methods or \ | 474 if optname in self._options_methods or \ |
493 optname in self._bw_options_methods: | 475 optname in self._bw_options_methods: |
494 if value: | 476 if value: |
495 try: | 477 try: |
496 meth = self._options_methods[optname] | 478 meth = self._options_methods[optname] |
497 except KeyError: | 479 except KeyError: |
498 meth = self._bw_options_methods[optname] | 480 meth = self._bw_options_methods[optname] |
499 warn('%s is deprecated, replace it by %s' % ( | 481 warnings.warn('%s is deprecated, replace it by %s' % ( |
500 optname, optname.split('-')[0]), DeprecationWarning) | 482 optname, optname.split('-')[0]), |
501 value = check_csv(None, optname, value) | 483 DeprecationWarning) |
| 484 value = optik_ext.check_csv(None, optname, value) |
502 if isinstance(value, (list, tuple)): | 485 if isinstance(value, (list, tuple)): |
503 for _id in value: | 486 for _id in value: |
504 meth(_id, ignore_unknown=True) | 487 meth(_id, ignore_unknown=True) |
505 else: | 488 else: |
506 meth(value) | 489 meth(value) |
| 490 return # no need to call set_option, disable/enable methods do i
t |
507 elif optname == 'output-format': | 491 elif optname == 'output-format': |
508 self._reporter_name = value | 492 self._reporter_name = value |
509 # If the reporters are already available, load | 493 # If the reporters are already available, load |
510 # the reporter class. | 494 # the reporter class. |
511 if self._reporters: | 495 if self._reporters: |
512 self._load_reporter() | 496 self._load_reporter() |
| 497 |
513 try: | 498 try: |
514 BaseTokenChecker.set_option(self, optname, value, action, optdict) | 499 checkers.BaseTokenChecker.set_option(self, optname, |
515 except UnsupportedAction: | 500 value, action, optdict) |
| 501 except configuration.UnsupportedAction: |
516 print('option %s can\'t be read from config file' % \ | 502 print('option %s can\'t be read from config file' % \ |
517 optname, file=sys.stderr) | 503 optname, file=sys.stderr) |
518 | 504 |
519 def register_reporter(self, reporter_class): | 505 def register_reporter(self, reporter_class): |
520 self._reporters[reporter_class.name] = reporter_class | 506 self._reporters[reporter_class.name] = reporter_class |
521 | 507 |
522 def report_order(self): | 508 def report_order(self): |
523 reports = sorted(self._reports, key=lambda x: getattr(x, 'name', '')) | 509 reports = sorted(self._reports, key=lambda x: getattr(x, 'name', '')) |
524 try: | 510 try: |
525 # Remove the current reporter and add it | 511 # Remove the current reporter and add it |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
557 if msgcat == 'E': | 543 if msgcat == 'E': |
558 for msgid in msgids: | 544 for msgid in msgids: |
559 self.enable(msgid) | 545 self.enable(msgid) |
560 else: | 546 else: |
561 for msgid in msgids: | 547 for msgid in msgids: |
562 self.disable(msgid) | 548 self.disable(msgid) |
563 | 549 |
564 def disable_reporters(self): | 550 def disable_reporters(self): |
565 """disable all reporters""" | 551 """disable all reporters""" |
566 for reporters in six.itervalues(self._reports): | 552 for reporters in six.itervalues(self._reports): |
567 for report_id, _title, _cb in reporters: | 553 for report_id, _, _ in reporters: |
568 self.disable_report(report_id) | 554 self.disable_report(report_id) |
569 | 555 |
570 def error_mode(self): | 556 def error_mode(self): |
571 """error mode: enable only errors; no reports, no persistent""" | 557 """error mode: enable only errors; no reports, no persistent""" |
572 self.disable_noerror_messages() | 558 self.disable_noerror_messages() |
573 self.disable('miscellaneous') | 559 self.disable('miscellaneous') |
574 self.set_option('reports', False) | 560 self.set_option('reports', False) |
575 self.set_option('persistent', False) | 561 self.set_option('persistent', False) |
576 | 562 |
577 # block level option handling ############################################# | 563 # block level option handling ############################################# |
578 # | 564 # |
579 # see func_block_disable_msg.py test case for expected behaviour | 565 # see func_block_disable_msg.py test case for expected behaviour |
580 | 566 |
581 def process_tokens(self, tokens): | 567 def process_tokens(self, tokens): |
582 """process tokens from the current module to search for module/block | 568 """process tokens from the current module to search for module/block |
583 level options | 569 level options |
584 """ | 570 """ |
585 control_pragmas = {'disable', 'enable'} | 571 control_pragmas = {'disable', 'enable'} |
586 for (tok_type, content, start, _, _) in tokens: | 572 for (tok_type, content, start, _, _) in tokens: |
587 if tok_type != tokenize.COMMENT: | 573 if tok_type != tokenize.COMMENT: |
588 continue | 574 continue |
589 match = OPTION_RGX.search(content) | 575 match = utils.OPTION_RGX.search(content) |
590 if match is None: | 576 if match is None: |
591 continue | 577 continue |
592 if match.group(1).strip() == "disable-all" or \ | 578 if match.group(1).strip() == "disable-all" or \ |
593 match.group(1).strip() == 'skip-file': | 579 match.group(1).strip() == 'skip-file': |
594 if match.group(1).strip() == "disable-all": | 580 if match.group(1).strip() == "disable-all": |
595 self.add_message('deprecated-pragma', line=start[0], | 581 self.add_message('deprecated-pragma', line=start[0], |
596 args=('disable-all', 'skip-file')) | 582 args=('disable-all', 'skip-file')) |
597 self.add_message('file-ignored', line=start[0]) | 583 self.add_message('file-ignored', line=start[0]) |
598 self._ignore_file = True | 584 self._ignore_file = True |
599 return | 585 return |
600 try: | 586 try: |
601 opt, value = match.group(1).split('=', 1) | 587 opt, value = match.group(1).split('=', 1) |
602 except ValueError: | 588 except ValueError: |
603 self.add_message('bad-inline-option', args=match.group(1).strip(
), | 589 self.add_message('bad-inline-option', args=match.group(1).strip(
), |
604 line=start[0]) | 590 line=start[0]) |
605 continue | 591 continue |
606 opt = opt.strip() | 592 opt = opt.strip() |
607 if opt in self._options_methods or opt in self._bw_options_methods: | 593 if opt in self._options_methods or opt in self._bw_options_methods: |
608 try: | 594 try: |
609 meth = self._options_methods[opt] | 595 meth = self._options_methods[opt] |
610 except KeyError: | 596 except KeyError: |
611 meth = self._bw_options_methods[opt] | 597 meth = self._bw_options_methods[opt] |
612 # found a "(dis|en)able-msg" pragma deprecated suppresssion | 598 # found a "(dis|en)able-msg" pragma deprecated suppresssion |
613 self.add_message('deprecated-pragma', line=start[0], args=(o
pt, opt.replace('-msg', ''))) | 599 self.add_message('deprecated-pragma', line=start[0], args=(o
pt, opt.replace('-msg', ''))) |
614 for msgid in splitstrip(value): | 600 for msgid in textutils.splitstrip(value): |
615 # Add the line where a control pragma was encountered. | 601 # Add the line where a control pragma was encountered. |
616 if opt in control_pragmas: | 602 if opt in control_pragmas: |
617 self._pragma_lineno[msgid] = start[0] | 603 self._pragma_lineno[msgid] = start[0] |
618 | 604 |
619 try: | 605 try: |
620 if (opt, msgid) == ('disable', 'all'): | 606 if (opt, msgid) == ('disable', 'all'): |
621 self.add_message('deprecated-pragma', line=start[0],
args=('disable=all', 'skip-file')) | 607 self.add_message('deprecated-pragma', line=start[0],
args=('disable=all', 'skip-file')) |
622 self.add_message('file-ignored', line=start[0]) | 608 self.add_message('file-ignored', line=start[0]) |
623 self._ignore_file = True | 609 self._ignore_file = True |
624 return | 610 return |
625 meth(msgid, 'module', start[0]) | 611 meth(msgid, 'module', start[0]) |
626 except UnknownMessage: | 612 except utils.UnknownMessage: |
627 self.add_message('bad-option-value', args=msgid, line=st
art[0]) | 613 self.add_message('bad-option-value', args=msgid, line=st
art[0]) |
628 else: | 614 else: |
629 self.add_message('unrecognized-inline-option', args=opt, line=st
art[0]) | 615 self.add_message('unrecognized-inline-option', args=opt, line=st
art[0]) |
630 | 616 |
631 | 617 |
632 # code checking methods ################################################### | 618 # code checking methods ################################################### |
633 | 619 |
634 def get_checkers(self): | 620 def get_checkers(self): |
635 """return all available checkers as a list""" | 621 """return all available checkers as a list""" |
636 return [self] + [c for checkers in six.itervalues(self._checkers) | 622 return [self] + [c for checkers in six.itervalues(self._checkers) |
637 for c in checkers if c is not self] | 623 for c in checkers if c is not self] |
638 | 624 |
639 def prepare_checkers(self): | 625 def prepare_checkers(self): |
640 """return checkers needed for activated messages and reports""" | 626 """return checkers needed for activated messages and reports""" |
641 if not self.config.reports: | 627 if not self.config.reports: |
642 self.disable_reporters() | 628 self.disable_reporters() |
643 # get needed checkers | 629 # get needed checkers |
644 neededcheckers = [self] | 630 neededcheckers = [self] |
645 for checker in self.get_checkers()[1:]: | 631 for checker in self.get_checkers()[1:]: |
646 # fatal errors should not trigger enable / disabling a checker | 632 # fatal errors should not trigger enable / disabling a checker |
647 messages = set(msg for msg in checker.msgs | 633 messages = set(msg for msg in checker.msgs |
648 if msg[0] != 'F' and self.is_message_enabled(msg)) | 634 if msg[0] != 'F' and self.is_message_enabled(msg)) |
649 if (messages or | 635 if (messages or |
650 any(self.report_is_enabled(r[0]) for r in checker.reports)): | 636 any(self.report_is_enabled(r[0]) for r in checker.reports)): |
651 neededcheckers.append(checker) | 637 neededcheckers.append(checker) |
652 # Sort checkers by priority | 638 # Sort checkers by priority |
653 neededcheckers = sorted(neededcheckers, key=attrgetter('priority'), | 639 neededcheckers = sorted(neededcheckers, |
| 640 key=operator.attrgetter('priority'), |
654 reverse=True) | 641 reverse=True) |
655 return neededcheckers | 642 return neededcheckers |
656 | 643 |
657 def should_analyze_file(self, modname, path): # pylint: disable=unused-argum
ent, no-self-use | 644 def should_analyze_file(self, modname, path): # pylint: disable=unused-argum
ent, no-self-use |
658 """Returns whether or not a module should be checked. | 645 """Returns whether or not a module should be checked. |
659 | 646 |
660 This implementation returns True for all python source file, indicating | 647 This implementation returns True for all python source file, indicating |
661 that all files should be linted. | 648 that all files should be linted. |
662 | 649 |
663 Subclasses may override this method to indicate that modules satisfying | 650 Subclasses may override this method to indicate that modules satisfying |
(...skipping 16 matching lines...) Expand all Loading... |
680 if not msg.may_be_emitted(): | 667 if not msg.may_be_emitted(): |
681 self._msgs_state[msg.msgid] = False | 668 self._msgs_state[msg.msgid] = False |
682 | 669 |
683 if not isinstance(files_or_modules, (list, tuple)): | 670 if not isinstance(files_or_modules, (list, tuple)): |
684 files_or_modules = (files_or_modules,) | 671 files_or_modules = (files_or_modules,) |
685 | 672 |
686 if self.config.jobs == 1: | 673 if self.config.jobs == 1: |
687 self._do_check(files_or_modules) | 674 self._do_check(files_or_modules) |
688 else: | 675 else: |
689 # Hack that permits running pylint, on Windows, with -m switch | 676 # Hack that permits running pylint, on Windows, with -m switch |
690 # and with --jobs, as in 'py -2 -m pylint .. --jobs'. | 677 # and with --jobs, as in 'python -2 -m pylint .. --jobs'. |
691 # For more details why this is needed, | 678 # For more details why this is needed, |
692 # see Python issue http://bugs.python.org/issue10845. | 679 # see Python issue http://bugs.python.org/issue10845. |
693 | 680 |
694 mock_main = six.PY2 and __name__ != '__main__' # -m switch | 681 mock_main = __name__ != '__main__' # -m switch |
695 if mock_main: | 682 if mock_main: |
696 sys.modules['__main__'] = sys.modules[__name__] | 683 sys.modules['__main__'] = sys.modules[__name__] |
697 try: | 684 try: |
698 self._parallel_check(files_or_modules) | 685 self._parallel_check(files_or_modules) |
699 finally: | 686 finally: |
700 if mock_main: | 687 if mock_main: |
701 sys.modules.pop('__main__') | 688 sys.modules.pop('__main__') |
702 | 689 |
703 def _parallel_task(self, files_or_modules): | 690 def _parallel_task(self, files_or_modules): |
704 # Prepare configuration for child linters. | 691 # Prepare configuration for child linters. |
705 config = vars(self.config) | 692 filter_options = {'symbols', 'include-ids', 'long-help'} |
| 693 filter_options.update([opt_name for opt_name, _ in self._external_opts]) |
| 694 config = {} |
| 695 for opt_providers in six.itervalues(self._all_options): |
| 696 for optname, optdict, val in opt_providers.options_and_values(): |
| 697 if optname not in filter_options: |
| 698 config[optname] = configuration.format_option_value(optdict,
val) |
| 699 |
706 childs = [] | 700 childs = [] |
707 manager = multiprocessing.Manager() # pylint: disable=no-member | 701 manager = multiprocessing.Manager() # pylint: disable=no-member |
708 tasks_queue = manager.Queue() # pylint: disable=no-member | 702 tasks_queue = manager.Queue() # pylint: disable=no-member |
709 results_queue = manager.Queue() # pylint: disable=no-member | 703 results_queue = manager.Queue() # pylint: disable=no-member |
710 | 704 |
711 for _ in range(self.config.jobs): | 705 for _ in range(self.config.jobs): |
712 cl = ChildLinter(args=(tasks_queue, results_queue, config)) | 706 cl = ChildLinter(args=(tasks_queue, results_queue, config)) |
713 cl.start() # pylint: disable=no-member | 707 cl.start() # pylint: disable=no-member |
714 childs.append(cl) | 708 childs.append(cl) |
715 | 709 |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
752 module, | 746 module, |
753 messages, | 747 messages, |
754 stats, | 748 stats, |
755 msg_status | 749 msg_status |
756 ) = result | 750 ) = result |
757 | 751 |
758 if file_or_module == files_or_modules[-1]: | 752 if file_or_module == files_or_modules[-1]: |
759 last_module = module | 753 last_module = module |
760 | 754 |
761 for msg in messages: | 755 for msg in messages: |
762 msg = Message(*msg) | 756 msg = utils.Message(*msg) |
763 self.set_current_module(module) | 757 self.set_current_module(module) |
764 self.reporter.handle_message(msg) | 758 self.reporter.handle_message(msg) |
765 | 759 |
766 all_stats.append(stats) | 760 all_stats.append(stats) |
767 self.msg_status |= msg_status | 761 self.msg_status |= msg_status |
768 | 762 |
769 self.stats = _merge_stats(chain(all_stats, [self.stats])) | 763 self.stats = _merge_stats(itertools.chain(all_stats, [self.stats])) |
770 self.current_name = last_module | 764 self.current_name = last_module |
771 | 765 |
772 # Insert stats data to local checkers. | 766 # Insert stats data to local checkers. |
773 for checker in self.get_checkers(): | 767 for checker in self.get_checkers(): |
774 if checker is not self: | 768 if checker is not self: |
775 checker.stats = self.stats | 769 checker.stats = self.stats |
776 | 770 |
777 def _do_check(self, files_or_modules): | 771 def _do_check(self, files_or_modules): |
778 walker = PyLintASTWalker(self) | 772 walker = utils.PyLintASTWalker(self) |
779 checkers = self.prepare_checkers() | 773 checkers = self.prepare_checkers() |
780 tokencheckers = [c for c in checkers if implements(c, ITokenChecker) | 774 tokencheckers = [c for c in checkers |
| 775 if interface.implements(c, interfaces.ITokenChecker) |
781 and c is not self] | 776 and c is not self] |
782 rawcheckers = [c for c in checkers if implements(c, IRawChecker)] | 777 rawcheckers = [c for c in checkers |
| 778 if interface.implements(c, interfaces.IRawChecker)] |
783 # notify global begin | 779 # notify global begin |
784 for checker in checkers: | 780 for checker in checkers: |
785 checker.open() | 781 checker.open() |
786 if implements(checker, IAstroidChecker): | 782 if interface.implements(checker, interfaces.IAstroidChecker): |
787 walker.add_checker(checker) | 783 walker.add_checker(checker) |
788 # build ast and check modules or packages | 784 # build ast and check modules or packages |
789 for descr in self.expand_files(files_or_modules): | 785 for descr in self.expand_files(files_or_modules): |
790 modname, filepath = descr['name'], descr['path'] | 786 modname, filepath = descr['name'], descr['path'] |
791 if not descr['isarg'] and not self.should_analyze_file(modname, file
path): | 787 if not descr['isarg'] and not self.should_analyze_file(modname, file
path): |
792 continue | 788 continue |
793 if self.config.files_output: | 789 if self.config.files_output: |
794 reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) | 790 reportfile = 'pylint_%s.%s' % (modname, self.reporter.extension) |
795 self.reporter.set_output(open(reportfile, 'w')) | 791 self.reporter.set_output(open(reportfile, 'w')) |
796 self.set_current_module(modname, filepath) | 792 self.set_current_module(modname, filepath) |
797 # get the module representation | 793 # get the module representation |
798 astroid = self.get_ast(filepath, modname) | 794 ast_node = self.get_ast(filepath, modname) |
799 if astroid is None: | 795 if ast_node is None: |
800 continue | 796 continue |
801 # XXX to be correct we need to keep module_msgs_state for every | 797 # XXX to be correct we need to keep module_msgs_state for every |
802 # analyzed module (the problem stands with localized messages which | 798 # analyzed module (the problem stands with localized messages which |
803 # are only detected in the .close step) | 799 # are only detected in the .close step) |
804 self.file_state = FileState(descr['basename']) | 800 self.file_state = utils.FileState(descr['basename']) |
805 self._ignore_file = False | 801 self._ignore_file = False |
806 # fix the current file (if the source file was not available or | 802 # fix the current file (if the source file was not available or |
807 # if it's actually a c extension) | 803 # if it's actually a c extension) |
808 self.current_file = astroid.file # pylint: disable=maybe-no-member | 804 self.current_file = ast_node.file # pylint: disable=maybe-no-member |
809 self.check_astroid_module(astroid, walker, rawcheckers, tokenchecker
s) | 805 self.check_astroid_module(ast_node, walker, rawcheckers, tokenchecke
rs) |
810 # warn about spurious inline messages handling | 806 # warn about spurious inline messages handling |
811 for msgid, line, args in self.file_state.iter_spurious_suppression_m
essages(self.msgs_store): | 807 for msgid, line, args in self.file_state.iter_spurious_suppression_m
essages(self.msgs_store): |
812 self.add_message(msgid, line, None, args) | 808 self.add_message(msgid, line, None, args) |
813 # notify global end | 809 # notify global end |
814 self.stats['statement'] = walker.nbstatements | 810 self.stats['statement'] = walker.nbstatements |
815 checkers.reverse() | 811 checkers.reverse() |
816 for checker in checkers: | 812 for checker in checkers: |
817 checker.close() | 813 checker.close() |
818 | 814 |
819 def expand_files(self, modules): | 815 def expand_files(self, modules): |
820 """get modules and errors from a list of modules and handle errors | 816 """get modules and errors from a list of modules and handle errors |
821 """ | 817 """ |
822 result, errors = expand_modules(modules, self.config.black_list) | 818 result, errors = utils.expand_modules(modules, self.config.black_list) |
823 for error in errors: | 819 for error in errors: |
824 message = modname = error["mod"] | 820 message = modname = error["mod"] |
825 key = error["key"] | 821 key = error["key"] |
826 self.set_current_module(modname) | 822 self.set_current_module(modname) |
827 if key == "fatal": | 823 if key == "fatal": |
828 message = str(error["ex"]).replace(os.getcwd() + os.sep, '') | 824 message = str(error["ex"]).replace(os.getcwd() + os.sep, '') |
829 self.add_message(key, args=message) | 825 self.add_message(key, args=message) |
830 return result | 826 return result |
831 | 827 |
832 def set_current_module(self, modname, filepath=None): | 828 def set_current_module(self, modname, filepath=None): |
833 """set the name of the currently analyzed module and | 829 """set the name of the currently analyzed module and |
834 init statistics for it | 830 init statistics for it |
835 """ | 831 """ |
836 if not modname and filepath is None: | 832 if not modname and filepath is None: |
837 return | 833 return |
838 self.reporter.on_set_current_module(modname, filepath) | 834 self.reporter.on_set_current_module(modname, filepath) |
839 self.current_name = modname | 835 self.current_name = modname |
840 self.current_file = filepath or modname | 836 self.current_file = filepath or modname |
841 self.stats['by_module'][modname] = {} | 837 self.stats['by_module'][modname] = {} |
842 self.stats['by_module'][modname]['statement'] = 0 | 838 self.stats['by_module'][modname]['statement'] = 0 |
843 for msg_cat in six.itervalues(MSG_TYPES): | 839 for msg_cat in six.itervalues(utils.MSG_TYPES): |
844 self.stats['by_module'][modname][msg_cat] = 0 | 840 self.stats['by_module'][modname][msg_cat] = 0 |
845 | 841 |
846 def get_ast(self, filepath, modname): | 842 def get_ast(self, filepath, modname): |
847 """return a ast(roid) representation for a module""" | 843 """return a ast(roid) representation for a module""" |
848 try: | 844 try: |
849 return MANAGER.ast_from_file(filepath, modname, source=True) | 845 return MANAGER.ast_from_file(filepath, modname, source=True) |
850 except SyntaxError as ex: | 846 except SyntaxError as ex: |
851 self.add_message('syntax-error', line=ex.lineno, args=ex.msg) | 847 self.add_message('syntax-error', line=ex.lineno, args=ex.msg) |
852 except AstroidBuildingException as ex: | 848 except astroid.AstroidBuildingException as ex: |
853 self.add_message('parse-error', args=ex) | 849 self.add_message('parse-error', args=ex) |
854 except Exception as ex: # pylint: disable=broad-except | 850 except Exception as ex: # pylint: disable=broad-except |
855 import traceback | 851 import traceback |
856 traceback.print_exc() | 852 traceback.print_exc() |
857 self.add_message('astroid-error', args=(ex.__class__, ex)) | 853 self.add_message('astroid-error', args=(ex.__class__, ex)) |
858 | 854 |
859 def check_astroid_module(self, astroid, walker, rawcheckers, tokencheckers): | 855 def check_astroid_module(self, ast_node, walker, |
860 """check a module from its astroid representation, real work""" | 856 rawcheckers, tokencheckers): |
| 857 """Check a module from its astroid representation.""" |
861 try: | 858 try: |
862 return self._check_astroid_module(astroid, walker, | 859 tokens = utils.tokenize_module(ast_node) |
863 rawcheckers, tokencheckers) | |
864 finally: | |
865 # Close file_stream, if opened, to avoid to open many files. | |
866 if astroid.file_stream: | |
867 astroid.file_stream.close() | |
868 # TODO(cpopa): This is an implementation detail, but it will | |
869 # be moved in astroid at some point. | |
870 # We invalidate the cached property, to let the others | |
871 # modules which relies on this one to get a new file stream. | |
872 del astroid.file_stream | |
873 | |
874 def _check_astroid_module(self, astroid, walker, rawcheckers, tokencheckers)
: | |
875 # call raw checkers if possible | |
876 try: | |
877 tokens = tokenize_module(astroid) | |
878 except tokenize.TokenError as ex: | 860 except tokenize.TokenError as ex: |
879 self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0]
) | 861 self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0]
) |
880 return | 862 return |
881 | 863 |
882 if not astroid.pure_python: | 864 if not ast_node.pure_python: |
883 self.add_message('raw-checker-failed', args=astroid.name) | 865 self.add_message('raw-checker-failed', args=ast_node.name) |
884 else: | 866 else: |
885 #assert astroid.file.endswith('.py') | 867 #assert astroid.file.endswith('.py') |
886 # invoke ITokenChecker interface on self to fetch module/block | 868 # invoke ITokenChecker interface on self to fetch module/block |
887 # level options | 869 # level options |
888 self.process_tokens(tokens) | 870 self.process_tokens(tokens) |
889 if self._ignore_file: | 871 if self._ignore_file: |
890 return False | 872 return False |
891 # walk ast to collect line numbers | 873 # walk ast to collect line numbers |
892 self.file_state.collect_block_lines(self.msgs_store, astroid) | 874 self.file_state.collect_block_lines(self.msgs_store, ast_node) |
893 # run raw and tokens checkers | 875 # run raw and tokens checkers |
894 for checker in rawcheckers: | 876 for checker in rawcheckers: |
895 checker.process_module(astroid) | 877 checker.process_module(ast_node) |
896 for checker in tokencheckers: | 878 for checker in tokencheckers: |
897 checker.process_tokens(tokens) | 879 checker.process_tokens(tokens) |
898 # generate events to astroid checkers | 880 # generate events to astroid checkers |
899 walker.walk(astroid) | 881 walker.walk(ast_node) |
900 return True | 882 return True |
901 | 883 |
902 # IAstroidChecker interface ################################################
# | 884 # IAstroidChecker interface ################################################
# |
903 | 885 |
904 def open(self): | 886 def open(self): |
905 """initialize counters""" | 887 """initialize counters""" |
906 self.stats = {'by_module' : {}, | 888 self.stats = {'by_module' : {}, |
907 'by_msg' : {}, | 889 'by_msg' : {}, |
908 } | 890 } |
909 MANAGER.always_load_extensions = self.config.unsafe_load_any_extension | 891 MANAGER.always_load_extensions = self.config.unsafe_load_any_extension |
910 MANAGER.extension_package_whitelist.update(self.config.extension_pkg_whi
telist) | 892 MANAGER.extension_package_whitelist.update( |
911 for msg_cat in six.itervalues(MSG_TYPES): | 893 self.config.extension_pkg_whitelist) |
| 894 for msg_cat in six.itervalues(utils.MSG_TYPES): |
912 self.stats[msg_cat] = 0 | 895 self.stats[msg_cat] = 0 |
913 | 896 |
914 def generate_reports(self): | 897 def generate_reports(self): |
915 """close the whole package /module, it's time to make reports ! | 898 """close the whole package /module, it's time to make reports ! |
916 | 899 |
917 if persistent run, pickle results for later comparison | 900 if persistent run, pickle results for later comparison |
918 """ | 901 """ |
919 if self.file_state.base_name is not None: | 902 if self.file_state.base_name is not None: |
920 # load previous results if any | 903 # load previous results if any |
921 previous_stats = config.load_results(self.file_state.base_name) | 904 previous_stats = config.load_results(self.file_state.base_name) |
922 # XXX code below needs refactoring to be more reporter agnostic | 905 # XXX code below needs refactoring to be more reporter agnostic |
923 self.reporter.on_close(self.stats, previous_stats) | 906 self.reporter.on_close(self.stats, previous_stats) |
924 if self.config.reports: | 907 if self.config.reports: |
925 sect = self.make_reports(self.stats, previous_stats) | 908 sect = self.make_reports(self.stats, previous_stats) |
926 if self.config.files_output: | 909 if self.config.files_output: |
927 filename = 'pylint_global.' + self.reporter.extension | 910 filename = 'pylint_global.' + self.reporter.extension |
928 self.reporter.set_output(open(filename, 'w')) | 911 self.reporter.set_output(open(filename, 'w')) |
929 else: | 912 else: |
930 sect = Section() | 913 sect = ureports.Section() |
931 if self.config.reports or self.config.output_format == 'html': | 914 if self.config.reports or self.config.output_format == 'html': |
932 self.reporter.display_results(sect) | 915 self.reporter.display_results(sect) |
933 # save results if persistent run | 916 # save results if persistent run |
934 if self.config.persistent: | 917 if self.config.persistent: |
935 config.save_results(self.stats, self.file_state.base_name) | 918 config.save_results(self.stats, self.file_state.base_name) |
936 else: | 919 else: |
937 if self.config.output_format == 'html': | 920 if self.config.output_format == 'html': |
938 # No output will be emitted for the html | 921 # No output will be emitted for the html |
939 # reporter if the file doesn't exist, so emit | 922 # reporter if the file doesn't exist, so emit |
940 # the results here. | 923 # the results here. |
941 self.reporter.display_results(Section()) | 924 self.reporter.display_results(ureports.Section()) |
942 self.reporter.on_close(self.stats, {}) | 925 self.reporter.on_close(self.stats, {}) |
943 | 926 |
944 # specific reports ######################################################## | 927 # specific reports ######################################################## |
945 | 928 |
946 def report_evaluation(self, sect, stats, previous_stats): | 929 def report_evaluation(self, sect, stats, previous_stats): |
947 """make the global evaluation report""" | 930 """make the global evaluation report""" |
948 # check with at least check 1 statements (usually 0 when there is a | 931 # check with at least check 1 statements (usually 0 when there is a |
949 # syntax error preventing pylint from further processing) | 932 # syntax error preventing pylint from further processing) |
950 if stats['statement'] == 0: | 933 if stats['statement'] == 0: |
951 raise EmptyReport() | 934 raise utils.EmptyReport() |
952 # get a global note for the code | 935 # get a global note for the code |
953 evaluation = self.config.evaluation | 936 evaluation = self.config.evaluation |
954 try: | 937 try: |
955 note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used | 938 note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used |
956 except Exception as ex: # pylint: disable=broad-except | 939 except Exception as ex: # pylint: disable=broad-except |
957 msg = 'An exception occurred while rating: %s' % ex | 940 msg = 'An exception occurred while rating: %s' % ex |
958 else: | 941 else: |
959 stats['global_note'] = note | 942 stats['global_note'] = note |
960 msg = 'Your code has been rated at %.2f/10' % note | 943 msg = 'Your code has been rated at %.2f/10' % note |
961 pnote = previous_stats.get('global_note') | 944 pnote = previous_stats.get('global_note') |
962 if pnote is not None: | 945 if pnote is not None: |
963 msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote) | 946 msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote) |
964 if self.config.comment: | 947 if self.config.comment: |
965 msg = '%s\n%s' % (msg, config.get_note_message(note)) | 948 msg = '%s\n%s' % (msg, config.get_note_message(note)) |
966 sect.append(Text(msg)) | 949 sect.append(ureports.Text(msg)) |
967 | 950 |
968 # some reporting functions #################################################### | 951 # some reporting functions #################################################### |
969 | 952 |
970 def report_total_messages_stats(sect, stats, previous_stats): | 953 def report_total_messages_stats(sect, stats, previous_stats): |
971 """make total errors / warnings report""" | 954 """make total errors / warnings report""" |
972 lines = ['type', 'number', 'previous', 'difference'] | 955 lines = ['type', 'number', 'previous', 'difference'] |
973 lines += table_lines_from_stats(stats, previous_stats, | 956 lines += checkers.table_lines_from_stats(stats, previous_stats, |
974 ('convention', 'refactor', | 957 ('convention', 'refactor', |
975 'warning', 'error')) | 958 'warning', 'error')) |
976 sect.append(Table(children=lines, cols=4, rheaders=1)) | 959 sect.append(ureports.Table(children=lines, cols=4, rheaders=1)) |
977 | 960 |
978 def report_messages_stats(sect, stats, _): | 961 def report_messages_stats(sect, stats, _): |
979 """make messages type report""" | 962 """make messages type report""" |
980 if not stats['by_msg']: | 963 if not stats['by_msg']: |
981 # don't print this report when we didn't detected any errors | 964 # don't print this report when we didn't detected any errors |
982 raise EmptyReport() | 965 raise utils.EmptyReport() |
983 in_order = sorted([(value, msg_id) | 966 in_order = sorted([(value, msg_id) |
984 for msg_id, value in six.iteritems(stats['by_msg']) | 967 for msg_id, value in six.iteritems(stats['by_msg']) |
985 if not msg_id.startswith('I')]) | 968 if not msg_id.startswith('I')]) |
986 in_order.reverse() | 969 in_order.reverse() |
987 lines = ('message id', 'occurrences') | 970 lines = ('message id', 'occurrences') |
988 for value, msg_id in in_order: | 971 for value, msg_id in in_order: |
989 lines += (msg_id, str(value)) | 972 lines += (msg_id, str(value)) |
990 sect.append(Table(children=lines, cols=2, rheaders=1)) | 973 sect.append(ureports.Table(children=lines, cols=2, rheaders=1)) |
991 | 974 |
992 def report_messages_by_module_stats(sect, stats, _): | 975 def report_messages_by_module_stats(sect, stats, _): |
993 """make errors / warnings by modules report""" | 976 """make errors / warnings by modules report""" |
994 if len(stats['by_module']) == 1: | 977 if len(stats['by_module']) == 1: |
995 # don't print this report when we are analysing a single module | 978 # don't print this report when we are analysing a single module |
996 raise EmptyReport() | 979 raise utils.EmptyReport() |
997 by_mod = defaultdict(dict) | 980 by_mod = collections.defaultdict(dict) |
998 for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'): | 981 for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'): |
999 total = stats[m_type] | 982 total = stats[m_type] |
1000 for module in six.iterkeys(stats['by_module']): | 983 for module in six.iterkeys(stats['by_module']): |
1001 mod_total = stats['by_module'][module][m_type] | 984 mod_total = stats['by_module'][module][m_type] |
1002 if total == 0: | 985 if total == 0: |
1003 percent = 0 | 986 percent = 0 |
1004 else: | 987 else: |
1005 percent = float((mod_total)*100) / total | 988 percent = float((mod_total)*100) / total |
1006 by_mod[module][m_type] = percent | 989 by_mod[module][m_type] = percent |
1007 sorted_result = [] | 990 sorted_result = [] |
1008 for module, mod_info in six.iteritems(by_mod): | 991 for module, mod_info in six.iteritems(by_mod): |
1009 sorted_result.append((mod_info['error'], | 992 sorted_result.append((mod_info['error'], |
1010 mod_info['warning'], | 993 mod_info['warning'], |
1011 mod_info['refactor'], | 994 mod_info['refactor'], |
1012 mod_info['convention'], | 995 mod_info['convention'], |
1013 module)) | 996 module)) |
1014 sorted_result.sort() | 997 sorted_result.sort() |
1015 sorted_result.reverse() | 998 sorted_result.reverse() |
1016 lines = ['module', 'error', 'warning', 'refactor', 'convention'] | 999 lines = ['module', 'error', 'warning', 'refactor', 'convention'] |
1017 for line in sorted_result: | 1000 for line in sorted_result: |
1018 # Don't report clean modules. | 1001 # Don't report clean modules. |
1019 if all(entry == 0 for entry in line[:-1]): | 1002 if all(entry == 0 for entry in line[:-1]): |
1020 continue | 1003 continue |
1021 lines.append(line[-1]) | 1004 lines.append(line[-1]) |
1022 for val in line[:-1]: | 1005 for val in line[:-1]: |
1023 lines.append('%.2f' % val) | 1006 lines.append('%.2f' % val) |
1024 if len(lines) == 5: | 1007 if len(lines) == 5: |
1025 raise EmptyReport() | 1008 raise utils.EmptyReport() |
1026 sect.append(Table(children=lines, cols=5, rheaders=1)) | 1009 sect.append(ureports.Table(children=lines, cols=5, rheaders=1)) |
1027 | 1010 |
1028 | 1011 |
1029 # utilities ################################################################### | 1012 # utilities ################################################################### |
1030 | 1013 |
1031 | 1014 |
1032 class ArgumentPreprocessingError(Exception): | 1015 class ArgumentPreprocessingError(Exception): |
1033 """Raised if an error occurs during argument preprocessing.""" | 1016 """Raised if an error occurs during argument preprocessing.""" |
1034 | 1017 |
1035 | 1018 |
1036 def preprocess_options(args, search_for): | 1019 def preprocess_options(args, search_for): |
(...skipping 24 matching lines...) Expand all Loading... |
1061 val = args[i] | 1044 val = args[i] |
1062 del args[i] | 1045 del args[i] |
1063 elif not takearg and val is not None: | 1046 elif not takearg and val is not None: |
1064 msg = "Option %s doesn't expects a value" % option | 1047 msg = "Option %s doesn't expects a value" % option |
1065 raise ArgumentPreprocessingError(msg) | 1048 raise ArgumentPreprocessingError(msg) |
1066 cb(option, val) | 1049 cb(option, val) |
1067 else: | 1050 else: |
1068 i += 1 | 1051 i += 1 |
1069 | 1052 |
1070 | 1053 |
1071 @contextmanager | 1054 @contextlib.contextmanager |
1072 def fix_import_path(args): | 1055 def fix_import_path(args): |
1073 """Prepare sys.path for running the linter checks. | 1056 """Prepare sys.path for running the linter checks. |
1074 | 1057 |
1075 Within this context, each of the given arguments is importable. | 1058 Within this context, each of the given arguments is importable. |
1076 Paths are added to sys.path in corresponding order to the arguments. | 1059 Paths are added to sys.path in corresponding order to the arguments. |
1077 We avoid adding duplicate directories to sys.path. | 1060 We avoid adding duplicate directories to sys.path. |
1078 `sys.path` is reset to its original value upon exitign this context. | 1061 `sys.path` is reset to its original value upon exitign this context. |
1079 """ | 1062 """ |
1080 orig = list(sys.path) | 1063 orig = list(sys.path) |
1081 changes = [] | 1064 changes = [] |
(...skipping 140 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1222 level=1) | 1205 level=1) |
1223 # read configuration | 1206 # read configuration |
1224 linter.disable('pointless-except') | 1207 linter.disable('pointless-except') |
1225 linter.disable('suppressed-message') | 1208 linter.disable('suppressed-message') |
1226 linter.disable('useless-suppression') | 1209 linter.disable('useless-suppression') |
1227 linter.read_config_file() | 1210 linter.read_config_file() |
1228 config_parser = linter.cfgfile_parser | 1211 config_parser = linter.cfgfile_parser |
1229 # run init hook, if present, before loading plugins | 1212 # run init hook, if present, before loading plugins |
1230 if config_parser.has_option('MASTER', 'init-hook'): | 1213 if config_parser.has_option('MASTER', 'init-hook'): |
1231 cb_init_hook('init-hook', | 1214 cb_init_hook('init-hook', |
1232 unquote(config_parser.get('MASTER', 'init-hook'))) | 1215 textutils.unquote(config_parser.get('MASTER', |
| 1216 'init-hook'))) |
1233 # is there some additional plugins in the file configuration, in | 1217 # is there some additional plugins in the file configuration, in |
1234 if config_parser.has_option('MASTER', 'load-plugins'): | 1218 if config_parser.has_option('MASTER', 'load-plugins'): |
1235 plugins = splitstrip(config_parser.get('MASTER', 'load-plugins')) | 1219 plugins = textutils.splitstrip( |
| 1220 config_parser.get('MASTER', 'load-plugins')) |
1236 linter.load_plugin_modules(plugins) | 1221 linter.load_plugin_modules(plugins) |
1237 # now we can load file config and command line, plugins (which can | 1222 # now we can load file config and command line, plugins (which can |
1238 # provide options) have been registered | 1223 # provide options) have been registered |
1239 linter.load_config_file() | 1224 linter.load_config_file() |
1240 if reporter: | 1225 if reporter: |
1241 # if a custom reporter is provided as argument, it may be overridden | 1226 # if a custom reporter is provided as argument, it may be overridden |
1242 # by file parameters, so re-set it here, but before command line | 1227 # by file parameters, so re-set it here, but before command line |
1243 # parsing so it's still overrideable by command line option | 1228 # parsing so it's still overrideable by command line option |
1244 linter.set_reporter(reporter) | 1229 linter.set_reporter(reporter) |
1245 try: | 1230 try: |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1282 linter.generate_reports() | 1267 linter.generate_reports() |
1283 if exit: | 1268 if exit: |
1284 sys.exit(self.linter.msg_status) | 1269 sys.exit(self.linter.msg_status) |
1285 | 1270 |
1286 def cb_set_rcfile(self, name, value): | 1271 def cb_set_rcfile(self, name, value): |
1287 """callback for option preprocessing (i.e. before option parsing)""" | 1272 """callback for option preprocessing (i.e. before option parsing)""" |
1288 self._rcfile = value | 1273 self._rcfile = value |
1289 | 1274 |
1290 def cb_add_plugins(self, name, value): | 1275 def cb_add_plugins(self, name, value): |
1291 """callback for option preprocessing (i.e. before option parsing)""" | 1276 """callback for option preprocessing (i.e. before option parsing)""" |
1292 self._plugins.extend(splitstrip(value)) | 1277 self._plugins.extend(textutils.splitstrip(value)) |
1293 | 1278 |
1294 def cb_error_mode(self, *args, **kwargs): | 1279 def cb_error_mode(self, *args, **kwargs): |
1295 """error mode: | 1280 """error mode: |
1296 * disable all but error messages | 1281 * disable all but error messages |
1297 * disable the 'miscellaneous' checker which can be safely deactivated in | 1282 * disable the 'miscellaneous' checker which can be safely deactivated in |
1298 debug | 1283 debug |
1299 * disable reports | 1284 * disable reports |
1300 * do not save execution information | 1285 * do not save execution information |
1301 """ | 1286 """ |
1302 self.linter.error_mode() | 1287 self.linter.error_mode() |
1303 | 1288 |
1304 def cb_generate_config(self, *args, **kwargs): | 1289 def cb_generate_config(self, *args, **kwargs): |
1305 """optik callback for sample config file generation""" | 1290 """optik callback for sample config file generation""" |
1306 self.linter.generate_config(skipsections=('COMMANDS',)) | 1291 self.linter.generate_config(skipsections=('COMMANDS',)) |
1307 sys.exit(0) | 1292 sys.exit(0) |
1308 | 1293 |
1309 def cb_generate_manpage(self, *args, **kwargs): | 1294 def cb_generate_manpage(self, *args, **kwargs): |
1310 """optik callback for sample config file generation""" | 1295 """optik callback for sample config file generation""" |
1311 from pylint import __pkginfo__ | 1296 from pylint import __pkginfo__ |
1312 self.linter.generate_manpage(__pkginfo__) | 1297 self.linter.generate_manpage(__pkginfo__) |
1313 sys.exit(0) | 1298 sys.exit(0) |
1314 | 1299 |
1315 def cb_help_message(self, option, optname, value, parser): | 1300 def cb_help_message(self, option, optname, value, parser): |
1316 """optik callback for printing some help about a particular message""" | 1301 """optik callback for printing some help about a particular message""" |
1317 self.linter.msgs_store.help_message(splitstrip(value)) | 1302 self.linter.msgs_store.help_message(textutils.splitstrip(value)) |
1318 sys.exit(0) | 1303 sys.exit(0) |
1319 | 1304 |
1320 def cb_full_documentation(self, option, optname, value, parser): | 1305 def cb_full_documentation(self, option, optname, value, parser): |
1321 """optik callback for printing full documentation""" | 1306 """optik callback for printing full documentation""" |
1322 self.linter.print_full_documentation() | 1307 self.linter.print_full_documentation() |
1323 sys.exit(0) | 1308 sys.exit(0) |
1324 | 1309 |
1325 def cb_list_messages(self, option, optname, value, parser): # FIXME | 1310 def cb_list_messages(self, option, optname, value, parser): # FIXME |
1326 """optik callback for printing available messages""" | 1311 """optik callback for printing available messages""" |
1327 self.linter.msgs_store.list_messages() | 1312 self.linter.msgs_store.list_messages() |
1328 sys.exit(0) | 1313 sys.exit(0) |
1329 | 1314 |
1330 def cb_python3_porting_mode(self, *args, **kwargs): | 1315 def cb_python3_porting_mode(self, *args, **kwargs): |
1331 """Activate only the python3 porting checker.""" | 1316 """Activate only the python3 porting checker.""" |
1332 self.linter.disable('all') | 1317 self.linter.disable('all') |
1333 self.linter.enable('python3') | 1318 self.linter.enable('python3') |
1334 | 1319 |
1335 | 1320 |
1336 def cb_list_confidence_levels(option, optname, value, parser): | 1321 def cb_list_confidence_levels(option, optname, value, parser): |
1337 for level in CONFIDENCE_LEVELS: | 1322 for level in interfaces.CONFIDENCE_LEVELS: |
1338 print('%-18s: %s' % level) | 1323 print('%-18s: %s' % level) |
1339 sys.exit(0) | 1324 sys.exit(0) |
1340 | 1325 |
1341 def cb_init_hook(optname, value): | 1326 def cb_init_hook(optname, value): |
1342 """exec arbitrary code to set sys.path for instance""" | 1327 """exec arbitrary code to set sys.path for instance""" |
1343 exec(value) # pylint: disable=exec-used | 1328 exec(value) # pylint: disable=exec-used |
1344 | 1329 |
1345 | 1330 |
1346 if __name__ == '__main__': | 1331 if __name__ == '__main__': |
1347 Run(sys.argv[1:]) | 1332 Run(sys.argv[1:]) |
OLD | NEW |