OLD | NEW |
1 # Copyright (c) 2003-2014 LOGILAB S.A. (Paris, FRANCE). | 1 # Copyright (c) 2003-2010 Sylvain Thenault (thenault@gmail.com). |
| 2 # Copyright (c) 2003-2010 LOGILAB S.A. (Paris, FRANCE). |
2 # http://www.logilab.fr/ -- mailto:contact@logilab.fr | 3 # http://www.logilab.fr/ -- mailto:contact@logilab.fr |
3 # | 4 # |
4 # This program is free software; you can redistribute it and/or modify it under | 5 # This program is free software; you can redistribute it and/or modify it under |
5 # the terms of the GNU General Public License as published by the Free Software | 6 # the terms of the GNU General Public License as published by the Free Software |
6 # Foundation; either version 2 of the License, or (at your option) any later | 7 # Foundation; either version 2 of the License, or (at your option) any later |
7 # version. | 8 # version. |
8 # | 9 # |
9 # This program is distributed in the hope that it will be useful, but WITHOUT | 10 # This program is distributed in the hope that it will be useful, but WITHOUT |
10 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
11 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details | 12 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details |
12 # | 13 # |
13 # You should have received a copy of the GNU General Public License along with | 14 # You should have received a copy of the GNU General Public License along with |
14 # this program; if not, write to the Free Software Foundation, Inc., | 15 # this program; if not, write to the Free Software Foundation, Inc., |
15 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | 16 # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. |
16 """some various utilities and helper classes, most of them used in the | 17 """some various utilities and helper classes, most of them used in the |
17 main pylint class | 18 main pylint class |
18 """ | 19 """ |
19 | 20 |
20 import re | |
21 import sys | 21 import sys |
22 import tokenize | 22 from os import linesep |
23 import os | |
24 from warnings import warn | |
25 from os.path import dirname, basename, splitext, exists, isdir, join, normpath | 23 from os.path import dirname, basename, splitext, exists, isdir, join, normpath |
26 | 24 |
27 from logilab.common.interface import implements | 25 from logilab.common.modutils import modpath_from_file, get_module_files, \ |
| 26 file_from_modpath |
28 from logilab.common.textutils import normalize_text | 27 from logilab.common.textutils import normalize_text |
29 from logilab.common.configuration import rest_format_section | 28 from logilab.common.configuration import rest_format_section |
30 from logilab.common.ureports import Section | 29 from logilab.common.ureports import Section |
31 | 30 |
32 from astroid import nodes, Module | 31 from logilab.astng import nodes, Module |
33 from astroid.modutils import modpath_from_file, get_module_files, \ | |
34 file_from_modpath, load_module_from_file | |
35 | 32 |
36 from pylint.interfaces import IRawChecker, ITokenChecker | 33 from pylint.checkers import EmptyReport |
37 | 34 |
38 | 35 |
39 class UnknownMessage(Exception): | 36 class UnknownMessage(Exception): |
40 """raised when a unregistered message id is encountered""" | 37 """raised when a unregistered message id is encountered""" |
41 | 38 |
42 class EmptyReport(Exception): | |
43 """raised when a report is empty and so should not be displayed""" | |
44 | |
45 | 39 |
46 MSG_TYPES = { | 40 MSG_TYPES = { |
47 'I' : 'info', | 41 'I' : 'info', |
48 'C' : 'convention', | 42 'C' : 'convention', |
49 'R' : 'refactor', | 43 'R' : 'refactor', |
50 'W' : 'warning', | 44 'W' : 'warning', |
51 'E' : 'error', | 45 'E' : 'error', |
52 'F' : 'fatal' | 46 'F' : 'fatal' |
53 } | 47 } |
54 MSG_TYPES_LONG = dict([(v, k) for k, v in MSG_TYPES.iteritems()]) | 48 MSG_TYPES_LONG = dict([(v, k) for k, v in MSG_TYPES.iteritems()]) |
55 | 49 |
56 MSG_TYPES_STATUS = { | 50 MSG_TYPES_STATUS = { |
57 'I' : 0, | 51 'I' : 0, |
58 'C' : 16, | 52 'C' : 16, |
59 'R' : 8, | 53 'R' : 8, |
60 'W' : 4, | 54 'W' : 4, |
61 'E' : 2, | 55 'E' : 2, |
62 'F' : 1 | 56 'F' : 1 |
63 } | 57 } |
64 | 58 |
65 _MSG_ORDER = 'EWRCIF' | 59 _MSG_ORDER = 'EWRCIF' |
66 MSG_STATE_SCOPE_CONFIG = 0 | |
67 MSG_STATE_SCOPE_MODULE = 1 | |
68 | 60 |
69 OPTION_RGX = re.compile(r'\s*#.*\bpylint:(.*)') | 61 def sort_msgs(msgids): |
70 | 62 """sort message identifiers according to their category first""" |
71 # The line/node distinction does not apply to fatal errors and reports. | 63 msgs = {} |
72 _SCOPE_EXEMPT = 'FR' | 64 for msg in msgids: |
73 | 65 msgs.setdefault(msg[0], []).append(msg) |
74 class WarningScope(object): | 66 result = [] |
75 LINE = 'line-based-msg' | 67 for m_id in _MSG_ORDER: |
76 NODE = 'node-based-msg' | 68 if m_id in msgs: |
77 | 69 result.extend( sorted(msgs[m_id]) ) |
| 70 return result |
78 | 71 |
79 def get_module_and_frameid(node): | 72 def get_module_and_frameid(node): |
80 """return the module name and the frame id in the module""" | 73 """return the module name and the frame id in the module""" |
81 frame = node.frame() | 74 frame = node.frame() |
82 module, obj = '', [] | 75 module, obj = '', [] |
83 while frame: | 76 while frame: |
84 if isinstance(frame, Module): | 77 if isinstance(frame, Module): |
85 module = frame.name | 78 module = frame.name |
86 else: | 79 else: |
87 obj.append(getattr(frame, 'name', '<lambda>')) | 80 obj.append(getattr(frame, 'name', '<lambda>')) |
88 try: | 81 try: |
89 frame = frame.parent.frame() | 82 frame = frame.parent.frame() |
90 except AttributeError: | 83 except AttributeError: |
91 frame = None | 84 frame = None |
92 obj.reverse() | 85 obj.reverse() |
93 return module, '.'.join(obj) | 86 return module, '.'.join(obj) |
94 | 87 |
95 def category_id(id): | 88 def category_id(id): |
96 id = id.upper() | 89 id = id.upper() |
97 if id in MSG_TYPES: | 90 if id in MSG_TYPES: |
98 return id | 91 return id |
99 return MSG_TYPES_LONG.get(id) | 92 return MSG_TYPES_LONG.get(id) |
100 | 93 |
101 | 94 |
102 def tokenize_module(module): | 95 class Message: |
103 stream = module.file_stream | 96 def __init__(self, checker, msgid, msg, descr): |
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 | |
138 assert len(msgid) == 5, 'Invalid message id %s' % msgid | 97 assert len(msgid) == 5, 'Invalid message id %s' % msgid |
139 assert msgid[0] in MSG_TYPES, \ | 98 assert msgid[0] in MSG_TYPES, \ |
140 'Bad message type %s in %r' % (msgid[0], msgid) | 99 'Bad message type %s in %r' % (msgid[0], msgid) |
141 self.msgid = msgid | 100 self.msgid = msgid |
142 self.msg = msg | 101 self.msg = msg |
143 self.descr = descr | 102 self.descr = descr |
144 self.symbol = symbol | 103 self.checker = checker |
145 self.scope = scope | |
146 self.minversion = minversion | |
147 self.maxversion = maxversion | |
148 self.old_names = old_names or [] | |
149 | 104 |
150 def may_be_emitted(self): | 105 class MessagesHandlerMixIn: |
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): | |
188 """a mix-in class containing all the messages related methods for the main | 106 """a mix-in class containing all the messages related methods for the main |
189 lint class | 107 lint class |
190 """ | 108 """ |
191 | 109 |
192 def __init__(self): | 110 def __init__(self): |
| 111 # dictionary of registered messages |
| 112 self._messages = {} |
193 self._msgs_state = {} | 113 self._msgs_state = {} |
| 114 self._module_msgs_state = {} # None |
| 115 self._msgs_by_category = {} |
194 self.msg_status = 0 | 116 self.msg_status = 0 |
195 | 117 |
196 def disable(self, msgid, scope='package', line=None, ignore_unknown=False): | 118 def register_messages(self, checker): |
| 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): |
197 """don't output message of the given id""" | 153 """don't output message of the given id""" |
198 assert scope in ('package', 'module') | 154 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 | |
204 # msgid is a category? | 155 # msgid is a category? |
205 catid = category_id(msgid) | 156 catid = category_id(msgid) |
206 if catid is not None: | 157 if catid is not None: |
207 for _msgid in self.msgs_store._msgs_by_category.get(catid): | 158 for msgid in self._msgs_by_category.get(catid): |
208 self.disable(_msgid, scope, line) | 159 self.disable(msgid, scope, line) |
209 return | 160 return |
210 # msgid is a checker name? | 161 # msgid is a checker name? |
211 if msgid.lower() in self._checkers: | 162 if msgid.lower() in self._checkers: |
212 msgs_store = self.msgs_store | |
213 for checker in self._checkers[msgid.lower()]: | 163 for checker in self._checkers[msgid.lower()]: |
214 for _msgid in checker.msgs: | 164 for msgid in checker.msgs: |
215 if _msgid in msgs_store._alternative_names: | 165 self.disable(msgid, scope, line) |
216 self.disable(_msgid, scope, line) | |
217 return | 166 return |
218 # msgid is report id? | 167 # msgid is report id? |
219 if msgid.lower().startswith('rp'): | 168 if msgid.lower().startswith('rp'): |
220 self.disable_report(msgid) | 169 self.disable_report(msgid) |
221 return | 170 return |
222 | 171 # msgid is a msgid. |
223 try: | 172 msg = self.check_message_id(msgid) |
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 | |
231 if scope == 'module': | 173 if scope == 'module': |
232 self.file_state.set_msg_status(msg, line, False) | 174 assert line > 0 |
233 if msg.symbol != 'locally-disabled': | 175 try: |
234 self.add_message('locally-disabled', line=line, | 176 self._module_msgs_state[msg.msgid][line] = False |
235 args=(msg.symbol, msg.msgid)) | 177 except KeyError: |
| 178 self._module_msgs_state[msg.msgid] = {line: False} |
| 179 if msgid != 'I0011': |
| 180 self.add_message('I0011', line=line, args=msg.msgid) |
236 | 181 |
237 else: | 182 else: |
238 msgs = self._msgs_state | 183 msgs = self._msgs_state |
239 msgs[msg.msgid] = False | 184 msgs[msg.msgid] = False |
240 # sync configuration object | 185 # sync configuration object |
241 self.config.disable_msg = [mid for mid, val in msgs.iteritems() | 186 self.config.disable_msg = [mid for mid, val in msgs.items() |
242 if not val] | 187 if not val] |
243 | 188 |
244 def enable(self, msgid, scope='package', line=None, ignore_unknown=False): | 189 def enable(self, msgid, scope='package', line=None): |
245 """reenable message of the given id""" | 190 """reenable message of the given id""" |
246 assert scope in ('package', 'module') | 191 assert scope in ('package', 'module') |
247 catid = category_id(msgid) | 192 catid = category_id(msgid) |
248 # msgid is a category? | 193 # msgid is a category? |
249 if catid is not None: | 194 if catid is not None: |
250 for msgid in self.msgs_store._msgs_by_category.get(catid): | 195 for msgid in self._msgs_by_category.get(catid): |
251 self.enable(msgid, scope, line) | 196 self.enable(msgid, scope, line) |
252 return | 197 return |
253 # msgid is a checker name? | 198 # msgid is a checker name? |
254 if msgid.lower() in self._checkers: | 199 if msgid.lower() in self._checkers: |
255 for checker in self._checkers[msgid.lower()]: | 200 for checker in self._checkers[msgid.lower()]: |
256 for msgid_ in checker.msgs: | 201 for msgid in checker.msgs: |
257 self.enable(msgid_, scope, line) | 202 self.enable(msgid, scope, line) |
258 return | 203 return |
259 # msgid is report id? | 204 # msgid is report id? |
260 if msgid.lower().startswith('rp'): | 205 if msgid.lower().startswith('rp'): |
261 self.enable_report(msgid) | 206 self.enable_report(msgid) |
262 return | 207 return |
263 | 208 # msgid is a msgid. |
264 try: | 209 msg = self.check_message_id(msgid) |
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 | |
272 if scope == 'module': | 210 if scope == 'module': |
273 self.file_state.set_msg_status(msg, line, True) | 211 assert line > 0 |
274 self.add_message('locally-enabled', line=line, args=(msg.symbol, msg
.msgid)) | 212 try: |
| 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) |
275 else: | 217 else: |
276 msgs = self._msgs_state | 218 msgs = self._msgs_state |
277 msgs[msg.msgid] = True | 219 msgs[msg.msgid] = True |
278 # sync configuration object | 220 # sync configuration object |
279 self.config.enable = [mid for mid, val in msgs.iteritems() if val] | 221 self.config.enable = [mid for mid, val in msgs.items() if val] |
280 | 222 |
281 def is_message_enabled(self, msg_descr, line=None): | 223 def check_message_id(self, msgid): |
| 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): |
282 """return true if the message associated to the given message id is | 232 """return true if the message associated to the given message id is |
283 enabled | 233 enabled |
284 | |
285 msgid may be either a numeric or symbolic message id. | |
286 """ | 234 """ |
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 | |
294 if line is None: | 235 if line is None: |
295 return self._msgs_state.get(msgid, True) | 236 return self._msgs_state.get(msgid, True) |
296 try: | 237 try: |
297 return self.file_state._module_msgs_state[msgid][line] | 238 return self._module_msgs_state[msgid][line] |
298 except KeyError: | 239 except (KeyError, TypeError): |
299 return self._msgs_state.get(msgid, True) | 240 return self._msgs_state.get(msgid, True) |
300 | 241 |
301 def add_message(self, msg_descr, line=None, node=None, args=None): | 242 def add_message(self, msgid, line=None, node=None, args=None): |
302 """Adds a message given by ID or name. | 243 """add the message corresponding to the given id. |
303 | 244 |
304 If provided, the message string is expanded using args | 245 If provided, msg is expanded using args |
305 | 246 |
306 AST checkers should must the node argument (but may optionally | 247 astng checkers should provide the node argument, raw checkers should |
307 provide line if the line number is different), raw and token checkers | 248 provide the line argument. |
308 must provide the line argument. | |
309 """ | 249 """ |
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 | |
324 if line is None and node is not None: | 250 if line is None and node is not None: |
325 line = node.fromlineno | 251 line = node.fromlineno |
326 if hasattr(node, 'col_offset'): | 252 if hasattr(node, 'col_offset'): |
327 col_offset = node.col_offset # XXX measured in bytes for utf-8, divi
de by two for chars? | 253 col_offset = node.col_offset # XXX measured in bytes for utf-8, divi
de by two for chars? |
328 else: | 254 else: |
329 col_offset = None | 255 col_offset = None |
330 # should this message be displayed | 256 # should this message be displayed |
331 if not self.is_message_enabled(msgid, line): | 257 if not self.is_message_enabled(msgid, line): |
332 self.file_state.handle_ignored_message(msgid, line, node, args) | |
333 return | 258 return |
334 # update stats | 259 # update stats |
335 msg_cat = MSG_TYPES[msgid[0]] | 260 msg_cat = MSG_TYPES[msgid[0]] |
336 self.msg_status |= MSG_TYPES_STATUS[msgid[0]] | 261 self.msg_status |= MSG_TYPES_STATUS[msgid[0]] |
337 self.stats[msg_cat] += 1 | 262 self.stats[msg_cat] += 1 |
338 self.stats['by_module'][self.current_name][msg_cat] += 1 | 263 self.stats['by_module'][self.current_name][msg_cat] += 1 |
339 try: | 264 try: |
340 self.stats['by_msg'][symbol] += 1 | 265 self.stats['by_msg'][msgid] += 1 |
341 except KeyError: | 266 except KeyError: |
342 self.stats['by_msg'][symbol] = 1 | 267 self.stats['by_msg'][msgid] = 1 |
| 268 msg = self._messages[msgid].msg |
343 # expand message ? | 269 # expand message ? |
344 msg = msg_info.msg | |
345 if args: | 270 if args: |
346 msg %= args | 271 msg %= args |
347 # get module and object | 272 # get module and object |
348 if node is None: | 273 if node is None: |
349 module, obj = self.current_name, '' | 274 module, obj = self.current_name, '' |
350 path = self.current_file | 275 path = self.current_file |
351 else: | 276 else: |
352 module, obj = get_module_and_frameid(node) | 277 module, obj = get_module_and_frameid(node) |
353 path = node.root().file | 278 path = node.root().file |
354 # add the message | 279 # add the message |
355 self.reporter.add_message(msgid, (path, module, obj, line or 1, col_offs
et or 0), msg) | 280 self.reporter.add_message(msgid, (path, module, obj, line or 1, col_offs
et or 0), msg) |
356 | 281 |
| 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 |
357 def print_full_documentation(self): | 293 def print_full_documentation(self): |
358 """output a full documentation in ReST format""" | 294 """output a full documentation in ReST format""" |
359 by_checker = {} | 295 by_checker = {} |
360 for checker in self.get_checkers(): | 296 for checker in self.get_checkers(): |
361 if checker.name == 'master': | 297 if checker.name == 'master': |
362 prefix = 'Main ' | 298 prefix = 'Main ' |
363 print "Options" | 299 print "Options" |
364 print '-------\n' | 300 print '-------\n' |
365 if checker.options: | 301 if checker.options: |
366 for section, options in checker.options_by_section(): | 302 for section, options in checker.options_by_section(): |
367 if section is None: | 303 if section is None: |
368 title = 'General options' | 304 title = 'General options' |
369 else: | 305 else: |
370 title = '%s options' % section.capitalize() | 306 title = '%s options' % section.capitalize() |
371 print title | 307 print title |
372 print '~' * len(title) | 308 print '~' * len(title) |
373 rest_format_section(sys.stdout, None, options) | 309 rest_format_section(sys.stdout, None, options) |
374 print | 310 print |
375 else: | 311 else: |
376 try: | 312 try: |
377 by_checker[checker.name][0] += checker.options_and_values() | 313 by_checker[checker.name][0] += checker.options_and_values() |
378 by_checker[checker.name][1].update(checker.msgs) | 314 by_checker[checker.name][1].update(checker.msgs) |
379 by_checker[checker.name][2] += checker.reports | 315 by_checker[checker.name][2] += checker.reports |
380 except KeyError: | 316 except KeyError: |
381 by_checker[checker.name] = [list(checker.options_and_values(
)), | 317 by_checker[checker.name] = [list(checker.options_and_values(
)), |
382 dict(checker.msgs), | 318 dict(checker.msgs), |
383 list(checker.reports)] | 319 list(checker.reports)] |
384 for checker, (options, msgs, reports) in by_checker.iteritems(): | 320 for checker, (options, msgs, reports) in by_checker.items(): |
385 prefix = '' | 321 prefix = '' |
386 title = '%s checker' % checker | 322 title = '%s checker' % checker |
387 print title | 323 print title |
388 print '-' * len(title) | 324 print '-' * len(title) |
389 print | 325 print |
390 if options: | 326 if options: |
391 title = 'Options' | 327 title = 'Options' |
392 print title | 328 print title |
393 print '~' * len(title) | 329 print '~' * len(title) |
394 rest_format_section(sys.stdout, None, options) | 330 rest_format_section(sys.stdout, None, options) |
395 print | 331 print |
396 if msgs: | 332 if msgs: |
397 title = ('%smessages' % prefix).capitalize() | 333 title = ('%smessages' % prefix).capitalize() |
398 print title | 334 print title |
399 print '~' * len(title) | 335 print '~' * len(title) |
400 for msgid, msg in sorted(msgs.iteritems(), | 336 for msgid in sort_msgs(msgs.keys()): |
401 key=lambda (k, v): (_MSG_ORDER.index(k[
0]), k)): | 337 print self.get_message_help(msgid, False) |
402 msg = build_message_def(checker, msgid, msg) | |
403 print msg.format_help(checkerref=False) | |
404 print | 338 print |
405 if reports: | 339 if reports: |
406 title = ('%sreports' % prefix).capitalize() | 340 title = ('%sreports' % prefix).capitalize() |
407 print title | 341 print title |
408 print '~' * len(title) | 342 print '~' * len(title) |
409 for report in reports: | 343 for report in reports: |
410 print ':%s: %s' % report[:2] | 344 print ':%s: %s' % report[:2] |
411 print | 345 print |
412 print | 346 print |
413 | 347 |
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 | |
625 def list_messages(self): | 348 def list_messages(self): |
626 """output full messages list documentation in ReST format""" | 349 """output full messages list documentation in ReST format""" |
627 msgs = sorted(self._messages.itervalues(), key=lambda msg: msg.msgid) | 350 msgids = [] |
628 for msg in msgs: | 351 for checker in self.get_checkers(): |
629 if not msg.may_be_emitted(): | 352 for msgid in checker.msgs.keys(): |
630 continue | 353 msgids.append(msgid) |
631 print msg.format_help(checkerref=False) | 354 msgids.sort() |
| 355 for msgid in msgids: |
| 356 print self.get_message_help(msgid, False) |
632 print | 357 print |
633 | 358 |
634 | 359 |
635 class ReportsHandlerMixIn(object): | 360 class ReportsHandlerMixIn: |
636 """a mix-in class containing all the reports and stats manipulation | 361 """a mix-in class containing all the reports and stats manipulation |
637 related methods for the main lint class | 362 related methods for the main lint class |
638 """ | 363 """ |
639 def __init__(self): | 364 def __init__(self): |
640 self._reports = {} | 365 self._reports = {} |
641 self._reports_state = {} | 366 self._reports_state = {} |
642 | 367 |
643 def register_report(self, reportid, r_title, r_cb, checker): | 368 def register_report(self, reportid, r_title, r_cb, checker): |
644 """register a report | 369 """register a report |
645 | 370 |
646 reportid is the unique identifier for the report | 371 reportid is the unique identifier for the report |
647 r_title the report's title | 372 r_title the report's title |
648 r_cb the method to call to make the report | 373 r_cb the method to call to make the report |
649 checker is the checker defining the report | 374 checker is the checker defining the report |
650 """ | 375 """ |
651 reportid = reportid.upper() | 376 reportid = reportid.upper() |
652 self._reports.setdefault(checker, []).append((reportid, r_title, r_cb)) | 377 self._reports.setdefault(checker, []).append( (reportid, r_title, r_cb)
) |
653 | 378 |
654 def enable_report(self, reportid): | 379 def enable_report(self, reportid): |
655 """disable the report of the given id""" | 380 """disable the report of the given id""" |
656 reportid = reportid.upper() | 381 reportid = reportid.upper() |
657 self._reports_state[reportid] = True | 382 self._reports_state[reportid] = True |
658 | 383 |
659 def disable_report(self, reportid): | 384 def disable_report(self, reportid): |
660 """disable the report of the given id""" | 385 """disable the report of the given id""" |
661 reportid = reportid.upper() | 386 reportid = reportid.upper() |
662 self._reports_state[reportid] = False | 387 self._reports_state[reportid] = False |
663 | 388 |
664 def report_is_enabled(self, reportid): | 389 def report_is_enabled(self, reportid): |
665 """return true if the report associated to the given identifier is | 390 """return true if the report associated to the given identifier is |
666 enabled | 391 enabled |
667 """ | 392 """ |
668 return self._reports_state.get(reportid, True) | 393 return self._reports_state.get(reportid, True) |
669 | 394 |
670 def make_reports(self, stats, old_stats): | 395 def make_reports(self, stats, old_stats): |
671 """render registered reports""" | 396 """render registered reports""" |
| 397 if self.config.files_output: |
| 398 filename = 'pylint_global.' + self.reporter.extension |
| 399 self.reporter.set_output(open(filename, 'w')) |
672 sect = Section('Report', | 400 sect = Section('Report', |
673 '%s statements analysed.'% (self.stats['statement'])) | 401 '%s statements analysed.'% (self.stats['statement'])) |
674 for checker in self._reports: | 402 for checker in self._reports: |
675 for reportid, r_title, r_cb in self._reports[checker]: | 403 for reportid, r_title, r_cb in self._reports[checker]: |
676 if not self.report_is_enabled(reportid): | 404 if not self.report_is_enabled(reportid): |
677 continue | 405 continue |
678 report_sect = Section(r_title) | 406 report_sect = Section(r_title) |
679 try: | 407 try: |
680 r_cb(report_sect, stats, old_stats) | 408 r_cb(report_sect, stats, old_stats) |
681 except EmptyReport: | 409 except EmptyReport: |
682 continue | 410 continue |
683 report_sect.report_id = reportid | 411 report_sect.report_id = reportid |
684 sect.append(report_sect) | 412 sect.append(report_sect) |
685 return sect | 413 self.reporter.display_results(sect) |
686 | 414 |
687 def add_stats(self, **kwargs): | 415 def add_stats(self, **kwargs): |
688 """add some stats entries to the statistic dictionary | 416 """add some stats entries to the statistic dictionary |
689 raise an AssertionError if there is a key conflict | 417 raise an AssertionError if there is a key conflict |
690 """ | 418 """ |
691 for key, value in kwargs.iteritems(): | 419 for key, value in kwargs.items(): |
692 if key[-1] == '_': | 420 if key[-1] == '_': |
693 key = key[:-1] | 421 key = key[:-1] |
694 assert key not in self.stats | 422 assert key not in self.stats |
695 self.stats[key] = value | 423 self.stats[key] = value |
696 return self.stats | 424 return self.stats |
697 | 425 |
698 | 426 |
699 def expand_modules(files_or_modules, black_list): | 427 def expand_modules(files_or_modules, black_list): |
700 """take a list of files/modules/packages and return the list of tuple | 428 """take a list of files/modules/packages and return the list of tuple |
701 (file, module name) which have to be actually checked | 429 (file, module name) which have to be actually checked |
(...skipping 10 matching lines...) Expand all Loading... |
712 if isdir(something): | 440 if isdir(something): |
713 filepath = join(something, '__init__.py') | 441 filepath = join(something, '__init__.py') |
714 else: | 442 else: |
715 filepath = something | 443 filepath = something |
716 else: | 444 else: |
717 # suppose it's a module or package | 445 # suppose it's a module or package |
718 modname = something | 446 modname = something |
719 try: | 447 try: |
720 filepath = file_from_modpath(modname.split('.')) | 448 filepath = file_from_modpath(modname.split('.')) |
721 if filepath is None: | 449 if filepath is None: |
722 errors.append({'key' : 'ignored-builtin-module', 'mod': modn
ame}) | 450 errors.append( {'key' : 'F0003', 'mod': modname} ) |
723 continue | 451 continue |
724 except (ImportError, SyntaxError), ex: | 452 except (ImportError, SyntaxError), ex: |
725 # FIXME p3k : the SyntaxError is a Python bug and should be | 453 # FIXME p3k : the SyntaxError is a Python bug and should be |
726 # removed as soon as possible http://bugs.python.org/issue10588 | 454 # removed as soon as possible http://bugs.python.org/issue10588 |
727 errors.append({'key': 'fatal', 'mod': modname, 'ex': ex}) | 455 errors.append( {'key': 'F0001', 'mod': modname, 'ex': ex} ) |
728 continue | 456 continue |
729 filepath = normpath(filepath) | 457 filepath = normpath(filepath) |
730 result.append({'path': filepath, 'name': modname, 'isarg': True, | 458 result.append( {'path': filepath, 'name': modname, |
731 'basepath': filepath, 'basename': modname}) | 459 'basepath': filepath, 'basename': modname} ) |
732 if not (modname.endswith('.__init__') or modname == '__init__') \ | 460 if not (modname.endswith('.__init__') or modname == '__init__') \ |
733 and '__init__.py' in filepath: | 461 and '__init__.py' in filepath: |
734 for subfilepath in get_module_files(dirname(filepath), black_list): | 462 for subfilepath in get_module_files(dirname(filepath), black_list): |
735 if filepath == subfilepath: | 463 if filepath == subfilepath: |
736 continue | 464 continue |
737 submodname = '.'.join(modpath_from_file(subfilepath)) | 465 submodname = '.'.join(modpath_from_file(subfilepath)) |
738 result.append({'path': subfilepath, 'name': submodname, | 466 result.append( {'path': subfilepath, 'name': submodname, |
739 'isarg': False, | 467 'basepath': filepath, 'basename': modname} ) |
740 'basepath': filepath, 'basename': modname}) | |
741 return result, errors | 468 return result, errors |
742 | 469 |
743 | 470 |
744 class PyLintASTWalker(object): | 471 class PyLintASTWalker(object): |
745 | 472 |
746 def __init__(self, linter): | 473 def __init__(self, linter): |
747 # callbacks per node types | 474 # callbacks per node types |
748 self.nbstatements = 1 | 475 self.nbstatements = 1 |
749 self.visit_events = {} | 476 self.visit_events = {} |
750 self.leave_events = {} | 477 self.leave_events = {} |
751 self.linter = linter | 478 self.linter = linter |
752 | 479 |
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 | |
761 def add_checker(self, checker): | 480 def add_checker(self, checker): |
762 """walk to the checker's dir and collect visit and leave methods""" | 481 """walk to the checker's dir and collect visit and leave methods""" |
763 # XXX : should be possible to merge needed_checkers and add_checker | 482 # XXX : should be possible to merge needed_checkers and add_checker |
764 vcids = set() | 483 vcids = set() |
765 lcids = set() | 484 lcids = set() |
766 visits = self.visit_events | 485 visits = self.visit_events |
767 leaves = self.leave_events | 486 leaves = self.leave_events |
| 487 msgs = self.linter._msgs_state |
768 for member in dir(checker): | 488 for member in dir(checker): |
769 cid = member[6:] | 489 cid = member[6:] |
770 if cid == 'default': | 490 if cid == 'default': |
771 continue | 491 continue |
772 if member.startswith('visit_'): | 492 if member.startswith('visit_'): |
773 v_meth = getattr(checker, member) | 493 v_meth = getattr(checker, member) |
774 # don't use visit_methods with no activated message: | 494 # don't use visit_methods with no activated message: |
775 if self._is_method_enabled(v_meth): | 495 if hasattr(v_meth, 'checks_msgs'): |
776 visits.setdefault(cid, []).append(v_meth) | 496 if not any(msgs.get(m, True) for m in v_meth.checks_msgs): |
777 vcids.add(cid) | 497 continue |
| 498 visits.setdefault(cid, []).append(v_meth) |
| 499 vcids.add(cid) |
778 elif member.startswith('leave_'): | 500 elif member.startswith('leave_'): |
779 l_meth = getattr(checker, member) | 501 l_meth = getattr(checker, member) |
780 # don't use leave_methods with no activated message: | 502 # don't use leave_methods with no activated message: |
781 if self._is_method_enabled(l_meth): | 503 if hasattr(l_meth, 'checks_msgs'): |
782 leaves.setdefault(cid, []).append(l_meth) | 504 if not any(msgs.get(m, True) for m in l_meth.checks_msgs): |
783 lcids.add(cid) | 505 continue |
| 506 leaves.setdefault(cid, []).append(l_meth) |
| 507 lcids.add(cid) |
784 visit_default = getattr(checker, 'visit_default', None) | 508 visit_default = getattr(checker, 'visit_default', None) |
785 if visit_default: | 509 if visit_default: |
786 for cls in nodes.ALL_NODE_CLASSES: | 510 for cls in nodes.ALL_NODE_CLASSES: |
787 cid = cls.__name__.lower() | 511 cid = cls.__name__.lower() |
788 if cid not in vcids: | 512 if cid not in vcids: |
789 visits.setdefault(cid, []).append(visit_default) | 513 visits.setdefault(cid, []).append(visit_default) |
790 # for now we have no "leave_default" method in Pylint | 514 # for now we have no "leave_default" method in Pylint |
791 | 515 |
792 def walk(self, astroid): | 516 def walk(self, astng): |
793 """call visit events of astroid checkers for the given node, recurse on | 517 """call visit events of astng checkers for the given node, recurse on |
794 its children, then leave events. | 518 its children, then leave events. |
795 """ | 519 """ |
796 cid = astroid.__class__.__name__.lower() | 520 cid = astng.__class__.__name__.lower() |
797 if astroid.is_statement: | 521 if astng.is_statement: |
798 self.nbstatements += 1 | 522 self.nbstatements += 1 |
799 # generate events for this node on each checker | 523 # generate events for this node on each checker |
800 for cb in self.visit_events.get(cid, ()): | 524 for cb in self.visit_events.get(cid, ()): |
801 cb(astroid) | 525 cb(astng) |
802 # recurse on children | 526 # recurse on children |
803 for child in astroid.get_children(): | 527 for child in astng.get_children(): |
804 self.walk(child) | 528 self.walk(child) |
805 for cb in self.leave_events.get(cid, ()): | 529 for cb in self.leave_events.get(cid, ()): |
806 cb(astroid) | 530 cb(astng) |
807 | 531 |
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 |