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

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

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

Powered by Google App Engine
This is Rietveld 408576698