Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(364)

Side by Side Diff: third_party/pylint/lint.py

Issue 753543006: pylint: upgrade to 1.4.0 (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/pylint/interfaces.py ('k') | third_party/pylint/pyreverse/diadefslib.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 29
29 # import this first to avoid builtin namespace pollution 30 # import this first to avoid builtin namespace pollution
30 from pylint.checkers import utils #pylint: disable=unused-import 31 from pylint.checkers import utils #pylint: disable=unused-import
31 32
32 import sys 33 import sys
33 import os 34 import os
34 import tokenize 35 import tokenize
36 from collections import defaultdict
37 from contextlib import contextmanager
35 from operator import attrgetter 38 from operator import attrgetter
36 from warnings import warn 39 from warnings import warn
40 from itertools import chain
41 try:
42 import multiprocessing
43 except ImportError:
44 multiprocessing = None
37 45
38 from logilab.common.configuration import UnsupportedAction, OptionsManagerMixIn 46 import six
47 from logilab.common.configuration import (
48 UnsupportedAction, OptionsManagerMixIn)
39 from logilab.common.optik_ext import check_csv 49 from logilab.common.optik_ext import check_csv
40 from logilab.common.interface import implements 50 from logilab.common.interface import implements
41 from logilab.common.textutils import splitstrip, unquote 51 from logilab.common.textutils import splitstrip, unquote
42 from logilab.common.ureports import Table, Text, Section 52 from logilab.common.ureports import Table, Text, Section
43 from logilab.common.__pkginfo__ import version as common_version 53 from logilab.common.__pkginfo__ import version as common_version
44
45 from astroid import MANAGER, AstroidBuildingException 54 from astroid import MANAGER, AstroidBuildingException
46 from astroid.__pkginfo__ import version as astroid_version 55 from astroid.__pkginfo__ import version as astroid_version
47 from astroid.modutils import load_module_from_name, get_module_part 56 from astroid.modutils import load_module_from_name, get_module_part
48 57
49 from pylint.utils import ( 58 from pylint.utils import (
50 MSG_TYPES, OPTION_RGX, 59 MSG_TYPES, OPTION_RGX,
51 PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn, 60 PyLintASTWalker, UnknownMessage, MessagesHandlerMixIn, ReportsHandlerMixIn,
52 MessagesStore, FileState, EmptyReport, 61 MessagesStore, FileState, EmptyReport,
53 expand_modules, tokenize_module) 62 expand_modules, tokenize_module, Message)
54 from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker 63 from pylint.interfaces import IRawChecker, ITokenChecker, IAstroidChecker, CONFI DENCE_LEVELS
55 from pylint.checkers import (BaseTokenChecker, 64 from pylint.checkers import (BaseTokenChecker,
56 table_lines_from_stats, 65 table_lines_from_stats,
57 initialize as checkers_initialize) 66 initialize as checkers_initialize)
58 from pylint.reporters import initialize as reporters_initialize 67 from pylint.reporters import initialize as reporters_initialize, CollectingRepor ter
59 from pylint import config 68 from pylint import config
60
61 from pylint.__pkginfo__ import version 69 from pylint.__pkginfo__ import version
62 70
63 71
72 def _get_new_args(message):
73 location = (
74 message.abspath,
75 message.path,
76 message.module,
77 message.obj,
78 message.line,
79 message.column,
80 )
81 return (
82 message.msg_id,
83 message.symbol,
84 location,
85 message.msg,
86 message.confidence,
87 )
64 88
65 def _get_python_path(filepath): 89 def _get_python_path(filepath):
66 dirname = os.path.dirname(os.path.realpath( 90 dirname = os.path.realpath(os.path.expanduser(filepath))
67 os.path.expanduser(filepath))) 91 if not os.path.isdir(dirname):
92 dirname = os.path.dirname(dirname)
68 while True: 93 while True:
69 if not os.path.exists(os.path.join(dirname, "__init__.py")): 94 if not os.path.exists(os.path.join(dirname, "__init__.py")):
70 return dirname 95 return dirname
71 old_dirname = dirname 96 old_dirname = dirname
72 dirname = os.path.dirname(dirname) 97 dirname = os.path.dirname(dirname)
73 if old_dirname == dirname: 98 if old_dirname == dirname:
74 return os.getcwd() 99 return os.getcwd()
75 100
76 101
102 def _merge_stats(stats):
103 merged = {}
104 for stat in stats:
105 for key, item in six.iteritems(stat):
106 if key not in merged:
107 merged[key] = item
108 else:
109 if isinstance(item, dict):
110 merged[key].update(item)
111 else:
112 merged[key] = merged[key] + item
113 return merged
114
115
77 # Python Linter class ######################################################### 116 # Python Linter class #########################################################
78 117
79 MSGS = { 118 MSGS = {
80 'F0001': ('%s', 119 'F0001': ('%s',
81 'fatal', 120 'fatal',
82 'Used when an error occurred preventing the analysis of a \ 121 'Used when an error occurred preventing the analysis of a \
83 module (unable to find it for instance).'), 122 module (unable to find it for instance).'),
84 'F0002': ('%s: %s', 123 'F0002': ('%s: %s',
85 'astroid-error', 124 'astroid-error',
86 'Used when an unexpected error occurred while building the ' 125 'Used when an unexpected error occurred while building the '
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
140 'E0011': ('Unrecognized file option %r', 179 'E0011': ('Unrecognized file option %r',
141 'unrecognized-inline-option', 180 'unrecognized-inline-option',
142 'Used when an unknown inline option is encountered.'), 181 'Used when an unknown inline option is encountered.'),
143 'E0012': ('Bad option value %r', 182 'E0012': ('Bad option value %r',
144 'bad-option-value', 183 'bad-option-value',
145 'Used when a bad value for an inline option is encountered.'), 184 'Used when a bad value for an inline option is encountered.'),
146 } 185 }
147 186
148 187
149 def _deprecated_option(shortname, opt_type): 188 def _deprecated_option(shortname, opt_type):
150 def _warn_deprecated(option, optname, *args): 189 def _warn_deprecated(option, optname, *args): # pylint: disable=unused-argum ent
151 sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (op tname,)) 190 sys.stderr.write('Warning: option %s is deprecated and ignored.\n' % (op tname,))
152 return {'short': shortname, 'help': 'DEPRECATED', 'hide': True, 191 return {'short': shortname, 'help': 'DEPRECATED', 'hide': True,
153 'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated } 192 'type': opt_type, 'action': 'callback', 'callback': _warn_deprecated }
154 193
155 194
195 if multiprocessing is not None:
196 class ChildLinter(multiprocessing.Process): # pylint: disable=no-member
197 def run(self):
198 tasks_queue, results_queue, config = self._args # pylint: disable=no -member
199
200 for file_or_module in iter(tasks_queue.get, 'STOP'):
201 result = self._run_linter(config, file_or_module[0])
202 try:
203 results_queue.put(result)
204 except Exception as ex:
205 print("internal error with sending report for module %s" % f ile_or_module, file=sys.stderr)
206 print(ex, file=sys.stderr)
207 results_queue.put({})
208
209 def _run_linter(self, config, file_or_module):
210 linter = PyLinter()
211
212 # Register standard checkers.
213 linter.load_default_plugins()
214 # Load command line plugins.
215 # TODO linter.load_plugin_modules(self._plugins)
216
217 linter.disable('pointless-except')
218 linter.disable('suppressed-message')
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
238 # Run the checks.
239 linter.check(file_or_module)
240
241 msgs = [_get_new_args(m) for m in linter.reporter.messages]
242 return (file_or_module, linter.file_state.base_name, linter.current_ name,
243 msgs, linter.stats, linter.msg_status)
244
245
156 class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn, 246 class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
157 BaseTokenChecker): 247 BaseTokenChecker):
158 """lint Python modules using external checkers. 248 """lint Python modules using external checkers.
159 249
160 This is the main checker controlling the other ones and the reports 250 This is the main checker controlling the other ones and the reports
161 generation. It is itself both a raw checker and an astroid checker in order 251 generation. It is itself both a raw checker and an astroid checker in order
162 to: 252 to:
163 * handle message activation / deactivation at the module level 253 * handle message activation / deactivation at the module level
164 * handle some basic but necessary stats'data (number of classes, methods...) 254 * handle some basic but necessary stats'data (number of classes, methods...)
165 255
166 IDE plugins developpers: you may have to call 256 IDE plugins developpers: you may have to call
167 `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want 257 `astroid.builder.MANAGER.astroid_cache.clear()` accross run if you want
168 to ensure the latest code version is actually checked. 258 to ensure the latest code version is actually checked.
169 """ 259 """
170 260
171 __implements__ = (ITokenChecker,) 261 __implements__ = (ITokenChecker,)
172 262
173 name = 'master' 263 name = 'master'
174 priority = 0 264 priority = 0
175 level = 0 265 level = 0
176 msgs = MSGS 266 msgs = MSGS
177 may_be_disabled = False
178 267
179 @staticmethod 268 @staticmethod
180 def make_options(): 269 def make_options():
181 return (('ignore', 270 return (('ignore',
182 {'type' : 'csv', 'metavar' : '<file>[,<file>...]', 271 {'type' : 'csv', 'metavar' : '<file>[,<file>...]',
183 'dest' : 'black_list', 'default' : ('CVS',), 272 'dest' : 'black_list', 'default' : ('CVS',),
184 'help' : 'Add files or directories to the blacklist. ' 273 'help' : 'Add files or directories to the blacklist. '
185 'They should be base names, not paths.'}), 274 'They should be base names, not paths.'}),
186 ('persistent', 275 ('persistent',
187 {'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>', 276 {'default': True, 'type' : 'yn', 'metavar' : '<y_or_n>',
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
231 'warnings messages and the total number of ' 320 'warnings messages and the total number of '
232 'statements analyzed. This is used by the global ' 321 'statements analyzed. This is used by the global '
233 'evaluation report (RP0004).'}), 322 'evaluation report (RP0004).'}),
234 323
235 ('comment', 324 ('comment',
236 {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', 325 {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
237 'group': 'Reports', 'level': 1, 326 'group': 'Reports', 'level': 1,
238 'help' : 'Add a comment according to your evaluation note. ' 327 'help' : 'Add a comment according to your evaluation note. '
239 'This is used by the global evaluation report (RP0004 ).'}), 328 'This is used by the global evaluation report (RP0004 ).'}),
240 329
330 ('confidence',
331 {'type' : 'multiple_choice', 'metavar': '<levels>',
332 'default': '',
333 'choices': [c.name for c in CONFIDENCE_LEVELS],
334 'group': 'Messages control',
335 'help' : 'Only show warnings with the listed confidence levels .'
336 ' Leave empty to show all. Valid levels: %s' % (
337 ', '.join(c.name for c in CONFIDENCE_LEVELS),)}),
338
241 ('enable', 339 ('enable',
242 {'type' : 'csv', 'metavar': '<msg ids>', 340 {'type' : 'csv', 'metavar': '<msg ids>',
243 'short': 'e', 341 'short': 'e',
244 'group': 'Messages control', 342 'group': 'Messages control',
245 'help' : 'Enable the message, report, category or checker with the ' 343 'help' : 'Enable the message, report, category or checker with the '
246 'given id(s). You can either give multiple identifier ' 344 'given id(s). You can either give multiple identifier '
247 'separated by comma (,) or put this option multiple t ime. ' 345 'separated by comma (,) or put this option multiple t ime. '
248 'See also the "--disable" option for examples. '}), 346 'See also the "--disable" option for examples. '}),
249 347
250 ('disable', 348 ('disable',
(...skipping 17 matching lines...) Expand all
268 {'type' : 'string', 'metavar': '<template>', 366 {'type' : 'string', 'metavar': '<template>',
269 'group': 'Reports', 367 'group': 'Reports',
270 'help' : ('Template used to display messages. ' 368 'help' : ('Template used to display messages. '
271 'This is a python new-style format string ' 369 'This is a python new-style format string '
272 'used to format the message information. ' 370 'used to format the message information. '
273 'See doc for all details') 371 'See doc for all details')
274 }), 372 }),
275 373
276 ('include-ids', _deprecated_option('i', 'yn')), 374 ('include-ids', _deprecated_option('i', 'yn')),
277 ('symbols', _deprecated_option('s', 'yn')), 375 ('symbols', _deprecated_option('s', 'yn')),
376
377 ('jobs',
378 {'type' : 'int', 'metavar': '<n-processes>',
379 'short': 'j',
380 'default': 1,
381 'help' : '''Use multiple processes to speed up Pylint.''',
382 }),
383
384 ('unsafe-load-any-extension',
385 {'type': 'yn', 'metavar': '<yn>', 'default': False, 'hide': Tru e,
386 'help': ('Allow loading of arbitrary C extensions. Extensions'
387 ' are imported into the active Python interpreter and '
388 ' may run arbitrary code.')}),
389
390 ('extension-pkg-whitelist',
391 {'type': 'csv', 'metavar': '<pkg[,pkg]>', 'default': [],
392 'help': ('A comma-separated list of package or module names'
393 ' from where C extensions may be loaded. Extensions are'
394 ' loading into the active Python interpreter and may run'
395 ' arbitrary code')}
396 ),
278 ) 397 )
279 398
280 option_groups = ( 399 option_groups = (
281 ('Messages control', 'Options controling analysis messages'), 400 ('Messages control', 'Options controling analysis messages'),
282 ('Reports', 'Options related to output formating and reporting'), 401 ('Reports', 'Options related to output formating and reporting'),
283 ) 402 )
284 403
285 def __init__(self, options=(), reporter=None, option_groups=(), 404 def __init__(self, options=(), reporter=None, option_groups=(),
286 pylintrc=None): 405 pylintrc=None):
287 # some stuff has to be done before ancestors initialization... 406 # some stuff has to be done before ancestors initialization...
288 # 407 #
289 # messages store / checkers / reporter / astroid manager 408 # messages store / checkers / reporter / astroid manager
290 self.msgs_store = MessagesStore() 409 self.msgs_store = MessagesStore()
291 self.reporter = None 410 self.reporter = None
292 self._reporter_name = None 411 self._reporter_name = None
293 self._reporters = {} 412 self._reporters = {}
294 self._checkers = {} 413 self._checkers = defaultdict(list)
414 self._pragma_lineno = {}
295 self._ignore_file = False 415 self._ignore_file = False
296 # visit variables 416 # visit variables
297 self.file_state = FileState() 417 self.file_state = FileState()
298 self.current_name = None 418 self.current_name = None
299 self.current_file = None 419 self.current_file = None
300 self.stats = None 420 self.stats = None
301 # init options 421 # init options
302 self.options = options + PyLinter.make_options() 422 self.options = options + PyLinter.make_options()
303 self.option_groups = option_groups + PyLinter.option_groups 423 self.option_groups = option_groups + PyLinter.option_groups
304 self._options_methods = { 424 self._options_methods = {
(...skipping 26 matching lines...) Expand all
331 self.set_reporter(reporter) 451 self.set_reporter(reporter)
332 452
333 def load_default_plugins(self): 453 def load_default_plugins(self):
334 checkers_initialize(self) 454 checkers_initialize(self)
335 reporters_initialize(self) 455 reporters_initialize(self)
336 # Make sure to load the default reporter, because 456 # Make sure to load the default reporter, because
337 # the option has been set before the plugins had been loaded. 457 # the option has been set before the plugins had been loaded.
338 if not self.reporter: 458 if not self.reporter:
339 self._load_reporter() 459 self._load_reporter()
340 460
341 def prepare_import_path(self, args):
342 """Prepare sys.path for running the linter checks."""
343 if len(args) == 1:
344 sys.path.insert(0, _get_python_path(args[0]))
345 else:
346 sys.path.insert(0, os.getcwd())
347
348 def cleanup_import_path(self):
349 """Revert any changes made to sys.path in prepare_import_path."""
350 sys.path.pop(0)
351
352 def load_plugin_modules(self, modnames): 461 def load_plugin_modules(self, modnames):
353 """take a list of module names which are pylint plugins and load 462 """take a list of module names which are pylint plugins and load
354 and register them 463 and register them
355 """ 464 """
356 for modname in modnames: 465 for modname in modnames:
357 if modname in self._dynamic_plugins: 466 if modname in self._dynamic_plugins:
358 continue 467 continue
359 self._dynamic_plugins.add(modname) 468 self._dynamic_plugins.add(modname)
360 module = load_module_from_name(modname) 469 module = load_module_from_name(modname)
361 module.register(self) 470 module.register(self)
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
397 meth(value) 506 meth(value)
398 elif optname == 'output-format': 507 elif optname == 'output-format':
399 self._reporter_name = value 508 self._reporter_name = value
400 # If the reporters are already available, load 509 # If the reporters are already available, load
401 # the reporter class. 510 # the reporter class.
402 if self._reporters: 511 if self._reporters:
403 self._load_reporter() 512 self._load_reporter()
404 try: 513 try:
405 BaseTokenChecker.set_option(self, optname, value, action, optdict) 514 BaseTokenChecker.set_option(self, optname, value, action, optdict)
406 except UnsupportedAction: 515 except UnsupportedAction:
407 print >> sys.stderr, 'option %s can\'t be read from config file' % \ 516 print('option %s can\'t be read from config file' % \
408 optname 517 optname, file=sys.stderr)
409 518
410 def register_reporter(self, reporter_class): 519 def register_reporter(self, reporter_class):
411 self._reporters[reporter_class.name] = reporter_class 520 self._reporters[reporter_class.name] = reporter_class
412 521
522 def report_order(self):
523 reports = sorted(self._reports, key=lambda x: getattr(x, 'name', ''))
524 try:
525 # Remove the current reporter and add it
526 # at the end of the list.
527 reports.pop(reports.index(self))
528 except ValueError:
529 pass
530 else:
531 reports.append(self)
532 return reports
533
413 # checkers manipulation methods ############################################ 534 # checkers manipulation methods ############################################
414 535
415 def register_checker(self, checker): 536 def register_checker(self, checker):
416 """register a new checker 537 """register a new checker
417 538
418 checker is an object implementing IRawChecker or / and IAstroidChecker 539 checker is an object implementing IRawChecker or / and IAstroidChecker
419 """ 540 """
420 assert checker.priority <= 0, 'checker priority can\'t be >= 0' 541 assert checker.priority <= 0, 'checker priority can\'t be >= 0'
421 self._checkers.setdefault(checker.name, []).append(checker) 542 self._checkers[checker.name].append(checker)
422 for r_id, r_title, r_cb in checker.reports: 543 for r_id, r_title, r_cb in checker.reports:
423 self.register_report(r_id, r_title, r_cb, checker) 544 self.register_report(r_id, r_title, r_cb, checker)
424 self.register_options_provider(checker) 545 self.register_options_provider(checker)
425 if hasattr(checker, 'msgs'): 546 if hasattr(checker, 'msgs'):
426 self.msgs_store.register_messages(checker) 547 self.msgs_store.register_messages(checker)
427 checker.load_defaults() 548 checker.load_defaults()
428 549
550 # Register the checker, but disable all of its messages.
551 # TODO(cpopa): we should have a better API for this.
552 if not getattr(checker, 'enabled', True):
553 self.disable(checker.name)
554
429 def disable_noerror_messages(self): 555 def disable_noerror_messages(self):
430 for msgcat, msgids in self.msgs_store._msgs_by_category.iteritems(): 556 for msgcat, msgids in six.iteritems(self.msgs_store._msgs_by_category):
431 if msgcat == 'E': 557 if msgcat == 'E':
432 for msgid in msgids: 558 for msgid in msgids:
433 self.enable(msgid) 559 self.enable(msgid)
434 else: 560 else:
435 for msgid in msgids: 561 for msgid in msgids:
436 self.disable(msgid) 562 self.disable(msgid)
437 563
438 def disable_reporters(self): 564 def disable_reporters(self):
439 """disable all reporters""" 565 """disable all reporters"""
440 for reporters in self._reports.itervalues(): 566 for reporters in six.itervalues(self._reports):
441 for report_id, _title, _cb in reporters: 567 for report_id, _title, _cb in reporters:
442 self.disable_report(report_id) 568 self.disable_report(report_id)
443 569
444 def error_mode(self): 570 def error_mode(self):
445 """error mode: enable only errors; no reports, no persistent""" 571 """error mode: enable only errors; no reports, no persistent"""
446 self.disable_noerror_messages() 572 self.disable_noerror_messages()
447 self.disable('miscellaneous') 573 self.disable('miscellaneous')
448 self.set_option('reports', False) 574 self.set_option('reports', False)
449 self.set_option('persistent', False) 575 self.set_option('persistent', False)
450 576
451 # block level option handling ############################################# 577 # block level option handling #############################################
452 # 578 #
453 # see func_block_disable_msg.py test case for expected behaviour 579 # see func_block_disable_msg.py test case for expected behaviour
454 580
455 def process_tokens(self, tokens): 581 def process_tokens(self, tokens):
456 """process tokens from the current module to search for module/block 582 """process tokens from the current module to search for module/block
457 level options 583 level options
458 """ 584 """
585 control_pragmas = {'disable', 'enable'}
459 for (tok_type, content, start, _, _) in tokens: 586 for (tok_type, content, start, _, _) in tokens:
460 if tok_type != tokenize.COMMENT: 587 if tok_type != tokenize.COMMENT:
461 continue 588 continue
462 match = OPTION_RGX.search(content) 589 match = OPTION_RGX.search(content)
463 if match is None: 590 if match is None:
464 continue 591 continue
465 if match.group(1).strip() == "disable-all" or \ 592 if match.group(1).strip() == "disable-all" or \
466 match.group(1).strip() == 'skip-file': 593 match.group(1).strip() == 'skip-file':
467 if match.group(1).strip() == "disable-all": 594 if match.group(1).strip() == "disable-all":
468 self.add_message('deprecated-pragma', line=start[0], 595 self.add_message('deprecated-pragma', line=start[0],
469 args=('disable-all', 'skip-file')) 596 args=('disable-all', 'skip-file'))
470 self.add_message('file-ignored', line=start[0]) 597 self.add_message('file-ignored', line=start[0])
471 self._ignore_file = True 598 self._ignore_file = True
472 return 599 return
473 try: 600 try:
474 opt, value = match.group(1).split('=', 1) 601 opt, value = match.group(1).split('=', 1)
475 except ValueError: 602 except ValueError:
476 self.add_message('bad-inline-option', args=match.group(1).strip( ), 603 self.add_message('bad-inline-option', args=match.group(1).strip( ),
477 line=start[0]) 604 line=start[0])
478 continue 605 continue
479 opt = opt.strip() 606 opt = opt.strip()
480 if opt in self._options_methods or opt in self._bw_options_methods: 607 if opt in self._options_methods or opt in self._bw_options_methods:
481 try: 608 try:
482 meth = self._options_methods[opt] 609 meth = self._options_methods[opt]
483 except KeyError: 610 except KeyError:
484 meth = self._bw_options_methods[opt] 611 meth = self._bw_options_methods[opt]
485 # found a "(dis|en)able-msg" pragma deprecated suppresssion 612 # found a "(dis|en)able-msg" pragma deprecated suppresssion
486 self.add_message('deprecated-pragma', line=start[0], args=(o pt, opt.replace('-msg', ''))) 613 self.add_message('deprecated-pragma', line=start[0], args=(o pt, opt.replace('-msg', '')))
487 for msgid in splitstrip(value): 614 for msgid in splitstrip(value):
615 # Add the line where a control pragma was encountered.
616 if opt in control_pragmas:
617 self._pragma_lineno[msgid] = start[0]
618
488 try: 619 try:
489 if (opt, msgid) == ('disable', 'all'): 620 if (opt, msgid) == ('disable', 'all'):
490 self.add_message('deprecated-pragma', line=start[0], args=('disable=all', 'skip-file')) 621 self.add_message('deprecated-pragma', line=start[0], args=('disable=all', 'skip-file'))
491 self.add_message('file-ignored', line=start[0]) 622 self.add_message('file-ignored', line=start[0])
492 self._ignore_file = True 623 self._ignore_file = True
493 return 624 return
494 meth(msgid, 'module', start[0]) 625 meth(msgid, 'module', start[0])
495 except UnknownMessage: 626 except UnknownMessage:
496 self.add_message('bad-option-value', args=msgid, line=st art[0]) 627 self.add_message('bad-option-value', args=msgid, line=st art[0])
497 else: 628 else:
498 self.add_message('unrecognized-inline-option', args=opt, line=st art[0]) 629 self.add_message('unrecognized-inline-option', args=opt, line=st art[0])
499 630
500 631
501 # code checking methods ################################################### 632 # code checking methods ###################################################
502 633
503 def get_checkers(self): 634 def get_checkers(self):
504 """return all available checkers as a list""" 635 """return all available checkers as a list"""
505 return [self] + [c for checkers in self._checkers.itervalues() 636 return [self] + [c for checkers in six.itervalues(self._checkers)
506 for c in checkers if c is not self] 637 for c in checkers if c is not self]
507 638
508 def prepare_checkers(self): 639 def prepare_checkers(self):
509 """return checkers needed for activated messages and reports""" 640 """return checkers needed for activated messages and reports"""
510 if not self.config.reports: 641 if not self.config.reports:
511 self.disable_reporters() 642 self.disable_reporters()
512 # get needed checkers 643 # get needed checkers
513 neededcheckers = [self] 644 neededcheckers = [self]
514 for checker in self.get_checkers()[1:]: 645 for checker in self.get_checkers()[1:]:
515 # fatal errors should not trigger enable / disabling a checker 646 # fatal errors should not trigger enable / disabling a checker
516 messages = set(msg for msg in checker.msgs 647 messages = set(msg for msg in checker.msgs
517 if msg[0] != 'F' and self.is_message_enabled(msg)) 648 if msg[0] != 'F' and self.is_message_enabled(msg))
518 if (messages or 649 if (messages or
519 any(self.report_is_enabled(r[0]) for r in checker.reports)): 650 any(self.report_is_enabled(r[0]) for r in checker.reports)):
520 neededcheckers.append(checker) 651 neededcheckers.append(checker)
521 # Sort checkers by priority 652 # Sort checkers by priority
522 neededcheckers = sorted(neededcheckers, key=attrgetter('priority'), 653 neededcheckers = sorted(neededcheckers, key=attrgetter('priority'),
523 reverse=True) 654 reverse=True)
524 return neededcheckers 655 return neededcheckers
525 656
526 def should_analyze_file(self, modname, path): # pylint: disable=unused-argum ent 657 def should_analyze_file(self, modname, path): # pylint: disable=unused-argum ent, no-self-use
527 """Returns whether or not a module should be checked. 658 """Returns whether or not a module should be checked.
528 659
529 This implementation returns True for all python source file, indicating 660 This implementation returns True for all python source file, indicating
530 that all files should be linted. 661 that all files should be linted.
531 662
532 Subclasses may override this method to indicate that modules satisfying 663 Subclasses may override this method to indicate that modules satisfying
533 certain conditions should not be linted. 664 certain conditions should not be linted.
534 665
535 :param str modname: The name of the module to be checked. 666 :param str modname: The name of the module to be checked.
536 :param str path: The full path to the source code of the module. 667 :param str path: The full path to the source code of the module.
537 :returns: True if the module should be checked. 668 :returns: True if the module should be checked.
538 :rtype: bool 669 :rtype: bool
539 """ 670 """
540 return path.endswith('.py') 671 return path.endswith('.py')
541 672
542 def check(self, files_or_modules): 673 def check(self, files_or_modules):
543 """main checking entry: check a list of files or modules from their 674 """main checking entry: check a list of files or modules from their
544 name. 675 name.
545 """ 676 """
546 # initialize msgs_state now that all messages have been registered into 677 # initialize msgs_state now that all messages have been registered into
547 # the store 678 # the store
548 for msg in self.msgs_store.messages: 679 for msg in self.msgs_store.messages:
549 if not msg.may_be_emitted(): 680 if not msg.may_be_emitted():
550 self._msgs_state[msg.msgid] = False 681 self._msgs_state[msg.msgid] = False
551 682
552 if not isinstance(files_or_modules, (list, tuple)): 683 if not isinstance(files_or_modules, (list, tuple)):
553 files_or_modules = (files_or_modules,) 684 files_or_modules = (files_or_modules,)
685
686 if self.config.jobs == 1:
687 self._do_check(files_or_modules)
688 else:
689 # Hack that permits running pylint, on Windows, with -m switch
690 # and with --jobs, as in 'py -2 -m pylint .. --jobs'.
691 # For more details why this is needed,
692 # see Python issue http://bugs.python.org/issue10845.
693
694 mock_main = six.PY2 and __name__ != '__main__' # -m switch
695 if mock_main:
696 sys.modules['__main__'] = sys.modules[__name__]
697 try:
698 self._parallel_check(files_or_modules)
699 finally:
700 if mock_main:
701 sys.modules.pop('__main__')
702
703 def _parallel_task(self, files_or_modules):
704 # Prepare configuration for child linters.
705 config = vars(self.config)
706 childs = []
707 manager = multiprocessing.Manager() # pylint: disable=no-member
708 tasks_queue = manager.Queue() # pylint: disable=no-member
709 results_queue = manager.Queue() # pylint: disable=no-member
710
711 for _ in range(self.config.jobs):
712 cl = ChildLinter(args=(tasks_queue, results_queue, config))
713 cl.start() # pylint: disable=no-member
714 childs.append(cl)
715
716 # send files to child linters
717 for files_or_module in files_or_modules:
718 tasks_queue.put([files_or_module])
719
720 # collect results from child linters
721 failed = False
722 for _ in files_or_modules:
723 try:
724 result = results_queue.get()
725 except Exception as ex:
726 print("internal error while receiving results from child linter" ,
727 file=sys.stderr)
728 print(ex, file=sys.stderr)
729 failed = True
730 break
731 yield result
732
733 # Stop child linters and wait for their completion.
734 for _ in range(self.config.jobs):
735 tasks_queue.put('STOP')
736 for cl in childs:
737 cl.join()
738
739 if failed:
740 print("Error occured, stopping the linter.", file=sys.stderr)
741 sys.exit(32)
742
743 def _parallel_check(self, files_or_modules):
744 # Reset stats.
745 self.open()
746
747 all_stats = []
748 for result in self._parallel_task(files_or_modules):
749 (
750 file_or_module,
751 self.file_state.base_name,
752 module,
753 messages,
754 stats,
755 msg_status
756 ) = result
757
758 if file_or_module == files_or_modules[-1]:
759 last_module = module
760
761 for msg in messages:
762 msg = Message(*msg)
763 self.set_current_module(module)
764 self.reporter.handle_message(msg)
765
766 all_stats.append(stats)
767 self.msg_status |= msg_status
768
769 self.stats = _merge_stats(chain(all_stats, [self.stats]))
770 self.current_name = last_module
771
772 # Insert stats data to local checkers.
773 for checker in self.get_checkers():
774 if checker is not self:
775 checker.stats = self.stats
776
777 def _do_check(self, files_or_modules):
554 walker = PyLintASTWalker(self) 778 walker = PyLintASTWalker(self)
555 checkers = self.prepare_checkers() 779 checkers = self.prepare_checkers()
556 tokencheckers = [c for c in checkers if implements(c, ITokenChecker) 780 tokencheckers = [c for c in checkers if implements(c, ITokenChecker)
557 and c is not self] 781 and c is not self]
558 rawcheckers = [c for c in checkers if implements(c, IRawChecker)] 782 rawcheckers = [c for c in checkers if implements(c, IRawChecker)]
559 # notify global begin 783 # notify global begin
560 for checker in checkers: 784 for checker in checkers:
561 checker.open() 785 checker.open()
562 if implements(checker, IAstroidChecker): 786 if implements(checker, IAstroidChecker):
563 walker.add_checker(checker) 787 walker.add_checker(checker)
(...skipping 16 matching lines...) Expand all
580 self.file_state = FileState(descr['basename']) 804 self.file_state = FileState(descr['basename'])
581 self._ignore_file = False 805 self._ignore_file = False
582 # fix the current file (if the source file was not available or 806 # fix the current file (if the source file was not available or
583 # if it's actually a c extension) 807 # if it's actually a c extension)
584 self.current_file = astroid.file # pylint: disable=maybe-no-member 808 self.current_file = astroid.file # pylint: disable=maybe-no-member
585 self.check_astroid_module(astroid, walker, rawcheckers, tokenchecker s) 809 self.check_astroid_module(astroid, walker, rawcheckers, tokenchecker s)
586 # warn about spurious inline messages handling 810 # warn about spurious inline messages handling
587 for msgid, line, args in self.file_state.iter_spurious_suppression_m essages(self.msgs_store): 811 for msgid, line, args in self.file_state.iter_spurious_suppression_m essages(self.msgs_store):
588 self.add_message(msgid, line, None, args) 812 self.add_message(msgid, line, None, args)
589 # notify global end 813 # notify global end
590 self.set_current_module('')
591 self.stats['statement'] = walker.nbstatements 814 self.stats['statement'] = walker.nbstatements
592 checkers.reverse() 815 checkers.reverse()
593 for checker in checkers: 816 for checker in checkers:
594 checker.close() 817 checker.close()
595 818
596 def expand_files(self, modules): 819 def expand_files(self, modules):
597 """get modules and errors from a list of modules and handle errors 820 """get modules and errors from a list of modules and handle errors
598 """ 821 """
599 result, errors = expand_modules(modules, self.config.black_list) 822 result, errors = expand_modules(modules, self.config.black_list)
600 for error in errors: 823 for error in errors:
601 message = modname = error["mod"] 824 message = modname = error["mod"]
602 key = error["key"] 825 key = error["key"]
603 self.set_current_module(modname) 826 self.set_current_module(modname)
604 if key == "fatal": 827 if key == "fatal":
605 message = str(error["ex"]).replace(os.getcwd() + os.sep, '') 828 message = str(error["ex"]).replace(os.getcwd() + os.sep, '')
606 self.add_message(key, args=message) 829 self.add_message(key, args=message)
607 return result 830 return result
608 831
609 def set_current_module(self, modname, filepath=None): 832 def set_current_module(self, modname, filepath=None):
610 """set the name of the currently analyzed module and 833 """set the name of the currently analyzed module and
611 init statistics for it 834 init statistics for it
612 """ 835 """
613 if not modname and filepath is None: 836 if not modname and filepath is None:
614 return 837 return
615 self.reporter.on_set_current_module(modname, filepath) 838 self.reporter.on_set_current_module(modname, filepath)
616 self.current_name = modname 839 self.current_name = modname
617 self.current_file = filepath or modname 840 self.current_file = filepath or modname
618 self.stats['by_module'][modname] = {} 841 self.stats['by_module'][modname] = {}
619 self.stats['by_module'][modname]['statement'] = 0 842 self.stats['by_module'][modname]['statement'] = 0
620 for msg_cat in MSG_TYPES.itervalues(): 843 for msg_cat in six.itervalues(MSG_TYPES):
621 self.stats['by_module'][modname][msg_cat] = 0 844 self.stats['by_module'][modname][msg_cat] = 0
622 845
623 def get_ast(self, filepath, modname): 846 def get_ast(self, filepath, modname):
624 """return a ast(roid) representation for a module""" 847 """return a ast(roid) representation for a module"""
625 try: 848 try:
626 return MANAGER.ast_from_file(filepath, modname, source=True) 849 return MANAGER.ast_from_file(filepath, modname, source=True)
627 except SyntaxError, ex: 850 except SyntaxError as ex:
628 self.add_message('syntax-error', line=ex.lineno, args=ex.msg) 851 self.add_message('syntax-error', line=ex.lineno, args=ex.msg)
629 except AstroidBuildingException, ex: 852 except AstroidBuildingException as ex:
630 self.add_message('parse-error', args=ex) 853 self.add_message('parse-error', args=ex)
631 except Exception, ex: 854 except Exception as ex: # pylint: disable=broad-except
632 import traceback 855 import traceback
633 traceback.print_exc() 856 traceback.print_exc()
634 self.add_message('astroid-error', args=(ex.__class__, ex)) 857 self.add_message('astroid-error', args=(ex.__class__, ex))
635 858
636 def check_astroid_module(self, astroid, walker, rawcheckers, tokencheckers): 859 def check_astroid_module(self, astroid, walker, rawcheckers, tokencheckers):
637 """check a module from its astroid representation, real work""" 860 """check a module from its astroid representation, real work"""
861 try:
862 return self._check_astroid_module(astroid, walker,
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) :
638 # call raw checkers if possible 875 # call raw checkers if possible
639 try: 876 try:
640 tokens = tokenize_module(astroid) 877 tokens = tokenize_module(astroid)
641 except tokenize.TokenError, ex: 878 except tokenize.TokenError as ex:
642 self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0] ) 879 self.add_message('syntax-error', line=ex.args[1][0], args=ex.args[0] )
643 return 880 return
644 881
645 if not astroid.pure_python: 882 if not astroid.pure_python:
646 self.add_message('raw-checker-failed', args=astroid.name) 883 self.add_message('raw-checker-failed', args=astroid.name)
647 else: 884 else:
648 #assert astroid.file.endswith('.py') 885 #assert astroid.file.endswith('.py')
649 # invoke ITokenChecker interface on self to fetch module/block 886 # invoke ITokenChecker interface on self to fetch module/block
650 # level options 887 # level options
651 self.process_tokens(tokens) 888 self.process_tokens(tokens)
(...skipping 10 matching lines...) Expand all
662 walker.walk(astroid) 899 walker.walk(astroid)
663 return True 900 return True
664 901
665 # IAstroidChecker interface ################################################ # 902 # IAstroidChecker interface ################################################ #
666 903
667 def open(self): 904 def open(self):
668 """initialize counters""" 905 """initialize counters"""
669 self.stats = {'by_module' : {}, 906 self.stats = {'by_module' : {},
670 'by_msg' : {}, 907 'by_msg' : {},
671 } 908 }
672 for msg_cat in MSG_TYPES.itervalues(): 909 MANAGER.always_load_extensions = self.config.unsafe_load_any_extension
910 MANAGER.extension_package_whitelist.update(self.config.extension_pkg_whi telist)
911 for msg_cat in six.itervalues(MSG_TYPES):
673 self.stats[msg_cat] = 0 912 self.stats[msg_cat] = 0
674 913
675 def close(self): 914 def generate_reports(self):
676 """close the whole package /module, it's time to make reports ! 915 """close the whole package /module, it's time to make reports !
677 916
678 if persistent run, pickle results for later comparison 917 if persistent run, pickle results for later comparison
679 """ 918 """
680 if self.file_state.base_name is not None: 919 if self.file_state.base_name is not None:
681 # load previous results if any 920 # load previous results if any
682 previous_stats = config.load_results(self.file_state.base_name) 921 previous_stats = config.load_results(self.file_state.base_name)
683 # XXX code below needs refactoring to be more reporter agnostic 922 # XXX code below needs refactoring to be more reporter agnostic
684 self.reporter.on_close(self.stats, previous_stats) 923 self.reporter.on_close(self.stats, previous_stats)
685 if self.config.reports: 924 if self.config.reports:
686 sect = self.make_reports(self.stats, previous_stats) 925 sect = self.make_reports(self.stats, previous_stats)
687 if self.config.files_output: 926 if self.config.files_output:
688 filename = 'pylint_global.' + self.reporter.extension 927 filename = 'pylint_global.' + self.reporter.extension
689 self.reporter.set_output(open(filename, 'w')) 928 self.reporter.set_output(open(filename, 'w'))
690 else: 929 else:
691 sect = Section() 930 sect = Section()
692 if self.config.reports or self.config.output_format == 'html': 931 if self.config.reports or self.config.output_format == 'html':
693 self.reporter.display_results(sect) 932 self.reporter.display_results(sect)
694 # save results if persistent run 933 # save results if persistent run
695 if self.config.persistent: 934 if self.config.persistent:
696 config.save_results(self.stats, self.file_state.base_name) 935 config.save_results(self.stats, self.file_state.base_name)
697 else: 936 else:
937 if self.config.output_format == 'html':
938 # No output will be emitted for the html
939 # reporter if the file doesn't exist, so emit
940 # the results here.
941 self.reporter.display_results(Section())
698 self.reporter.on_close(self.stats, {}) 942 self.reporter.on_close(self.stats, {})
699 943
700 # specific reports ######################################################## 944 # specific reports ########################################################
701 945
702 def report_evaluation(self, sect, stats, previous_stats): 946 def report_evaluation(self, sect, stats, previous_stats):
703 """make the global evaluation report""" 947 """make the global evaluation report"""
704 # check with at least check 1 statements (usually 0 when there is a 948 # check with at least check 1 statements (usually 0 when there is a
705 # syntax error preventing pylint from further processing) 949 # syntax error preventing pylint from further processing)
706 if stats['statement'] == 0: 950 if stats['statement'] == 0:
707 raise EmptyReport() 951 raise EmptyReport()
708 # get a global note for the code 952 # get a global note for the code
709 evaluation = self.config.evaluation 953 evaluation = self.config.evaluation
710 try: 954 try:
711 note = eval(evaluation, {}, self.stats) 955 note = eval(evaluation, {}, self.stats) # pylint: disable=eval-used
712 except Exception, ex: 956 except Exception as ex: # pylint: disable=broad-except
713 msg = 'An exception occurred while rating: %s' % ex 957 msg = 'An exception occurred while rating: %s' % ex
714 else: 958 else:
715 stats['global_note'] = note 959 stats['global_note'] = note
716 msg = 'Your code has been rated at %.2f/10' % note 960 msg = 'Your code has been rated at %.2f/10' % note
717 pnote = previous_stats.get('global_note') 961 pnote = previous_stats.get('global_note')
718 if pnote is not None: 962 if pnote is not None:
719 msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote) 963 msg += ' (previous run: %.2f/10, %+.2f)' % (pnote, note - pnote)
720 if self.config.comment: 964 if self.config.comment:
721 msg = '%s\n%s' % (msg, config.get_note_message(note)) 965 msg = '%s\n%s' % (msg, config.get_note_message(note))
722 sect.append(Text(msg)) 966 sect.append(Text(msg))
723 967
724 # some reporting functions #################################################### 968 # some reporting functions ####################################################
725 969
726 def report_total_messages_stats(sect, stats, previous_stats): 970 def report_total_messages_stats(sect, stats, previous_stats):
727 """make total errors / warnings report""" 971 """make total errors / warnings report"""
728 lines = ['type', 'number', 'previous', 'difference'] 972 lines = ['type', 'number', 'previous', 'difference']
729 lines += table_lines_from_stats(stats, previous_stats, 973 lines += table_lines_from_stats(stats, previous_stats,
730 ('convention', 'refactor', 974 ('convention', 'refactor',
731 'warning', 'error')) 975 'warning', 'error'))
732 sect.append(Table(children=lines, cols=4, rheaders=1)) 976 sect.append(Table(children=lines, cols=4, rheaders=1))
733 977
734 def report_messages_stats(sect, stats, _): 978 def report_messages_stats(sect, stats, _):
735 """make messages type report""" 979 """make messages type report"""
736 if not stats['by_msg']: 980 if not stats['by_msg']:
737 # don't print this report when we didn't detected any errors 981 # don't print this report when we didn't detected any errors
738 raise EmptyReport() 982 raise EmptyReport()
739 in_order = sorted([(value, msg_id) 983 in_order = sorted([(value, msg_id)
740 for msg_id, value in stats['by_msg'].iteritems() 984 for msg_id, value in six.iteritems(stats['by_msg'])
741 if not msg_id.startswith('I')]) 985 if not msg_id.startswith('I')])
742 in_order.reverse() 986 in_order.reverse()
743 lines = ('message id', 'occurrences') 987 lines = ('message id', 'occurrences')
744 for value, msg_id in in_order: 988 for value, msg_id in in_order:
745 lines += (msg_id, str(value)) 989 lines += (msg_id, str(value))
746 sect.append(Table(children=lines, cols=2, rheaders=1)) 990 sect.append(Table(children=lines, cols=2, rheaders=1))
747 991
748 def report_messages_by_module_stats(sect, stats, _): 992 def report_messages_by_module_stats(sect, stats, _):
749 """make errors / warnings by modules report""" 993 """make errors / warnings by modules report"""
750 if len(stats['by_module']) == 1: 994 if len(stats['by_module']) == 1:
751 # don't print this report when we are analysing a single module 995 # don't print this report when we are analysing a single module
752 raise EmptyReport() 996 raise EmptyReport()
753 by_mod = {} 997 by_mod = defaultdict(dict)
754 for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'): 998 for m_type in ('fatal', 'error', 'warning', 'refactor', 'convention'):
755 total = stats[m_type] 999 total = stats[m_type]
756 for module in stats['by_module'].iterkeys(): 1000 for module in six.iterkeys(stats['by_module']):
757 mod_total = stats['by_module'][module][m_type] 1001 mod_total = stats['by_module'][module][m_type]
758 if total == 0: 1002 if total == 0:
759 percent = 0 1003 percent = 0
760 else: 1004 else:
761 percent = float((mod_total)*100) / total 1005 percent = float((mod_total)*100) / total
762 by_mod.setdefault(module, {})[m_type] = percent 1006 by_mod[module][m_type] = percent
763 sorted_result = [] 1007 sorted_result = []
764 for module, mod_info in by_mod.iteritems(): 1008 for module, mod_info in six.iteritems(by_mod):
765 sorted_result.append((mod_info['error'], 1009 sorted_result.append((mod_info['error'],
766 mod_info['warning'], 1010 mod_info['warning'],
767 mod_info['refactor'], 1011 mod_info['refactor'],
768 mod_info['convention'], 1012 mod_info['convention'],
769 module)) 1013 module))
770 sorted_result.sort() 1014 sorted_result.sort()
771 sorted_result.reverse() 1015 sorted_result.reverse()
772 lines = ['module', 'error', 'warning', 'refactor', 'convention'] 1016 lines = ['module', 'error', 'warning', 'refactor', 'convention']
773 for line in sorted_result: 1017 for line in sorted_result:
774 if line[0] == 0 and line[1] == 0: 1018 # Don't report clean modules.
775 break 1019 if all(entry == 0 for entry in line[:-1]):
1020 continue
776 lines.append(line[-1]) 1021 lines.append(line[-1])
777 for val in line[:-1]: 1022 for val in line[:-1]:
778 lines.append('%.2f' % val) 1023 lines.append('%.2f' % val)
779 if len(lines) == 5: 1024 if len(lines) == 5:
780 raise EmptyReport() 1025 raise EmptyReport()
781 sect.append(Table(children=lines, cols=5, rheaders=1)) 1026 sect.append(Table(children=lines, cols=5, rheaders=1))
782 1027
783 1028
784 # utilities ################################################################### 1029 # utilities ###################################################################
785 1030
786 # this may help to import modules using gettext
787 # XXX syt, actually needed since we don't import code?
788
789 from logilab.common.compat import builtins
790 builtins._ = str
791
792 1031
793 class ArgumentPreprocessingError(Exception): 1032 class ArgumentPreprocessingError(Exception):
794 """Raised if an error occurs during argument preprocessing.""" 1033 """Raised if an error occurs during argument preprocessing."""
795 1034
796 1035
797 def preprocess_options(args, search_for): 1036 def preprocess_options(args, search_for):
798 """look for some options (keys of <search_for>) which have to be processed 1037 """look for some options (keys of <search_for>) which have to be processed
799 before others 1038 before others
800 1039
801 values of <search_for> are callback functions to call when the option is 1040 values of <search_for> are callback functions to call when the option is
(...skipping 19 matching lines...) Expand all
821 raise ArgumentPreprocessingError(msg) 1060 raise ArgumentPreprocessingError(msg)
822 val = args[i] 1061 val = args[i]
823 del args[i] 1062 del args[i]
824 elif not takearg and val is not None: 1063 elif not takearg and val is not None:
825 msg = "Option %s doesn't expects a value" % option 1064 msg = "Option %s doesn't expects a value" % option
826 raise ArgumentPreprocessingError(msg) 1065 raise ArgumentPreprocessingError(msg)
827 cb(option, val) 1066 cb(option, val)
828 else: 1067 else:
829 i += 1 1068 i += 1
830 1069
1070
1071 @contextmanager
1072 def fix_import_path(args):
1073 """Prepare sys.path for running the linter checks.
1074
1075 Within this context, each of the given arguments is importable.
1076 Paths are added to sys.path in corresponding order to the arguments.
1077 We avoid adding duplicate directories to sys.path.
1078 `sys.path` is reset to its original value upon exitign this context.
1079 """
1080 orig = list(sys.path)
1081 changes = []
1082 for arg in args:
1083 path = _get_python_path(arg)
1084 if path in changes:
1085 continue
1086 else:
1087 changes.append(path)
1088 sys.path[:] = changes + sys.path
1089 try:
1090 yield
1091 finally:
1092 sys.path[:] = orig
1093
1094
831 class Run(object): 1095 class Run(object):
832 """helper class to use as main for pylint : 1096 """helper class to use as main for pylint :
833 1097
834 run(*sys.argv[1:]) 1098 run(*sys.argv[1:])
835 """ 1099 """
836 LinterClass = PyLinter 1100 LinterClass = PyLinter
837 option_groups = ( 1101 option_groups = (
838 ('Commands', 'Options which are actually commands. Options in this \ 1102 ('Commands', 'Options which are actually commands. Options in this \
839 group are mutually exclusive.'), 1103 group are mutually exclusive.'),
840 ) 1104 )
841 1105
842 def __init__(self, args, reporter=None, exit=True): 1106 def __init__(self, args, reporter=None, exit=True):
843 self._rcfile = None 1107 self._rcfile = None
844 self._plugins = [] 1108 self._plugins = []
845 try: 1109 try:
846 preprocess_options(args, { 1110 preprocess_options(args, {
847 # option: (callback, takearg) 1111 # option: (callback, takearg)
848 'init-hook': (cb_init_hook, True), 1112 'init-hook': (cb_init_hook, True),
849 'rcfile': (self.cb_set_rcfile, True), 1113 'rcfile': (self.cb_set_rcfile, True),
850 'load-plugins': (self.cb_add_plugins, True), 1114 'load-plugins': (self.cb_add_plugins, True),
851 }) 1115 })
852 except ArgumentPreprocessingError, ex: 1116 except ArgumentPreprocessingError as ex:
853 print >> sys.stderr, ex 1117 print(ex, file=sys.stderr)
854 sys.exit(32) 1118 sys.exit(32)
855 1119
856 self.linter = linter = self.LinterClass(( 1120 self.linter = linter = self.LinterClass((
857 ('rcfile', 1121 ('rcfile',
858 {'action' : 'callback', 'callback' : lambda *args: 1, 1122 {'action' : 'callback', 'callback' : lambda *args: 1,
859 'type': 'string', 'metavar': '<file>', 1123 'type': 'string', 'metavar': '<file>',
860 'help' : 'Specify a configuration file.'}), 1124 'help' : 'Specify a configuration file.'}),
861 1125
862 ('init-hook', 1126 ('init-hook',
863 {'action' : 'callback', 'callback' : lambda *args: 1, 1127 {'action' : 'callback', 'callback' : lambda *args: 1,
864 'type' : 'string', 'metavar': '<code>', 1128 'type' : 'string', 'metavar': '<code>',
865 'level': 1, 1129 'level': 1,
866 'help' : 'Python code to execute, usually for sys.path ' 1130 'help' : 'Python code to execute, usually for sys.path '
867 'manipulation such as pygtk.require().'}), 1131 'manipulation such as pygtk.require().'}),
868 1132
869 ('help-msg', 1133 ('help-msg',
870 {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>', 1134 {'action' : 'callback', 'type' : 'string', 'metavar': '<msg-id>',
871 'callback' : self.cb_help_message, 1135 'callback' : self.cb_help_message,
872 'group': 'Commands', 1136 'group': 'Commands',
873 'help' : 'Display a help message for the given message id and ' 1137 'help' : 'Display a help message for the given message id and '
874 'exit. The value may be a comma separated list of message ids.'}), 1138 'exit. The value may be a comma separated list of message ids.'}),
875 1139
876 ('list-msgs', 1140 ('list-msgs',
877 {'action' : 'callback', 'metavar': '<msg-id>', 1141 {'action' : 'callback', 'metavar': '<msg-id>',
878 'callback' : self.cb_list_messages, 1142 'callback' : self.cb_list_messages,
879 'group': 'Commands', 'level': 1, 1143 'group': 'Commands', 'level': 1,
880 'help' : "Generate pylint's messages."}), 1144 'help' : "Generate pylint's messages."}),
881 1145
1146 ('list-conf-levels',
1147 {'action' : 'callback',
1148 'callback' : cb_list_confidence_levels,
1149 'group': 'Commands', 'level': 1,
1150 'help' : "Generate pylint's messages."}),
1151
882 ('full-documentation', 1152 ('full-documentation',
883 {'action' : 'callback', 'metavar': '<msg-id>', 1153 {'action' : 'callback', 'metavar': '<msg-id>',
884 'callback' : self.cb_full_documentation, 1154 'callback' : self.cb_full_documentation,
885 'group': 'Commands', 'level': 1, 1155 'group': 'Commands', 'level': 1,
886 'help' : "Generate pylint's full documentation."}), 1156 'help' : "Generate pylint's full documentation."}),
887 1157
888 ('generate-rcfile', 1158 ('generate-rcfile',
889 {'action' : 'callback', 'callback' : self.cb_generate_config, 1159 {'action' : 'callback', 'callback' : self.cb_generate_config,
890 'group': 'Commands', 1160 'group': 'Commands',
891 'help' : 'Generate a sample configuration file according to ' 1161 'help' : 'Generate a sample configuration file according to '
892 'the current configuration. You can put other options ' 1162 'the current configuration. You can put other options '
893 'before this one to get them in the generated ' 1163 'before this one to get them in the generated '
894 'configuration.'}), 1164 'configuration.'}),
895 1165
896 ('generate-man', 1166 ('generate-man',
897 {'action' : 'callback', 'callback' : self.cb_generate_manpage, 1167 {'action' : 'callback', 'callback' : self.cb_generate_manpage,
898 'group': 'Commands', 1168 'group': 'Commands',
899 'help' : "Generate pylint's man page.", 'hide': True}), 1169 'help' : "Generate pylint's man page.", 'hide': True}),
900 1170
901 ('errors-only', 1171 ('errors-only',
902 {'action' : 'callback', 'callback' : self.cb_error_mode, 1172 {'action' : 'callback', 'callback' : self.cb_error_mode,
903 'short': 'E', 1173 'short': 'E',
904 'help' : 'In error mode, checkers without error messages are ' 1174 'help' : 'In error mode, checkers without error messages are '
905 'disabled and for others, only the ERROR messages are ' 1175 'disabled and for others, only the ERROR messages are '
906 'displayed, and no reports are done by default'''}), 1176 'displayed, and no reports are done by default'''}),
907 1177
1178 ('py3k',
1179 {'action' : 'callback', 'callback' : self.cb_python3_porting_mode,
1180 'help' : 'In Python 3 porting mode, all checkers will be '
1181 'disabled and only messages emitted by the porting '
1182 'checker will be displayed'}),
1183
908 ('profile', 1184 ('profile',
909 {'type' : 'yn', 'metavar' : '<y_or_n>', 1185 {'type' : 'yn', 'metavar' : '<y_or_n>',
910 'default': False, 'hide': True, 1186 'default': False, 'hide': True,
911 'help' : 'Profiled execution.'}), 1187 'help' : 'Profiled execution.'}),
912 1188
913 ), option_groups=self.option_groups, pylintrc=self._rcfile) 1189 ), option_groups=self.option_groups, pylintrc=self._rcfile)
914 # register standard checkers 1190 # register standard checkers
915 linter.load_default_plugins() 1191 linter.load_default_plugins()
916 # load command line plugins 1192 # load command line plugins
917 linter.load_plugin_modules(self._plugins) 1193 linter.load_plugin_modules(self._plugins)
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
961 # now we can load file config and command line, plugins (which can 1237 # now we can load file config and command line, plugins (which can
962 # provide options) have been registered 1238 # provide options) have been registered
963 linter.load_config_file() 1239 linter.load_config_file()
964 if reporter: 1240 if reporter:
965 # if a custom reporter is provided as argument, it may be overridden 1241 # if a custom reporter is provided as argument, it may be overridden
966 # by file parameters, so re-set it here, but before command line 1242 # by file parameters, so re-set it here, but before command line
967 # parsing so it's still overrideable by command line option 1243 # parsing so it's still overrideable by command line option
968 linter.set_reporter(reporter) 1244 linter.set_reporter(reporter)
969 try: 1245 try:
970 args = linter.load_command_line_configuration(args) 1246 args = linter.load_command_line_configuration(args)
971 except SystemExit, exc: 1247 except SystemExit as exc:
972 if exc.code == 2: # bad options 1248 if exc.code == 2: # bad options
973 exc.code = 32 1249 exc.code = 32
974 raise 1250 raise
975 if not args: 1251 if not args:
976 print linter.help() 1252 print(linter.help())
977 sys.exit(32) 1253 sys.exit(32)
1254
1255 if linter.config.jobs < 0:
1256 print("Jobs number (%d) should be greater than 0"
1257 % linter.config.jobs, file=sys.stderr)
1258 sys.exit(32)
1259 if linter.config.jobs > 1 or linter.config.jobs == 0:
1260 if multiprocessing is None:
1261 print("Multiprocessing library is missing, "
1262 "fallback to single process", file=sys.stderr)
1263 linter.set_option("jobs", 1)
1264 else:
1265 if linter.config.jobs == 0:
1266 linter.config.jobs = multiprocessing.cpu_count()
1267
978 # insert current working directory to the python path to have a correct 1268 # insert current working directory to the python path to have a correct
979 # behaviour 1269 # behaviour
980 linter.prepare_import_path(args) 1270 with fix_import_path(args):
981 if self.linter.config.profile: 1271 if self.linter.config.profile:
982 print >> sys.stderr, '** profiled run' 1272 print('** profiled run', file=sys.stderr)
983 import cProfile, pstats 1273 import cProfile, pstats
984 cProfile.runctx('linter.check(%r)' % args, globals(), locals(), 1274 cProfile.runctx('linter.check(%r)' % args, globals(), locals(),
985 'stones.prof') 1275 'stones.prof')
986 data = pstats.Stats('stones.prof') 1276 data = pstats.Stats('stones.prof')
987 data.strip_dirs() 1277 data.strip_dirs()
988 data.sort_stats('time', 'calls') 1278 data.sort_stats('time', 'calls')
989 data.print_stats(30) 1279 data.print_stats(30)
990 else: 1280 else:
991 linter.check(args) 1281 linter.check(args)
992 linter.cleanup_import_path() 1282 linter.generate_reports()
993 if exit: 1283 if exit:
994 sys.exit(self.linter.msg_status) 1284 sys.exit(self.linter.msg_status)
995 1285
996 def cb_set_rcfile(self, name, value): 1286 def cb_set_rcfile(self, name, value):
997 """callback for option preprocessing (i.e. before option parsing)""" 1287 """callback for option preprocessing (i.e. before option parsing)"""
998 self._rcfile = value 1288 self._rcfile = value
999 1289
1000 def cb_add_plugins(self, name, value): 1290 def cb_add_plugins(self, name, value):
1001 """callback for option preprocessing (i.e. before option parsing)""" 1291 """callback for option preprocessing (i.e. before option parsing)"""
1002 self._plugins.extend(splitstrip(value)) 1292 self._plugins.extend(splitstrip(value))
(...skipping 27 matching lines...) Expand all
1030 def cb_full_documentation(self, option, optname, value, parser): 1320 def cb_full_documentation(self, option, optname, value, parser):
1031 """optik callback for printing full documentation""" 1321 """optik callback for printing full documentation"""
1032 self.linter.print_full_documentation() 1322 self.linter.print_full_documentation()
1033 sys.exit(0) 1323 sys.exit(0)
1034 1324
1035 def cb_list_messages(self, option, optname, value, parser): # FIXME 1325 def cb_list_messages(self, option, optname, value, parser): # FIXME
1036 """optik callback for printing available messages""" 1326 """optik callback for printing available messages"""
1037 self.linter.msgs_store.list_messages() 1327 self.linter.msgs_store.list_messages()
1038 sys.exit(0) 1328 sys.exit(0)
1039 1329
1330 def cb_python3_porting_mode(self, *args, **kwargs):
1331 """Activate only the python3 porting checker."""
1332 self.linter.disable('all')
1333 self.linter.enable('python3')
1334
1335
1336 def cb_list_confidence_levels(option, optname, value, parser):
1337 for level in CONFIDENCE_LEVELS:
1338 print('%-18s: %s' % level)
1339 sys.exit(0)
1340
1040 def cb_init_hook(optname, value): 1341 def cb_init_hook(optname, value):
1041 """exec arbitrary code to set sys.path for instance""" 1342 """exec arbitrary code to set sys.path for instance"""
1042 exec value 1343 exec(value) # pylint: disable=exec-used
1043 1344
1044 1345
1045 if __name__ == '__main__': 1346 if __name__ == '__main__':
1046 Run(sys.argv[1:]) 1347 Run(sys.argv[1:])
OLDNEW
« no previous file with comments | « third_party/pylint/interfaces.py ('k') | third_party/pylint/pyreverse/diadefslib.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698