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

Side by Side Diff: third_party/pylint/pylint/utils.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
« no previous file with comments | « third_party/pylint/pylint/testutils.py ('k') | tools/checklicenses/checklicenses.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 """some various utilities and helper classes, most of them used in the
17 main pylint class
18 """
19 from __future__ import print_function
20
21 import collections
22 import os
23 import re
24 import sys
25 import tokenize
26 import warnings
27 from os.path import dirname, basename, splitext, exists, isdir, join, normpath
28
29 import six
30 from six.moves import zip # pylint: disable=redefined-builtin
31
32 from logilab.common.interface import implements
33 from logilab.common.textutils import normalize_text
34 from logilab.common.configuration import rest_format_section
35 from logilab.common.ureports import Section
36
37 from astroid import nodes, Module
38 from astroid.modutils import modpath_from_file, get_module_files, \
39 file_from_modpath, load_module_from_file
40
41 from pylint.interfaces import IRawChecker, ITokenChecker, UNDEFINED
42
43
44 class UnknownMessage(Exception):
45 """raised when a unregistered message id is encountered"""
46
47 class EmptyReport(Exception):
48 """raised when a report is empty and so should not be displayed"""
49
50
51 MSG_TYPES = {
52 'I' : 'info',
53 'C' : 'convention',
54 'R' : 'refactor',
55 'W' : 'warning',
56 'E' : 'error',
57 'F' : 'fatal'
58 }
59 MSG_TYPES_LONG = {v: k for k, v in six.iteritems(MSG_TYPES)}
60
61 MSG_TYPES_STATUS = {
62 'I' : 0,
63 'C' : 16,
64 'R' : 8,
65 'W' : 4,
66 'E' : 2,
67 'F' : 1
68 }
69
70 _MSG_ORDER = 'EWRCIF'
71 MSG_STATE_SCOPE_CONFIG = 0
72 MSG_STATE_SCOPE_MODULE = 1
73 MSG_STATE_CONFIDENCE = 2
74
75 OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)')
76
77 # The line/node distinction does not apply to fatal errors and reports.
78 _SCOPE_EXEMPT = 'FR'
79
80 class WarningScope(object):
81 LINE = 'line-based-msg'
82 NODE = 'node-based-msg'
83
84 _MsgBase = collections.namedtuple(
85 '_MsgBase',
86 ['msg_id', 'symbol', 'msg', 'C', 'category', 'confidence',
87 'abspath', 'path', 'module', 'obj', 'line', 'column'])
88
89
90 class Message(_MsgBase):
91 """This class represent a message to be issued by the reporters"""
92 def __new__(cls, msg_id, symbol, location, msg, confidence):
93 return _MsgBase.__new__(
94 cls, msg_id, symbol, msg, msg_id[0], MSG_TYPES[msg_id[0]],
95 confidence, *location)
96
97 def format(self, template):
98 """Format the message according to the given template.
99
100 The template format is the one of the format method :
101 cf. http://docs.python.org/2/library/string.html#formatstrings
102 """
103 # For some reason, _asdict on derived namedtuples does not work with
104 # Python 3.4. Needs some investigation.
105 return template.format(**dict(zip(self._fields, self)))
106
107
108 def get_module_and_frameid(node):
109 """return the module name and the frame id in the module"""
110 frame = node.frame()
111 module, obj = '', []
112 while frame:
113 if isinstance(frame, Module):
114 module = frame.name
115 else:
116 obj.append(getattr(frame, 'name', '<lambda>'))
117 try:
118 frame = frame.parent.frame()
119 except AttributeError:
120 frame = None
121 obj.reverse()
122 return module, '.'.join(obj)
123
124 def category_id(cid):
125 cid = cid.upper()
126 if cid in MSG_TYPES:
127 return cid
128 return MSG_TYPES_LONG.get(cid)
129
130
131 def _decoding_readline(stream, module):
132 return lambda: stream.readline().decode(module.file_encoding,
133 'replace')
134
135
136 def tokenize_module(module):
137 with module.stream() as stream:
138 readline = stream.readline
139 if sys.version_info < (3, 0):
140 if module.file_encoding is not None:
141 readline = _decoding_readline(stream, module)
142 return list(tokenize.generate_tokens(readline))
143 return list(tokenize.tokenize(readline))
144
145 def build_message_def(checker, msgid, msg_tuple):
146 if implements(checker, (IRawChecker, ITokenChecker)):
147 default_scope = WarningScope.LINE
148 else:
149 default_scope = WarningScope.NODE
150 options = {}
151 if len(msg_tuple) > 3:
152 (msg, symbol, descr, options) = msg_tuple
153 elif len(msg_tuple) > 2:
154 (msg, symbol, descr) = msg_tuple[:3]
155 else:
156 # messages should have a symbol, but for backward compatibility
157 # they may not.
158 (msg, descr) = msg_tuple
159 warnings.warn("[pylint 0.26] description of message %s doesn't include "
160 "a symbolic name" % msgid, DeprecationWarning)
161 symbol = None
162 options.setdefault('scope', default_scope)
163 return MessageDefinition(checker, msgid, msg, descr, symbol, **options)
164
165
166 class MessageDefinition(object):
167 def __init__(self, checker, msgid, msg, descr, symbol, scope,
168 minversion=None, maxversion=None, old_names=None):
169 self.checker = checker
170 assert len(msgid) == 5, 'Invalid message id %s' % msgid
171 assert msgid[0] in MSG_TYPES, \
172 'Bad message type %s in %r' % (msgid[0], msgid)
173 self.msgid = msgid
174 self.msg = msg
175 self.descr = descr
176 self.symbol = symbol
177 self.scope = scope
178 self.minversion = minversion
179 self.maxversion = maxversion
180 self.old_names = old_names or []
181
182 def may_be_emitted(self):
183 """return True if message may be emitted using the current interpreter"" "
184 if self.minversion is not None and self.minversion > sys.version_info:
185 return False
186 if self.maxversion is not None and self.maxversion <= sys.version_info:
187 return False
188 return True
189
190 def format_help(self, checkerref=False):
191 """return the help string for the given message id"""
192 desc = self.descr
193 if checkerref:
194 desc += ' This message belongs to the %s checker.' % \
195 self.checker.name
196 title = self.msg
197 if self.symbol:
198 msgid = '%s (%s)' % (self.symbol, self.msgid)
199 else:
200 msgid = self.msgid
201 if self.minversion or self.maxversion:
202 restr = []
203 if self.minversion:
204 restr.append('< %s' % '.'.join([str(n) for n in self.minversion] ))
205 if self.maxversion:
206 restr.append('>= %s' % '.'.join([str(n) for n in self.maxversion ]))
207 restr = ' or '.join(restr)
208 if checkerref:
209 desc += " It can't be emitted when using Python %s." % restr
210 else:
211 desc += " This message can't be emitted when using Python %s." % restr
212 desc = normalize_text(' '.join(desc.split()), indent=' ')
213 if title != '%s':
214 title = title.splitlines()[0]
215 return ':%s: *%s*\n%s' % (msgid, title, desc)
216 return ':%s:\n%s' % (msgid, desc)
217
218
219 class MessagesHandlerMixIn(object):
220 """a mix-in class containing all the messages related methods for the main
221 lint class
222 """
223
224 def __init__(self):
225 self._msgs_state = {}
226 self.msg_status = 0
227
228 def disable(self, msgid, scope='package', line=None, ignore_unknown=False):
229 """don't output message of the given id"""
230 assert scope in ('package', 'module')
231 # handle disable=all by disabling all categories
232 if msgid == 'all':
233 for msgid in MSG_TYPES:
234 self.disable(msgid, scope, line)
235 return
236 # msgid is a category?
237 catid = category_id(msgid)
238 if catid is not None:
239 for _msgid in self.msgs_store._msgs_by_category.get(catid):
240 self.disable(_msgid, scope, line)
241 return
242 # msgid is a checker name?
243 if msgid.lower() in self._checkers:
244 msgs_store = self.msgs_store
245 for checker in self._checkers[msgid.lower()]:
246 for _msgid in checker.msgs:
247 if _msgid in msgs_store._alternative_names:
248 self.disable(_msgid, scope, line)
249 return
250 # msgid is report id?
251 if msgid.lower().startswith('rp'):
252 self.disable_report(msgid)
253 return
254
255 try:
256 # msgid is a symbolic or numeric msgid.
257 msg = self.msgs_store.check_message_id(msgid)
258 except UnknownMessage:
259 if ignore_unknown:
260 return
261 raise
262
263 if scope == 'module':
264 self.file_state.set_msg_status(msg, line, False)
265 if msg.symbol != 'locally-disabled':
266 self.add_message('locally-disabled', line=line,
267 args=(msg.symbol, msg.msgid))
268
269 else:
270 msgs = self._msgs_state
271 msgs[msg.msgid] = False
272 # sync configuration object
273 self.config.disable = [mid for mid, val in six.iteritems(msgs)
274 if not val]
275
276 def enable(self, msgid, scope='package', line=None, ignore_unknown=False):
277 """reenable message of the given id"""
278 assert scope in ('package', 'module')
279 catid = category_id(msgid)
280 # msgid is a category?
281 if catid is not None:
282 for msgid in self.msgs_store._msgs_by_category.get(catid):
283 self.enable(msgid, scope, line)
284 return
285 # msgid is a checker name?
286 if msgid.lower() in self._checkers:
287 for checker in self._checkers[msgid.lower()]:
288 for msgid_ in checker.msgs:
289 self.enable(msgid_, scope, line)
290 return
291 # msgid is report id?
292 if msgid.lower().startswith('rp'):
293 self.enable_report(msgid)
294 return
295
296 try:
297 # msgid is a symbolic or numeric msgid.
298 msg = self.msgs_store.check_message_id(msgid)
299 except UnknownMessage:
300 if ignore_unknown:
301 return
302 raise
303
304 if scope == 'module':
305 self.file_state.set_msg_status(msg, line, True)
306 self.add_message('locally-enabled', line=line, args=(msg.symbol, msg .msgid))
307 else:
308 msgs = self._msgs_state
309 msgs[msg.msgid] = True
310 # sync configuration object
311 self.config.enable = [mid for mid, val in six.iteritems(msgs) if val ]
312
313 def get_message_state_scope(self, msgid, line=None, confidence=UNDEFINED):
314 """Returns the scope at which a message was enabled/disabled."""
315 if self.config.confidence and confidence.name not in self.config.confide nce:
316 return MSG_STATE_CONFIDENCE
317 try:
318 if line in self.file_state._module_msgs_state[msgid]:
319 return MSG_STATE_SCOPE_MODULE
320 except (KeyError, TypeError):
321 return MSG_STATE_SCOPE_CONFIG
322
323 def is_message_enabled(self, msg_descr, line=None, confidence=None):
324 """return true if the message associated to the given message id is
325 enabled
326
327 msgid may be either a numeric or symbolic message id.
328 """
329 if self.config.confidence and confidence:
330 if confidence.name not in self.config.confidence:
331 return False
332 try:
333 msgid = self.msgs_store.check_message_id(msg_descr).msgid
334 except UnknownMessage:
335 # The linter checks for messages that are not registered
336 # due to version mismatch, just treat them as message IDs
337 # for now.
338 msgid = msg_descr
339 if line is None:
340 return self._msgs_state.get(msgid, True)
341 try:
342 return self.file_state._module_msgs_state[msgid][line]
343 except KeyError:
344 return self._msgs_state.get(msgid, True)
345
346 def add_message(self, msg_descr, line=None, node=None, args=None, confidence =UNDEFINED):
347 """Adds a message given by ID or name.
348
349 If provided, the message string is expanded using args
350
351 AST checkers should must the node argument (but may optionally
352 provide line if the line number is different), raw and token checkers
353 must provide the line argument.
354 """
355 msg_info = self.msgs_store.check_message_id(msg_descr)
356 msgid = msg_info.msgid
357 # backward compatibility, message may not have a symbol
358 symbol = msg_info.symbol or msgid
359 # Fatal messages and reports are special, the node/scope distinction
360 # does not apply to them.
361 if msgid[0] not in _SCOPE_EXEMPT:
362 if msg_info.scope == WarningScope.LINE:
363 assert node is None and line is not None, (
364 'Message %s must only provide line, got line=%s, node=%s' % (msgid, line, node))
365 elif msg_info.scope == WarningScope.NODE:
366 # Node-based warnings may provide an override line.
367 assert node is not None, 'Message %s must provide Node, got None '
368
369 if line is None and node is not None:
370 line = node.fromlineno
371 if hasattr(node, 'col_offset'):
372 col_offset = node.col_offset # XXX measured in bytes for utf-8, divi de by two for chars?
373 else:
374 col_offset = None
375 # should this message be displayed
376 if not self.is_message_enabled(msgid, line, confidence):
377 self.file_state.handle_ignored_message(
378 self.get_message_state_scope(msgid, line, confidence),
379 msgid, line, node, args, confidence)
380 return
381 # update stats
382 msg_cat = MSG_TYPES[msgid[0]]
383 self.msg_status |= MSG_TYPES_STATUS[msgid[0]]
384 self.stats[msg_cat] += 1
385 self.stats['by_module'][self.current_name][msg_cat] += 1
386 try:
387 self.stats['by_msg'][symbol] += 1
388 except KeyError:
389 self.stats['by_msg'][symbol] = 1
390 # expand message ?
391 msg = msg_info.msg
392 if args:
393 msg %= args
394 # get module and object
395 if node is None:
396 module, obj = self.current_name, ''
397 abspath = self.current_file
398 else:
399 module, obj = get_module_and_frameid(node)
400 abspath = node.root().file
401 path = abspath.replace(self.reporter.path_strip_prefix, '')
402 # add the message
403 self.reporter.handle_message(
404 Message(msgid, symbol,
405 (abspath, path, module, obj, line or 1, col_offset or 0), ms g, confidence))
406
407 def print_full_documentation(self):
408 """output a full documentation in ReST format"""
409 print("Pylint global options and switches")
410 print("----------------------------------")
411 print("")
412 print("Pylint provides global options and switches.")
413 print("")
414
415 by_checker = {}
416 for checker in self.get_checkers():
417 if checker.name == 'master':
418 if checker.options:
419 for section, options in checker.options_by_section():
420 if section is None:
421 title = 'General options'
422 else:
423 title = '%s options' % section.capitalize()
424 print(title)
425 print('~' * len(title))
426 rest_format_section(sys.stdout, None, options)
427 print("")
428 else:
429 try:
430 by_checker[checker.name][0] += checker.options_and_values()
431 by_checker[checker.name][1].update(checker.msgs)
432 by_checker[checker.name][2] += checker.reports
433 except KeyError:
434 by_checker[checker.name] = [list(checker.options_and_values( )),
435 dict(checker.msgs),
436 list(checker.reports)]
437
438 print("Pylint checkers' options and switches")
439 print("-------------------------------------")
440 print("")
441 print("Pylint checkers can provide three set of features:")
442 print("")
443 print("* options that control their execution,")
444 print("* messages that they can raise,")
445 print("* reports that they can generate.")
446 print("")
447 print("Below is a list of all checkers and their features.")
448 print("")
449
450 for checker, (options, msgs, reports) in six.iteritems(by_checker):
451 title = '%s checker' % (checker.replace("_", " ").title())
452 print(title)
453 print('~' * len(title))
454 print("")
455 print("Verbatim name of the checker is ``%s``." % checker)
456 print("")
457 if options:
458 title = 'Options'
459 print(title)
460 print('^' * len(title))
461 rest_format_section(sys.stdout, None, options)
462 print("")
463 if msgs:
464 title = 'Messages'
465 print(title)
466 print('~' * len(title))
467 for msgid, msg in sorted(six.iteritems(msgs),
468 key=lambda kv: (_MSG_ORDER.index(kv[0][ 0]), kv[1])):
469 msg = build_message_def(checker, msgid, msg)
470 print(msg.format_help(checkerref=False))
471 print("")
472 if reports:
473 title = 'Reports'
474 print(title)
475 print('~' * len(title))
476 for report in reports:
477 print(':%s: %s' % report[:2])
478 print("")
479 print("")
480
481
482 class FileState(object):
483 """Hold internal state specific to the currently analyzed file"""
484
485 def __init__(self, modname=None):
486 self.base_name = modname
487 self._module_msgs_state = {}
488 self._raw_module_msgs_state = {}
489 self._ignored_msgs = collections.defaultdict(set)
490 self._suppression_mapping = {}
491
492 def collect_block_lines(self, msgs_store, module_node):
493 """Walk the AST to collect block level options line numbers."""
494 for msg, lines in six.iteritems(self._module_msgs_state):
495 self._raw_module_msgs_state[msg] = lines.copy()
496 orig_state = self._module_msgs_state.copy()
497 self._module_msgs_state = {}
498 self._suppression_mapping = {}
499 self._collect_block_lines(msgs_store, module_node, orig_state)
500
501 def _collect_block_lines(self, msgs_store, node, msg_state):
502 """Recursivly walk (depth first) AST to collect block level options line
503 numbers.
504 """
505 for child in node.get_children():
506 self._collect_block_lines(msgs_store, child, msg_state)
507 first = node.fromlineno
508 last = node.tolineno
509 # first child line number used to distinguish between disable
510 # which are the first child of scoped node with those defined later.
511 # For instance in the code below:
512 #
513 # 1. def meth8(self):
514 # 2. """test late disabling"""
515 # 3. # pylint: disable=E1102
516 # 4. print self.blip
517 # 5. # pylint: disable=E1101
518 # 6. print self.bla
519 #
520 # E1102 should be disabled from line 1 to 6 while E1101 from line 5 to 6
521 #
522 # this is necessary to disable locally messages applying to class /
523 # function using their fromlineno
524 if isinstance(node, (nodes.Module, nodes.Class, nodes.Function)) and nod e.body:
525 firstchildlineno = node.body[0].fromlineno
526 else:
527 firstchildlineno = last
528 for msgid, lines in six.iteritems(msg_state):
529 for lineno, state in list(lines.items()):
530 original_lineno = lineno
531 if first <= lineno <= last:
532 # Set state for all lines for this block, if the
533 # warning is applied to nodes.
534 if msgs_store.check_message_id(msgid).scope == WarningScope .NODE:
535 if lineno > firstchildlineno:
536 state = True
537 first_, last_ = node.block_range(lineno)
538 else:
539 first_ = lineno
540 last_ = last
541 for line in range(first_, last_+1):
542 # do not override existing entries
543 if not line in self._module_msgs_state.get(msgid, ()):
544 if line in lines: # state change in the same block
545 state = lines[line]
546 original_lineno = line
547 if not state:
548 self._suppression_mapping[(msgid, line)] = origi nal_lineno
549 try:
550 self._module_msgs_state[msgid][line] = state
551 except KeyError:
552 self._module_msgs_state[msgid] = {line: state}
553 del lines[lineno]
554
555 def set_msg_status(self, msg, line, status):
556 """Set status (enabled/disable) for a given message at a given line"""
557 assert line > 0
558 try:
559 self._module_msgs_state[msg.msgid][line] = status
560 except KeyError:
561 self._module_msgs_state[msg.msgid] = {line: status}
562
563 def handle_ignored_message(self, state_scope, msgid, line,
564 node, args, confidence): # pylint: disable=unused -argument
565 """Report an ignored message.
566
567 state_scope is either MSG_STATE_SCOPE_MODULE or MSG_STATE_SCOPE_CONFIG,
568 depending on whether the message was disabled locally in the module,
569 or globally. The other arguments are the same as for add_message.
570 """
571 if state_scope == MSG_STATE_SCOPE_MODULE:
572 try:
573 orig_line = self._suppression_mapping[(msgid, line)]
574 self._ignored_msgs[(msgid, orig_line)].add(line)
575 except KeyError:
576 pass
577
578 def iter_spurious_suppression_messages(self, msgs_store):
579 for warning, lines in six.iteritems(self._raw_module_msgs_state):
580 for line, enable in six.iteritems(lines):
581 if not enable and (warning, line) not in self._ignored_msgs:
582 yield 'useless-suppression', line, \
583 (msgs_store.get_msg_display_string(warning),)
584 # don't use iteritems here, _ignored_msgs may be modified by add_message
585 for (warning, from_), lines in list(self._ignored_msgs.items()):
586 for line in lines:
587 yield 'suppressed-message', line, \
588 (msgs_store.get_msg_display_string(warning), from_)
589
590
591 class MessagesStore(object):
592 """The messages store knows information about every possible message but has
593 no particular state during analysis.
594 """
595
596 def __init__(self):
597 # Primary registry for all active messages (i.e. all messages
598 # that can be emitted by pylint for the underlying Python
599 # version). It contains the 1:1 mapping from symbolic names
600 # to message definition objects.
601 self._messages = {}
602 # Maps alternative names (numeric IDs, deprecated names) to
603 # message definitions. May contain several names for each definition
604 # object.
605 self._alternative_names = {}
606 self._msgs_by_category = collections.defaultdict(list)
607
608 @property
609 def messages(self):
610 """The list of all active messages."""
611 return six.itervalues(self._messages)
612
613 def add_renamed_message(self, old_id, old_symbol, new_symbol):
614 """Register the old ID and symbol for a warning that was renamed.
615
616 This allows users to keep using the old ID/symbol in suppressions.
617 """
618 msg = self.check_message_id(new_symbol)
619 msg.old_names.append((old_id, old_symbol))
620 self._alternative_names[old_id] = msg
621 self._alternative_names[old_symbol] = msg
622
623 def register_messages(self, checker):
624 """register a dictionary of messages
625
626 Keys are message ids, values are a 2-uple with the message type and the
627 message itself
628
629 message ids should be a string of len 4, where the two first characters
630 are the checker id and the two last the message id in this checker
631 """
632 chkid = None
633 for msgid, msg_tuple in six.iteritems(checker.msgs):
634 msg = build_message_def(checker, msgid, msg_tuple)
635 assert msg.symbol not in self._messages, \
636 'Message symbol %r is already defined' % msg.symbol
637 # avoid duplicate / malformed ids
638 assert msg.msgid not in self._alternative_names, \
639 'Message id %r is already defined' % msgid
640 assert chkid is None or chkid == msg.msgid[1:3], \
641 'Inconsistent checker part in message id %r' % msgid
642 chkid = msg.msgid[1:3]
643 self._messages[msg.symbol] = msg
644 self._alternative_names[msg.msgid] = msg
645 for old_id, old_symbol in msg.old_names:
646 self._alternative_names[old_id] = msg
647 self._alternative_names[old_symbol] = msg
648 self._msgs_by_category[msg.msgid[0]].append(msg.msgid)
649
650 def check_message_id(self, msgid):
651 """returns the Message object for this message.
652
653 msgid may be either a numeric or symbolic id.
654
655 Raises UnknownMessage if the message id is not defined.
656 """
657 if msgid[1:].isdigit():
658 msgid = msgid.upper()
659 for source in (self._alternative_names, self._messages):
660 try:
661 return source[msgid]
662 except KeyError:
663 pass
664 raise UnknownMessage('No such message id %s' % msgid)
665
666 def get_msg_display_string(self, msgid):
667 """Generates a user-consumable representation of a message.
668
669 Can be just the message ID or the ID and the symbol.
670 """
671 return repr(self.check_message_id(msgid).symbol)
672
673 def help_message(self, msgids):
674 """display help messages for the given message identifiers"""
675 for msgid in msgids:
676 try:
677 print(self.check_message_id(msgid).format_help(checkerref=True))
678 print("")
679 except UnknownMessage as ex:
680 print(ex)
681 print("")
682 continue
683
684 def list_messages(self):
685 """output full messages list documentation in ReST format"""
686 msgs = sorted(six.itervalues(self._messages), key=lambda msg: msg.msgid)
687 for msg in msgs:
688 if not msg.may_be_emitted():
689 continue
690 print(msg.format_help(checkerref=False))
691 print("")
692
693
694 class ReportsHandlerMixIn(object):
695 """a mix-in class containing all the reports and stats manipulation
696 related methods for the main lint class
697 """
698 def __init__(self):
699 self._reports = collections.defaultdict(list)
700 self._reports_state = {}
701
702 def report_order(self):
703 """ Return a list of reports, sorted in the order
704 in which they must be called.
705 """
706 return list(self._reports)
707
708 def register_report(self, reportid, r_title, r_cb, checker):
709 """register a report
710
711 reportid is the unique identifier for the report
712 r_title the report's title
713 r_cb the method to call to make the report
714 checker is the checker defining the report
715 """
716 reportid = reportid.upper()
717 self._reports[checker].append((reportid, r_title, r_cb))
718
719 def enable_report(self, reportid):
720 """disable the report of the given id"""
721 reportid = reportid.upper()
722 self._reports_state[reportid] = True
723
724 def disable_report(self, reportid):
725 """disable the report of the given id"""
726 reportid = reportid.upper()
727 self._reports_state[reportid] = False
728
729 def report_is_enabled(self, reportid):
730 """return true if the report associated to the given identifier is
731 enabled
732 """
733 return self._reports_state.get(reportid, True)
734
735 def make_reports(self, stats, old_stats):
736 """render registered reports"""
737 sect = Section('Report',
738 '%s statements analysed.'% (self.stats['statement']))
739 for checker in self.report_order():
740 for reportid, r_title, r_cb in self._reports[checker]:
741 if not self.report_is_enabled(reportid):
742 continue
743 report_sect = Section(r_title)
744 try:
745 r_cb(report_sect, stats, old_stats)
746 except EmptyReport:
747 continue
748 report_sect.report_id = reportid
749 sect.append(report_sect)
750 return sect
751
752 def add_stats(self, **kwargs):
753 """add some stats entries to the statistic dictionary
754 raise an AssertionError if there is a key conflict
755 """
756 for key, value in six.iteritems(kwargs):
757 if key[-1] == '_':
758 key = key[:-1]
759 assert key not in self.stats
760 self.stats[key] = value
761 return self.stats
762
763
764 def expand_modules(files_or_modules, black_list):
765 """take a list of files/modules/packages and return the list of tuple
766 (file, module name) which have to be actually checked
767 """
768 result = []
769 errors = []
770 for something in files_or_modules:
771 if exists(something):
772 # this is a file or a directory
773 try:
774 modname = '.'.join(modpath_from_file(something))
775 except ImportError:
776 modname = splitext(basename(something))[0]
777 if isdir(something):
778 filepath = join(something, '__init__.py')
779 else:
780 filepath = something
781 else:
782 # suppose it's a module or package
783 modname = something
784 try:
785 filepath = file_from_modpath(modname.split('.'))
786 if filepath is None:
787 errors.append({'key' : 'ignored-builtin-module', 'mod': modn ame})
788 continue
789 except (ImportError, SyntaxError) as ex:
790 # FIXME p3k : the SyntaxError is a Python bug and should be
791 # removed as soon as possible http://bugs.python.org/issue10588
792 errors.append({'key': 'fatal', 'mod': modname, 'ex': ex})
793 continue
794 filepath = normpath(filepath)
795 result.append({'path': filepath, 'name': modname, 'isarg': True,
796 'basepath': filepath, 'basename': modname})
797 if not (modname.endswith('.__init__') or modname == '__init__') \
798 and '__init__.py' in filepath:
799 for subfilepath in get_module_files(dirname(filepath), black_list):
800 if filepath == subfilepath:
801 continue
802 submodname = '.'.join(modpath_from_file(subfilepath))
803 result.append({'path': subfilepath, 'name': submodname,
804 'isarg': False,
805 'basepath': filepath, 'basename': modname})
806 return result, errors
807
808
809 class PyLintASTWalker(object):
810
811 def __init__(self, linter):
812 # callbacks per node types
813 self.nbstatements = 1
814 self.visit_events = collections.defaultdict(list)
815 self.leave_events = collections.defaultdict(list)
816 self.linter = linter
817
818 def _is_method_enabled(self, method):
819 if not hasattr(method, 'checks_msgs'):
820 return True
821 for msg_desc in method.checks_msgs:
822 if self.linter.is_message_enabled(msg_desc):
823 return True
824 return False
825
826 def add_checker(self, checker):
827 """walk to the checker's dir and collect visit and leave methods"""
828 # XXX : should be possible to merge needed_checkers and add_checker
829 vcids = set()
830 lcids = set()
831 visits = self.visit_events
832 leaves = self.leave_events
833 for member in dir(checker):
834 cid = member[6:]
835 if cid == 'default':
836 continue
837 if member.startswith('visit_'):
838 v_meth = getattr(checker, member)
839 # don't use visit_methods with no activated message:
840 if self._is_method_enabled(v_meth):
841 visits[cid].append(v_meth)
842 vcids.add(cid)
843 elif member.startswith('leave_'):
844 l_meth = getattr(checker, member)
845 # don't use leave_methods with no activated message:
846 if self._is_method_enabled(l_meth):
847 leaves[cid].append(l_meth)
848 lcids.add(cid)
849 visit_default = getattr(checker, 'visit_default', None)
850 if visit_default:
851 for cls in nodes.ALL_NODE_CLASSES:
852 cid = cls.__name__.lower()
853 if cid not in vcids:
854 visits[cid].append(visit_default)
855 # for now we have no "leave_default" method in Pylint
856
857 def walk(self, astroid):
858 """call visit events of astroid checkers for the given node, recurse on
859 its children, then leave events.
860 """
861 cid = astroid.__class__.__name__.lower()
862 if astroid.is_statement:
863 self.nbstatements += 1
864 # generate events for this node on each checker
865 for cb in self.visit_events.get(cid, ()):
866 cb(astroid)
867 # recurse on children
868 for child in astroid.get_children():
869 self.walk(child)
870 for cb in self.leave_events.get(cid, ()):
871 cb(astroid)
872
873
874 PY_EXTS = ('.py', '.pyc', '.pyo', '.pyw', '.so', '.dll')
875
876 def register_plugins(linter, directory):
877 """load all module and package in the given directory, looking for a
878 'register' function in each one, used to register pylint checkers
879 """
880 imported = {}
881 for filename in os.listdir(directory):
882 base, extension = splitext(filename)
883 if base in imported or base == '__pycache__':
884 continue
885 if extension in PY_EXTS and base != '__init__' or (
886 not extension and isdir(join(directory, base))):
887 try:
888 module = load_module_from_file(join(directory, filename))
889 except ValueError:
890 # empty module name (usually emacs auto-save files)
891 continue
892 except ImportError as exc:
893 print("Problem importing module %s: %s" % (filename, exc),
894 file=sys.stderr)
895 else:
896 if hasattr(module, 'register'):
897 module.register(linter)
898 imported[base] = 1
899
900 def get_global_option(checker, option, default=None):
901 """ Retrieve an option defined by the given *checker* or
902 by all known option providers.
903
904 It will look in the list of all options providers
905 until the given *option* will be found.
906 If the option wasn't found, the *default* value will be returned.
907 """
908 # First, try in the given checker's config.
909 # After that, look in the options providers.
910
911 try:
912 return getattr(checker.config, option.replace("-", "_"))
913 except AttributeError:
914 pass
915 for provider in checker.linter.options_providers:
916 for options in provider.options:
917 if options[0] == option:
918 return getattr(provider.config, option.replace("-", "_"))
919 return default
OLDNEW
« no previous file with comments | « third_party/pylint/pylint/testutils.py ('k') | tools/checklicenses/checklicenses.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698