summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ChangeLog5
-rw-r--r--doc/whatsnew/2.0.rst7
-rw-r--r--pylint/exceptions.py8
-rw-r--r--pylint/test/unittest_lint.py37
-rw-r--r--pylint/test/unittest_utils.py143
-rw-r--r--pylint/utils.py59
6 files changed, 240 insertions, 19 deletions
diff --git a/ChangeLog b/ChangeLog
index 1dc4848a4..e2d166fc3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -131,6 +131,11 @@ Release date: tba
* Added proper exception type inference for 'missing-raises-doc'.
+ * Added InvalidMessageError exception class to replace asserts in
+ pylint.utils.
+
+ * More thorough validation in MessagesStore.register_messages() to avoid
+ one message accidentally overwriting another.
What's new in Pylint 1.6.3?
===========================
diff --git a/doc/whatsnew/2.0.rst b/doc/whatsnew/2.0.rst
index 11aa517a0..000b5843b 100644
--- a/doc/whatsnew/2.0.rst
+++ b/doc/whatsnew/2.0.rst
@@ -142,6 +142,9 @@ Other Changes
applies to variable names, only with an inverse rule: you want long
descriptive names for variables with bigger scope, like globals.
+* Add ``InvalidMessageError`` exception class and replace ``assert`` in
+ pylint.utils with ``raise InvalidMessageError``.
+
Bug fixes
=========
@@ -168,6 +171,10 @@ Bug fixes
* Fix false positives of ``missing-[raises|params|type]-doc`` due to not
recognizing valid keyword synonyms supported by Sphinx.
+* More thorough validation in ``MessagesStore.register_messages()`` to detect
+ conflicts between a new message and any existing message id, symbol,
+ or ``old_names``.
+
Removed Changes
===============
diff --git a/pylint/exceptions.py b/pylint/exceptions.py
new file mode 100644
index 000000000..20c569be6
--- /dev/null
+++ b/pylint/exceptions.py
@@ -0,0 +1,8 @@
+# Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+# For details: https://github.com/PyCQA/pylint/blob/master/COPYING
+
+"""Exception classes raised by various operations within pylint."""
+
+
+class InvalidMessageError(Exception):
+ """raised when a message creation, registration or addition is rejected"""
diff --git a/pylint/test/unittest_lint.py b/pylint/test/unittest_lint.py
index 9c6c6cf93..0b253ddb8 100644
--- a/pylint/test/unittest_lint.py
+++ b/pylint/test/unittest_lint.py
@@ -22,6 +22,7 @@ from pylint.lint import PyLinter, Run, preprocess_options, \
from pylint.utils import MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, MSG_STATE_CONFIDENCE, \
MessagesStore, PyLintASTWalker, MessageDefinition, FileState, \
build_message_def, tokenize_module, UnknownMessage
+from pylint.exceptions import InvalidMessageError
from pylint.testutils import TestReporter
from pylint.reporters import text
from pylint import checkers
@@ -444,6 +445,26 @@ class PyLinterTC(unittest.TestCase):
['C: 1: Line too long (1/2)', 'C: 2: Line too long (3/4)'],
self.linter.reporter.messages)
+ def test_addmessage_invalid(self):
+ self.linter.set_reporter(TestReporter())
+ self.linter.open()
+ self.linter.set_current_module('0123')
+
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.linter.add_message('line-too-long', args=(1,2))
+ self.assertEqual(str(cm.exception),
+ "Message C0301 must provide line, got None")
+
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.linter.add_message('line-too-long', line=2, node='fake_node', args=(1, 2))
+ self.assertEqual(str(cm.exception),
+ "Message C0301 must only provide line, got line=2, node=fake_node")
+
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.linter.add_message('C0321')
+ self.assertEqual(str(cm.exception),
+ "Message C0321 must provide Node, got None")
+
def test_init_hooks_called_before_load_plugins(self):
self.assertRaises(RuntimeError,
Run, ['--load-plugins', 'unexistant', '--init-hook', 'raise RuntimeError'])
@@ -661,12 +682,26 @@ class MessagesStoreTC(unittest.TestCase):
self.assertEqual('msg-symbol',
self.store.check_message_id('old-bad-name').symbol)
+ def test_add_renamed_message_invalid(self):
+ # conflicting message ID
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.add_renamed_message(
+ 'W1234', 'old-msg-symbol', 'duplicate-keyword-arg')
+ self.assertEqual(str(cm.exception),
+ "Message id 'W1234' is already defined")
+ # conflicting message symbol
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.add_renamed_message(
+ 'W1337', 'msg-symbol', 'duplicate-keyword-arg')
+ self.assertEqual(str(cm.exception),
+ "Message symbol 'msg-symbol' is already defined")
+
def test_renamed_message_register(self):
self.assertEqual('msg-symbol',
self.store.check_message_id('W0001').symbol)
self.assertEqual('msg-symbol',
self.store.check_message_id('old-symbol').symbol)
-
+
if __name__ == '__main__':
unittest.main()
diff --git a/pylint/test/unittest_utils.py b/pylint/test/unittest_utils.py
index 7e075f016..7034429bb 100644
--- a/pylint/test/unittest_utils.py
+++ b/pylint/test/unittest_utils.py
@@ -13,6 +13,7 @@ from pylint import __pkginfo__
from pylint import utils
from pylint import interfaces
from pylint.checkers.utils import check_messages
+from pylint.exceptions import InvalidMessageError
class PyLintASTWalkerTest(unittest.TestCase):
@@ -84,5 +85,147 @@ class RegexBlacklistTest(unittest.TestCase):
self.assertFalse(utils._basename_in_blacklist_re("test_utils.py", patterns))
self.assertFalse(utils._basename_in_blacklist_re("enchilad.py", patterns))
+
+class MessagesStoreRegisterMessagesTest(unittest.TestCase):
+ def setUp(self):
+ self.store = utils.MessagesStore()
+
+ def test_register_error_inconsistent_checker_id(self):
+ class Checker(object):
+ name = 'checker'
+ msgs = {
+ 'W1234': ('message one', 'msg-symbol-one', 'msg description'),
+ 'W4321': ('message two', 'msg-symbol-two', 'msg description'),
+ }
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(Checker())
+ self.assertEqual(str(cm.exception),
+ r"Inconsistent checker part in message id 'W4321' (expected 'x12xx')")
+
+ def test_register_error_new_id_duplicate_of_new(self):
+ class CheckerOne(object):
+ name = 'checker_one'
+ msgs = {
+ 'W1234': ('message one', 'msg-symbol-one', 'msg description.'),
+ }
+ class CheckerTwo(object):
+ name = 'checker_two'
+ msgs = {
+ 'W1234': ('message two', 'msg-symbol-two', 'another msg description.'),
+ }
+ self.store.register_messages(CheckerOne())
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(CheckerTwo())
+ self.assertEqual(str(cm.exception),
+ "Message id 'W1234' is already defined")
+
+ def test_register_error_new_id_duplicate_of_old(self):
+ class Checker(object):
+ name = 'checker'
+ msgs = {
+ 'W1233': ('message two', 'msg-symbol-two', 'msg description',
+ {'old_names': [('W1234', 'old-symbol')]}),
+ 'W1234': ('message one', 'msg-symbol-one', 'msg description'),
+ }
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(Checker())
+ self.assertEqual(str(cm.exception),
+ "Message id 'W1234' is already defined")
+
+ def test_register_error_old_id_duplicate_of_new(self):
+ class Checker(object):
+ name = 'checker'
+ msgs = {
+ 'W1234': ('message one', 'msg-symbol-one', 'msg description'),
+ 'W1235': ('message two', 'msg-symbol-two', 'msg description',
+ {'old_names': [('W1234', 'old-symbol')]}),
+ }
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(Checker())
+ self.assertEqual(str(cm.exception),
+ "Message id 'W1234' is already defined")
+
+ def test_register_error_old_id_duplicate_of_old(self):
+ class Checker(object):
+ name = 'checker'
+ msgs = {
+ 'W1234': ('message one', 'msg-symbol-one', 'msg description',
+ {'old_names': [('W1201', 'old-symbol-one')]}),
+ 'W1235': ('message two', 'msg-symbol-two', 'msg description',
+ {'old_names': [('W1201', 'old-symbol-two')]}),
+ }
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(Checker())
+ self.assertEqual(str(cm.exception),
+ "Message id 'W1201' is already defined")
+
+
+ def test_register_error_new_symbol_duplicate_of_new(self):
+ class Checker(object):
+ name = 'checker'
+ msgs = {
+ 'W1234': ('message one', 'msg-symbol', 'msg description'),
+ 'W1235': ('message two', 'msg-symbol', 'msg description'),
+ }
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(Checker())
+ self.assertEqual(str(cm.exception),
+ "Message symbol 'msg-symbol' is already defined")
+
+ def test_register_error_new_symbol_duplicate_of_old(self):
+ class Checker(object):
+ name = 'checker'
+ msgs = {
+ 'W1233': ('message two', 'msg-symbol-two', 'msg description',
+ {'old_names': [('W1230', 'msg-symbol-one')]}),
+ 'W1234': ('message one', 'msg-symbol-one', 'msg description'),
+ }
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(Checker())
+ self.assertEqual(str(cm.exception),
+ "Message symbol 'msg-symbol-one' is already defined")
+
+ def test_register_error_old_symbol_duplicate_of_new(self):
+ class Checker(object):
+ name = 'checker'
+ msgs = {
+ 'W1234': ('message one', 'msg-symbol-one', 'msg description'),
+ 'W1235': ('message two', 'msg-symbol-two', 'msg description',
+ {'old_names': [('W1230', 'msg-symbol-one')]}),
+ }
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(Checker())
+ self.assertEqual(str(cm.exception),
+ "Message symbol 'msg-symbol-one' is already defined")
+
+ def test_register_error_old_symbol_duplicate_of_old(self):
+ class Checker(object):
+ name = 'checker'
+ msgs = {
+ 'W1234': ('message one', 'msg-symbol-one', 'msg description',
+ {'old_names': [('W1230', 'old-symbol-one')]}),
+ 'W1235': ('message two', 'msg-symbol-two', 'msg description',
+ {'old_names': [('W1231', 'old-symbol-one')]}),
+ }
+ with self.assertRaises(InvalidMessageError) as cm:
+ self.store.register_messages(Checker())
+ self.assertEqual(str(cm.exception),
+ "Message alternate name 'old-symbol-one' is already defined")
+
+class MessageDefinitionTest(unittest.TestCase):
+ def test_create_invalid_msgid(self):
+ with self.assertRaises(InvalidMessageError) as cm:
+ utils.MessageDefinition('checker', 'W12345',
+ 'msg', 'descr', 'symbol', 'scope')
+ self.assertEqual(str(cm.exception),
+ "Invalid message id 'W12345'")
+
+ def test_create_invalid_message_type(self):
+ with self.assertRaises(InvalidMessageError) as cm:
+ utils.MessageDefinition('checker', 'Q1234',
+ 'msg', 'descr', 'symbol', 'scope')
+ self.assertEqual(str(cm.exception),
+ "Bad message type Q in 'Q1234'")
+
if __name__ == '__main__':
unittest.main()
diff --git a/pylint/utils.py b/pylint/utils.py
index b51890d5d..14c464af9 100644
--- a/pylint/utils.py
+++ b/pylint/utils.py
@@ -26,6 +26,7 @@ from astroid import modutils
from pylint.interfaces import IRawChecker, ITokenChecker, UNDEFINED, implements
from pylint.reporters.ureports.nodes import Section
+from pylint.exceptions import InvalidMessageError
class UnknownMessage(Exception):
@@ -157,9 +158,11 @@ class MessageDefinition(object):
def __init__(self, checker, msgid, msg, descr, symbol, scope,
minversion=None, maxversion=None, old_names=None):
self.checker = checker
- assert len(msgid) == 5, 'Invalid message id %s' % msgid
- assert msgid[0] in MSG_TYPES, \
- 'Bad message type %s in %r' % (msgid[0], msgid)
+ if len(msgid) != 5:
+ raise InvalidMessageError('Invalid message id %r' % msgid)
+ if not msgid[0] in MSG_TYPES:
+ raise InvalidMessageError(
+ 'Bad message type %s in %r' % (msgid[0], msgid))
self.msgid = msgid
self.msg = msg
self.descr = descr
@@ -375,11 +378,18 @@ class MessagesHandlerMixIn(object):
# does not apply to them.
if msgid[0] not in _SCOPE_EXEMPT:
if msg_info.scope == WarningScope.LINE:
- assert node is None and line is not None, (
- 'Message %s must only provide line, got line=%s, node=%s' % (msgid, line, node))
+ if line is None:
+ raise InvalidMessageError(
+ 'Message %s must provide line, got None' % msgid)
+ if node is not None:
+ raise InvalidMessageError(
+ 'Message %s must only provide line, '
+ 'got line=%s, node=%s' % (msgid, line, node))
elif msg_info.scope == WarningScope.NODE:
# Node-based warnings may provide an override line.
- assert node is not None, 'Message %s must provide Node, got None'
+ if node is None:
+ raise InvalidMessageError(
+ 'Message %s must provide Node, got None' % msgid)
if line is None and node is not None:
line = node.fromlineno
@@ -669,8 +679,8 @@ class MessagesStore(object):
"""
msg = self.check_message_id(new_symbol)
msg.old_names.append((old_id, old_symbol))
- self._alternative_names[old_id] = msg
- self._alternative_names[old_symbol] = msg
+ self._register_alternative_name(msg, old_id)
+ self._register_alternative_name(msg, old_symbol)
def register_messages(self, checker):
"""register a dictionary of messages
@@ -682,23 +692,36 @@ class MessagesStore(object):
are the checker id and the two last the message id in this checker
"""
chkid = None
- for msgid, msg_tuple in six.iteritems(checker.msgs):
+ for msgid, msg_tuple in sorted(six.iteritems(checker.msgs)):
msg = build_message_def(checker, msgid, msg_tuple)
- assert msg.symbol not in self._messages, \
- 'Message symbol %r is already defined' % msg.symbol
# avoid duplicate / malformed ids
- assert msg.msgid not in self._alternative_names, \
- 'Message id %r is already defined' % msgid
- assert chkid is None or chkid == msg.msgid[1:3], \
- 'Inconsistent checker part in message id %r' % msgid
+ if msg.symbol in self._messages or msg.symbol in self._alternative_names:
+ raise InvalidMessageError(
+ 'Message symbol %r is already defined' % msg.symbol)
+ if chkid is not None and chkid != msg.msgid[1:3]:
+ raise InvalidMessageError(
+ "Inconsistent checker part in message id %r (expected 'x%sxx')"
+ % (msgid, chkid))
chkid = msg.msgid[1:3]
self._messages[msg.symbol] = msg
- self._alternative_names[msg.msgid] = msg
+ self._register_alternative_name(msg, msg.msgid)
for old_id, old_symbol in msg.old_names:
- self._alternative_names[old_id] = msg
- self._alternative_names[old_symbol] = msg
+ self._register_alternative_name(msg, old_id)
+ self._register_alternative_name(msg, old_symbol)
self._msgs_by_category[msg.msgid[0]].append(msg.msgid)
+ def _register_alternative_name(self, msg, name):
+ """helper for register_message()"""
+ if name in self._messages and self._messages[name] != msg:
+ raise InvalidMessageError(
+ 'Message symbol %r is already defined' % name)
+ if name in self._alternative_names and self._alternative_names[name] != msg:
+ raise InvalidMessageError(
+ 'Message %s %r is already defined' % (
+ 'id' if len(name) == 5 and name[0] in MSG_TYPES else 'alternate name',
+ name))
+ self._alternative_names[name] = msg
+
def check_message_id(self, msgid):
"""returns the Message object for this message.