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