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

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

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

Powered by Google App Engine
This is Rietveld 408576698