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

Side by Side Diff: third_party/pylint/utils.py

Issue 719313003: Revert "pylint: upgrade to 1.3.1" (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « third_party/pylint/testutils.py ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
OLDNEW
« no previous file with comments | « third_party/pylint/testutils.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698