mirror of
https://fuchsia.googlesource.com/third_party/github.com/pylint-dev/pylint
synced 2024-09-21 16:19:21 +00:00
53f596746d
* new W0212 message for access to protected member from client code * new W0105 message extracted from W0104 (statement seems to have no effect) with string
353 lines
13 KiB
Python
353 lines
13 KiB
Python
# Copyright (c) 2003-2005 Sylvain Thenault (thenault@gmail.com).
|
|
# Copyright (c) 2003-2005 LOGILAB S.A. (Paris, FRANCE).
|
|
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it under
|
|
# the terms of the GNU General Public License as published by the Free Software
|
|
# Foundation; either version 2 of the License, or (at your option) any later
|
|
# version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program; if not, write to the Free Software Foundation, Inc.,
|
|
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
"""some various utilities and helper classes, most of them used in the
|
|
main pylint class
|
|
"""
|
|
|
|
__revision__ = "$Id: utils.py,v 1.13 2006-04-19 09:17:40 syt Exp $"
|
|
|
|
from os import linesep
|
|
|
|
from logilab.astng import Module
|
|
from logilab.common.textutils import normalize_text
|
|
from logilab.common.ureports import Section
|
|
|
|
from pylint.checkers import EmptyReport
|
|
|
|
class UnknownMessage(Exception):
|
|
"""raised when a unregistered message id is encountered"""
|
|
|
|
|
|
MSG_TYPES = {
|
|
'I' : 'info',
|
|
'C' : 'convention',
|
|
'R' : 'refactor',
|
|
'W' : 'warning',
|
|
'E' : 'error',
|
|
'F' : 'fatal'
|
|
}
|
|
MSG_CATEGORIES = MSG_TYPES.keys()
|
|
|
|
|
|
def sort_checkers(checkers, enabled_only=True):
|
|
"""return a list of enabled checker sorted by priority"""
|
|
if enabled_only:
|
|
checkers = [(-checker.priority, checker) for checker in checkers
|
|
if checker.is_enabled()]
|
|
else:
|
|
checkers = [(-checker.priority, checker) for checker in checkers]
|
|
checkers.sort()
|
|
return [item[1] for item in checkers]
|
|
|
|
def sort_msgs(msg_ids):
|
|
"""sort message identifiers according to their category first"""
|
|
msg_order = ['I', 'C', 'R', 'W', 'E', 'F']
|
|
def cmp_func(msgid1, msgid2):
|
|
"""comparison function for two message identifiers"""
|
|
if msgid1[0] != msgid2[0]:
|
|
return cmp(msg_order.index(msgid1[0]), msg_order.index(msgid2[0]))
|
|
else:
|
|
return cmp(msgid1, msgid2)
|
|
msg_ids.sort(cmp_func)
|
|
return msg_ids
|
|
|
|
def get_module_and_frameid(node):
|
|
"""return the module name and the frame id in the module"""
|
|
frame = node.frame()
|
|
module, obj = '', []
|
|
while frame:
|
|
if isinstance(frame, Module):
|
|
module = frame.name
|
|
else:
|
|
obj.append(getattr(frame, 'name', '<lambda>'))
|
|
try:
|
|
frame = frame.parent.frame()
|
|
except AttributeError:
|
|
frame = None
|
|
obj.reverse()
|
|
return module, '.'.join(obj)
|
|
|
|
|
|
class MessagesHandlerMixIn:
|
|
"""a mix-in class containing all the messages related methods for the main
|
|
lint class
|
|
"""
|
|
|
|
def __init__(self):
|
|
# dictionary of registered messages
|
|
self._messages = {}
|
|
self._messages_help = {}
|
|
self._msgs_state = {}
|
|
self._module_msgs_state = None
|
|
self._msg_cats_state = {}
|
|
self._module_msg_cats_state = None
|
|
|
|
def register_messages(self, checker):
|
|
"""register a dictionary of messages
|
|
|
|
Keys are message ids, values are a 2-uple with the message type and the
|
|
message itself
|
|
|
|
message ids should be a string of len 4, where the to first characters
|
|
are the checker id and the two last the message id in this checker
|
|
"""
|
|
msgs_dict = checker.msgs
|
|
chk_id = None
|
|
for msg_id, (msg, msg_help) in msgs_dict.items():
|
|
# avoid duplicate / malformed ids
|
|
assert not self._messages.has_key(msg_id), \
|
|
'Message id %r is already defined' % msg_id
|
|
assert len(msg_id) == 5, 'Invalid message id %s' % msg_id
|
|
assert chk_id is None or chk_id == msg_id[1:3], \
|
|
'Inconsistent checker part in message id %r' %msg_id
|
|
assert msg_id[0] in MSG_CATEGORIES, \
|
|
'Bad message type %s in %r' % (msg_id[0], msg_id)
|
|
chk_id = msg_id[1:3]
|
|
if checker is not None:
|
|
add = ' This message belongs to the %s checker.' % checker.name
|
|
msg_help += add
|
|
self._messages_help[msg_id] = msg_help
|
|
self._messages[msg_id] = msg
|
|
|
|
def get_message_help(self, msg_id):
|
|
"""return the help string for the given message id"""
|
|
msg_id = self.check_message_id(msg_id)
|
|
msg = self._messages_help[msg_id]
|
|
msg = normalize_text(' '.join(msg.split()), indent=' ')
|
|
return '%s:\n%s' % (msg_id, msg)
|
|
|
|
def disable_message(self, msg_id, scope='package', line=None):
|
|
"""don't output message of the given id"""
|
|
assert scope in ('package', 'module')
|
|
msg_id = self.check_message_id(msg_id)
|
|
if scope == 'module':
|
|
assert line > 0
|
|
try:
|
|
self._module_msgs_state[msg_id][line] = False
|
|
except KeyError:
|
|
self._module_msgs_state[msg_id] = {line: False}
|
|
if msg_id != 'I0011':
|
|
self.add_message('I0011', line=line, args=msg_id)
|
|
|
|
else:
|
|
msgs = self._msgs_state
|
|
msgs[msg_id] = False
|
|
# sync configuration object
|
|
self.config.disable_msg = [mid for mid, val in msgs.items()
|
|
if not val]
|
|
|
|
def enable_message(self, msg_id, scope='package', line=None):
|
|
"""reenable message of the given id"""
|
|
assert scope in ('package', 'module')
|
|
msg_id = self.check_message_id(msg_id)
|
|
if scope == 'module':
|
|
assert line > 0
|
|
try:
|
|
self._module_msgs_state[msg_id][line] = True
|
|
except KeyError:
|
|
self._module_msgs_state[msg_id] = {line: True}
|
|
self.add_message('I0012', line=line, args=msg_id)
|
|
else:
|
|
msgs = self._msgs_state
|
|
msgs[msg_id] = True
|
|
# sync configuration object
|
|
self.config.enable_msg = [mid for mid, val in msgs.items() if val]
|
|
|
|
def disable_message_category(self, msg_cat_id, scope='package', line=None):
|
|
"""don't output message in the given category"""
|
|
assert scope in ('package', 'module')
|
|
msg_cat_id = msg_cat_id[0].upper()
|
|
if scope == 'module':
|
|
self.add_message('I0011', line=line, args=msg_cat_id)
|
|
self._module_msg_cats_state[msg_cat_id] = False
|
|
else:
|
|
self._msg_cats_state[msg_cat_id] = False
|
|
|
|
def enable_message_category(self, msg_cat_id, scope='package', line=None):
|
|
"""reenable message of the given category"""
|
|
assert scope in ('package', 'module')
|
|
msg_cat_id = msg_cat_id[0].upper()
|
|
if scope == 'module':
|
|
self.add_message('I0012', line=line, args=msg_cat_id)
|
|
self._module_msg_cats_state[msg_cat_id] = True
|
|
else:
|
|
self._msg_cats_state[msg_cat_id] = True
|
|
|
|
def check_message_id(self, msg_id):
|
|
"""raise UnknownMessage if the message id is not defined"""
|
|
msg_id = msg_id.upper()
|
|
if not self._messages.has_key(msg_id):
|
|
raise UnknownMessage('No such message id %s' % msg_id)
|
|
return msg_id
|
|
|
|
def is_message_enabled(self, msg_id, line=None):
|
|
"""return true if the message associated to the given message id is
|
|
enabled
|
|
"""
|
|
try:
|
|
if not self._module_msg_cats_state[msg_id[0]]:
|
|
return False
|
|
except (KeyError, TypeError):
|
|
if not self._msg_cats_state.get(msg_id[0], True):
|
|
return False
|
|
if line is None:
|
|
return self._msgs_state.get(msg_id, True)
|
|
try:
|
|
return self._module_msgs_state[msg_id][line]
|
|
except (KeyError, TypeError):
|
|
return self._msgs_state.get(msg_id, True)
|
|
|
|
def add_message(self, msg_id, line=None, node=None, args=None):
|
|
"""add the message corresponding to the given id.
|
|
|
|
If provided, msg is expanded using args
|
|
|
|
astng checkers should provide the node argument, raw checkers should
|
|
provide the line argument.
|
|
"""
|
|
if line is None and node is not None:
|
|
line = node.lineno or node.statement().lineno
|
|
#if not isinstance(node, Module):
|
|
# assert line > 0, node.__class__
|
|
# should this message be displayed
|
|
if not self.is_message_enabled(msg_id, line):
|
|
return
|
|
# update stats
|
|
msg_cat = MSG_TYPES[msg_id[0]]
|
|
self.stats[msg_cat] += 1
|
|
self.stats['by_module'][self.current_name][msg_cat] += 1
|
|
try:
|
|
self.stats['by_msg'][msg_id] += 1
|
|
except KeyError:
|
|
self.stats['by_msg'][msg_id] = 1
|
|
msg = self._messages[msg_id]
|
|
# expand message ?
|
|
if args:
|
|
msg %= args
|
|
# get module and object
|
|
if node is None:
|
|
module, obj = self.current_name, ''
|
|
path = self.current_file
|
|
else:
|
|
module, obj = get_module_and_frameid(node)
|
|
path = node.root().file
|
|
# add the message
|
|
self.reporter.add_message(msg_id, (path, module, obj, line or 0), msg)
|
|
|
|
def help_message(self, msgids):
|
|
"""display help messages for the given message identifiers"""
|
|
for msg_id in msgids:
|
|
try:
|
|
print self.get_message_help(msg_id)
|
|
print
|
|
except UnknownMessage, ex:
|
|
print ex
|
|
print
|
|
continue
|
|
|
|
def list_messages(self):
|
|
"""list available messages"""
|
|
for checker in sort_checkers(self._checkers.keys()):
|
|
print checker.name.capitalize()
|
|
print '-' * len(checker.name)
|
|
print
|
|
if checker.__doc__: # __doc__ is None with -OO
|
|
print 'Description'
|
|
print '~~~~~~~~~~~'
|
|
print linesep.join([line.strip()
|
|
for line in checker.__doc__.splitlines()])
|
|
print
|
|
if not checker.msgs:
|
|
continue
|
|
print 'Messages'
|
|
print '~~~~~~~~'
|
|
for msg_id in sort_msgs(checker.msgs.keys()):
|
|
print self.get_message_help(msg_id)
|
|
print
|
|
print
|
|
print
|
|
|
|
|
|
class ReportsHandlerMixIn:
|
|
"""a mix-in class containing all the reports and stats manipulation
|
|
related methods for the main lint class
|
|
"""
|
|
def __init__(self):
|
|
self._reports = {}
|
|
self._reports_state = {}
|
|
|
|
def register_report(self, r_id, r_title, r_cb, checker):
|
|
"""register a report
|
|
|
|
r_id is the unique identifier for the report
|
|
r_title the report's title
|
|
r_cb the method to call to make the report
|
|
checker is the checker defining the report
|
|
"""
|
|
r_id = r_id.upper()
|
|
self._reports.setdefault(checker, []).append( (r_id, r_title, r_cb) )
|
|
|
|
def enable_report(self, r_id):
|
|
"""disable the report of the given id"""
|
|
r_id = r_id.upper()
|
|
self._reports_state[r_id] = True
|
|
|
|
def disable_report(self, r_id):
|
|
"""disable the report of the given id"""
|
|
r_id = r_id.upper()
|
|
self._reports_state[r_id] = False
|
|
|
|
def is_report_enabled(self, r_id):
|
|
"""return true if the report associated to the given identifier is
|
|
enabled
|
|
"""
|
|
return self._reports_state.get(r_id, True)
|
|
|
|
def make_reports(self, stats, old_stats):
|
|
"""render registered reports"""
|
|
if self.config.files_output:
|
|
filename = 'pylint_global.' + self.reporter.extension
|
|
self.reporter.set_output(open(filename, 'w'))
|
|
sect = Section('Report',
|
|
'%s statements analysed.'% (self.stats['statement']))
|
|
checkers = sort_checkers(self._reports.keys())
|
|
checkers.reverse()
|
|
for checker in checkers:
|
|
for r_id, r_title, r_cb in self._reports[checker]:
|
|
if not self.is_report_enabled(r_id):
|
|
continue
|
|
report_sect = Section(r_title)
|
|
try:
|
|
r_cb(report_sect, stats, old_stats)
|
|
except EmptyReport:
|
|
continue
|
|
report_sect.report_id = r_id
|
|
sect.append(report_sect)
|
|
self.reporter.display_results(sect)
|
|
|
|
def add_stats(self, **kwargs):
|
|
"""add some stats entries to the statistic dictionary
|
|
raise an AssertionError if there is a key conflict
|
|
"""
|
|
for key, value in kwargs.items():
|
|
if key[-1] == '_':
|
|
key = key[:-1]
|
|
assert not self.stats.has_key(key)
|
|
self.stats[key] = value
|
|
return self.stats
|
|
|