Fix a crash which occurred when old visit methods are encountered

in plugin modules.

If a plugin uses an old visit method (visit_class for instance), this can lead to a crash in pylint's base checkers, because the logic in the PylintASTWalker assumes that all checkers have a visit_class / leave_class method. The patch fixes this by looking for both names. Closes issue #711.

authorClaudiu Popa <pcmanticore@gmail.com>
changesetb1d55d8897ae
branchdefault
phasepublic
hiddenno
parent revision#39fbbf6cc275 Don't emit unsubscriptable-object if the node is found inside an abstract class. Closes #685.
child revision#d59766d8c147 Add changelog entry for 0c2ba76
files modified by this revision
ChangeLog
pylint/test/unittest_lint.py
pylint/utils.py
# HG changeset patch
# User Claudiu Popa <pcmanticore@gmail.com>
# Date 1448891445 -7200
# Mon Nov 30 15:50:45 2015 +0200
# Node ID b1d55d8897ae7b88756edf019b2552e40960568a
# Parent 39fbbf6cc275c8158aea8560362363ef551171b8
Fix a crash which occurred when old visit methods are encountered
in plugin modules.

If a plugin uses an old visit method (visit_class for instance), this can lead
to a crash in pylint's base checkers, because the logic in the PylintASTWalker
assumes that all checkers have a visit_class / leave_class method. The
patch fixes this by looking for both names.
Closes issue #711.

diff --git a/ChangeLog b/ChangeLog
@@ -8,10 +8,13 @@
1 
2      * Added a new warning, 'unsupported-delete-operation', which is
3        emitted when item deletion is tried on an object which doesn't
4        have this ability. Closes issue #592.
5 
6 +    * Fix a crash which occurred when old visit methods are encountered
7 +      in plugin modules. Closes issue #711.
8 +
9      * Added multiple warnings related to imports. 'wrong-import-order'
10        is emitted when PEP 8 recommendations regarding imports are not
11        respected (that is, standard imports should be followed by third-party
12        imports and then by local imports). 'ungrouped-imports' is emitted
13        when imports from the same package or module are not placed
diff --git a/pylint/test/unittest_lint.py b/pylint/test/unittest_lint.py
@@ -31,10 +31,11 @@
14      MessagesStore, PyLintASTWalker, MessageDefinition, FileState, \
15      build_message_def, tokenize_module, UnknownMessage
16  from pylint.testutils import TestReporter
17  from pylint.reporters import text, html
18  from pylint import checkers
19 +from pylint.checkers.utils import check_messages
20  from pylint import interfaces
21 
22  if os.name == 'java':
23      if os._name == 'nt':
24          HOME = 'USERPROFILE'
@@ -215,10 +216,26 @@
25          linter.open()
26          linter.set_current_module('toto')
27          linter.file_state = FileState('toto')
28          return linter
29 
30 +    def test_pylint_visit_method_taken_in_account(self):
31 +        class CustomChecker(checkers.BaseChecker):
32 +            __implements__ = interfaces.IAstroidChecker
33 +            name = 'custom'
34 +            msgs = {'W9999': ('', 'custom', '')}
35 +
36 +            @check_messages('custom')
37 +            def visit_class(self, _):
38 +               pass
39 +
40 +        self.linter.register_checker(CustomChecker(self.linter))
41 +        self.linter.open()
42 +        out = six.moves.StringIO()
43 +        self.linter.set_reporter(text.TextReporter(out))
44 +        self.linter.check('abc')
45 +
46      def test_enable_message(self):
47          linter = self.init_linter()
48          self.assertTrue(linter.is_message_enabled('W0101'))
49          self.assertTrue(linter.is_message_enabled('W0102'))
50          linter.disable('W0101', scope='package')
@@ -667,8 +684,8 @@
51          self.assertEqual('msg-symbol',
52                           self.store.check_message_id('W0001').symbol)
53          self.assertEqual('msg-symbol',
54                           self.store.check_message_id('old-symbol').symbol)
55 
56 -
57 +   
58  if __name__ == '__main__':
59      unittest.main()
diff --git a/pylint/utils.py b/pylint/utils.py
@@ -17,10 +17,11 @@
60  main pylint class
61  """
62  from __future__ import print_function
63 
64  import collections
65 +import itertools
66  import os
67  from os.path import dirname, basename, splitext, exists, isdir, join, normpath
68  import re
69  import sys
70  import tokenize
@@ -902,33 +903,32 @@
71          cid = astroid.__class__.__name__.lower()
72 
73          # Detect if the node is a new name for a deprecated alias.
74          # In this case, favour the methods for the deprecated
75          # alias if any,  in order to maintain backwards
76 -        # compatibility. If both of them are present,
77 -        # only the old ones will be called.
78 +        # compatibility.
79          old_cid = DEPRECATED_ALIASES.get(cid)
80          visit_events = ()
81          leave_events = ()
82 
83          if old_cid:
84 -            visit_events = self.visit_events.get(old_cid)
85 -            leave_events = self.leave_events.get(old_cid)
86 +            visit_events = self.visit_events.get(old_cid, ())
87 +            leave_events = self.leave_events.get(old_cid, ())
88              if visit_events or leave_events:
89                  msg = ("Implemented method {meth}_{old} instead of {meth}_{new}. "
90                         "This will be supported until Pylint 2.0.")
91                  if visit_events:
92                      warnings.warn(msg.format(meth="visit", old=old_cid, new=cid),
93                                    PendingDeprecationWarning)
94                  if leave_events:
95                      warnings.warn(msg.format(meth="leave", old=old_cid, new=cid),
96                                    PendingDeprecationWarning)
97 
98 -        if not visit_events:
99 -            visit_events = self.visit_events.get(cid)
100 -        if not leave_events:
101 -            leave_events = self.leave_events.get(cid)
102 +        visit_events = itertools.chain(visit_events,
103 +                                       self.visit_events.get(cid, ()))
104 +        leave_events = itertools.chain(leave_events,
105 +                                       self.leave_events.get(cid, ()))
106 
107          if astroid.is_statement:
108              self.nbstatements += 1
109          # generate events for this node on each checker
110          for cb in visit_events or ():