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