summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcpopa <devnull@localhost>2013-09-26 13:12:45 +0300
committercpopa <devnull@localhost>2013-09-26 13:12:45 +0300
commit7d46a3cd8b4563fd0d93b6ea14fe9a3ec90c647d (patch)
tree32ea3b034c86ea363539d4268bea166b9a0166f9
parentaef858eedb5bef81e32600f53504e73818e297ef (diff)
parent023b225765346f2026f714aec31ee57c4b8da5b5 (diff)
downloadpylint-7d46a3cd8b4563fd0d93b6ea14fe9a3ec90c647d.tar.gz
Merge with default.
-rw-r--r--ChangeLog12
-rw-r--r--__main__.py3
-rw-r--r--checkers/classes.py36
-rw-r--r--doc/Makefile4
-rw-r--r--doc/output.rst24
-rwxr-xr-xepylint.py25
-rw-r--r--gui.py30
-rw-r--r--lint.py2
-rw-r--r--reporters/guireporter.py10
-rw-r--r--test/input/func_non_iterator_returned_py30.py52
-rw-r--r--test/input/func_non_iterator_returned_py_30.py52
-rw-r--r--test/input/func_useless_else_on_loop.py4
-rw-r--r--test/messages/func_non_iterator_returned_py30.txt3
-rw-r--r--test/messages/func_non_iterator_returned_py_30.txt3
-rw-r--r--test/unittest_lint.py51
-rw-r--r--test/unittest_reporting.py29
-rw-r--r--utils.py156
17 files changed, 338 insertions, 158 deletions
diff --git a/ChangeLog b/ChangeLog
index 228d9ab..a241958 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,10 +2,20 @@ ChangeLog for Pylint
====================
--
- * Add new warning for unpacking non-sequences in assignments
+ * Run pylint as a python module 'python -m pylint' (anatoly techtonik)
* Check for non-exception classes inside an except clause
+ * epylint support options to give to pylint after the file to analyze and
+ have basic input validation (bitbucket #53 and #54), patches provided by
+ felipeochoa and Brian Lane
+
+ * Added a new warning, 'non-iterator-returned', for non-iterators
+ returned by '__iter__'
+
+ * Add new warning for unpacking non-sequences in assignments
+
+
2013-08-06 -- 1.0.0
* Add check for the use of 'exec' function
diff --git a/__main__.py b/__main__.py
new file mode 100644
index 0000000..7716361
--- /dev/null
+++ b/__main__.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+import pylint
+pylint.run_pylint()
diff --git a/checkers/classes.py b/checkers/classes.py
index 98a2f27..4c8022d 100644
--- a/checkers/classes.py
+++ b/checkers/classes.py
@@ -16,15 +16,21 @@
"""classes checker for Python code
"""
from __future__ import generators
-
+import sys
import astroid
from astroid import YES, Instance, are_exclusive, AssAttr
+from astroid.bases import Generator
from pylint.interfaces import IAstroidChecker
from pylint.checkers import BaseChecker
from pylint.checkers.utils import (PYMETHODS, overrides_a_method,
check_messages, is_attr_private, is_attr_protected, node_frame_class)
+if sys.version_info >= (3, 0):
+ NEXT_METHOD = '__next__'
+else:
+ NEXT_METHOD = 'next'
+
def class_is_abstract(node):
"""return true if the given class node should be considered as an abstract
class
@@ -142,6 +148,11 @@ MSGS = {
'non-parent-init-called',
'Used when an __init__ method is called on a class which is not \
in the direct ancestors for the analysed class.'),
+ 'W0234': ('__iter__ returns non-iterator',
+ 'non-iterator-returned',
+ 'Used when an __iter__ method returns something which is not an \
+ iterable (i.e. has no `%s` method)' % NEXT_METHOD),
+
}
@@ -311,6 +322,29 @@ a metaclass class method.'}
except astroid.NotFoundError:
pass
+ # check non-iterators in __iter__
+ if node.name == '__iter__':
+ self._check_iter(node)
+
+ def _check_iter(self, node):
+ try:
+ infered = node.infer_call_result(node)
+ except astroid.InferenceError:
+ return
+
+ for infered_node in infered:
+ if (infered_node is YES
+ or isinstance(infered_node, Generator)):
+ continue
+ if isinstance(infered_node, astroid.Instance):
+ try:
+ infered_node.local_attr(NEXT_METHOD)
+ except astroid.NotFoundError:
+ self.add_message('non-iterator-returned',
+ node=node)
+ break
+
+
def leave_function(self, node):
"""on method node, check if this method couldn't be a function
diff --git a/doc/Makefile b/doc/Makefile
index 8b8b3ec..98076a6 100644
--- a/doc/Makefile
+++ b/doc/Makefile
@@ -37,7 +37,7 @@ clean:
-rm -rf $(BUILDDIR)/*
-rm -f features.rst
-html: features.rst
+html: features
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
@@ -131,7 +131,7 @@ doctest:
"results in $(BUILDDIR)/doctest/output.txt."
-features.rst:
+features:
rm -f features.rst
echo "Pylint features" > features.rst
echo "===============" >> features.rst
diff --git a/doc/output.rst b/doc/output.rst
index 6617ac4..3a87547 100644
--- a/doc/output.rst
+++ b/doc/output.rst
@@ -24,20 +24,34 @@ obj
object within the module (if any)
msg
text of the message
+msg_id
+ the message code (eg. I0011)
symbol
- symbolic name of the message
+ symbolic name of the message (eg. locally-disabled)
C
one letter indication of the message category
category
fullname of the message category
-For exemple the default format can be obtained with::
+For exemple the former (pre 1.0) default format can be obtained with::
- pylint --msg-template='{sigle}:{line:3d},{column}: {obj}: {msg}'
+ pylint --msg-template='{msg_id}:{line:3d},{column}: {obj}: {msg}'
+
+A few other examples:
+
+* the new default format::
+
+ {C}:{line:3d},{column:2d}: {msg} ({symbol})
+
+* Visual Studio compatible format (former 'msvs' output format)::
+
+ {path}({line}): [{msg_id}{obj}] {msg}
+
+* Parseable (Emacs and all, former 'parseable' output format) format::
+
+ {path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
-and a Visual Studio compatible format can be obtained with::
- pylint --msg-template='{path}({line}): [{sigle}{obj}] {msg}'
.. _Python new format syntax: http://docs.python.org/2/library/string.html#formatstrings
diff --git a/epylint.py b/epylint.py
index fbdfdac..76d7b68 100755
--- a/epylint.py
+++ b/epylint.py
@@ -29,7 +29,8 @@ For example:
a/b/x.py
a/c/y.py
- - Then if y.py imports x as "from a.b import x" the following produces pylint errors
+ - Then if y.py imports x as "from a.b import x" the following produces pylint
+ errors
cd a/c; pylint y.py
@@ -41,14 +42,15 @@ For example:
we are checking we need to go out of it to avoid these false positives.
-You may also use py_run to run pylint with desired options and get back (or not) its output.
+You may also use py_run to run pylint with desired options and get back (or not)
+its output.
"""
import sys, os, re
from subprocess import Popen, PIPE
-def lint(filename):
+def lint(filename, options=None):
"""Pylint the given file.
When run from emacs we will be in the directory of a file, and passed its filename.
@@ -74,8 +76,9 @@ def lint(filename):
# Start pylint
# Ensure we use the python and pylint associated with the running epylint
lintPath = os.path.join(os.path.dirname(__file__), 'lint.py')
- cmd = [sys.executable, lintPath, '--msg-template', '{path}:{line}: [{symbol}, {obj}] {msg}', '-r', 'n',
- '--disable=C,R,I', childPath]
+ options = options or ['--disable=C,R,I']
+ cmd = [sys.executable, lintPath] + options + ['--msg-template',
+ '{path}:{line}: [{symbol}, {obj}] {msg}', '-r', 'n', childPath]
process = Popen(cmd, stdout=PIPE, cwd=parentPath, universal_newlines=True)
# The parseable line format is '%(path)s:%(line)s: [%(sigle)s%(obj)s] %(msg)s'
@@ -108,7 +111,7 @@ def lint(filename):
def py_run(command_options='', return_std=False, stdout=None, stderr=None,
script='epylint'):
- """Run pylint from python (needs Python >= 2.4).
+ """Run pylint from python
``command_options`` is a string containing ``pylint`` command line options;
``return_std`` (boolean) indicates return of created standart output
@@ -158,7 +161,15 @@ def py_run(command_options='', return_std=False, stdout=None, stderr=None,
def Run():
- sys.exit(lint(sys.argv[1]))
+ if len(sys.argv) == 1:
+ print "Usage: %s <filename> [options]" % sys.argv[0]
+ sys.exit(1)
+ elif not os.path.exists(sys.argv[1]):
+ print "%s does not exist" % sys.argv[1]
+ sys.exit(1)
+ else:
+ sys.exit(lint(sys.argv[1]))
+
if __name__ == '__main__':
Run()
diff --git a/gui.py b/gui.py
index 79a4089..0ee5974 100644
--- a/gui.py
+++ b/gui.py
@@ -39,10 +39,10 @@ COLORS = {'(I)':'lightblue',
def convert_to_string(msg):
"""make a string representation of a message"""
- if (msg[4] != ""):
- return "(" + msg[0] + ") " + msg[3] + "." + msg[4] + " [" + msg[5] + "]: " + msg[6]
- else:
- return "(" + msg[0] + ") " + msg[3] + " [" + msg[5] + "]: " + msg[6]
+ module_object = msg.module
+ if msg.obj:
+ module_object += ".%s" % msg.obj
+ return "(%s) %s [%d]: %s" % (msg.C, module_object, msg.line, msg.msg)
class BasicStream(object):
'''
@@ -327,12 +327,7 @@ class LintGui(object):
self.lbMessages.delete(0, END)
self.visible_msgs = []
for msg in self.msgs:
-
- # Obtaining message type (pylint's '--include-ids' appends the
- # ID to this letter, so 1 character long is not guaranteed)
- msg_type = msg[0][0]
-
- if (self.msg_type_dict.get(msg_type)()):
+ if (self.msg_type_dict.get(msg.C)()):
self.visible_msgs.append(msg)
msg_str = convert_to_string(msg)
self.lbMessages.insert(END, msg_str)
@@ -361,12 +356,8 @@ class LintGui(object):
#adding message to list of msgs
self.msgs.append(msg)
- # Obtaining message type (pylint's '--include-ids' appends the
- # ID to this letter, so 1 character long is not guaranteed)
- msg_type = msg[0][0]
-
#displaying msg if message type is selected in check box
- if (self.msg_type_dict.get(msg_type)()):
+ if (self.msg_type_dict.get(msg.C)()):
self.visible_msgs.append(msg)
msg_str = convert_to_string(msg)
self.lbMessages.insert(END, msg_str)
@@ -478,18 +469,15 @@ class LintGui(object):
return
msg = self.visible_msgs[int(selected[0])]
- filename = msg[2]
- fileline = int(msg[5])
-
- scroll = fileline - 3
+ scroll = msg.line - 3
if scroll < 0:
scroll = 0
- self.tabs["Source File"] = open(filename, "r").readlines()
+ self.tabs["Source File"] = open(msg.path, "r").readlines()
self.box.set("Source File")
self.refresh_results_window()
self.results.yview(scroll)
- self.results.select_set(fileline - 1)
+ self.results.select_set(msg.line - 1)
def lint_thread(module, reporter, gui):
diff --git a/lint.py b/lint.py
index a039bae..e5e89e5 100644
--- a/lint.py
+++ b/lint.py
@@ -262,7 +262,7 @@ This is used by the global evaluation report (RP0004).'}),
'group': 'Reports',
'help' : ('Template used to display messages. '
'This is a python new-style format string '
- 'used to format the massage information. '
+ 'used to format the message information. '
'See doc for all details')
}), # msg-template
)
diff --git a/reporters/guireporter.py b/reporters/guireporter.py
index 9f3ae79..331eb17 100644
--- a/reporters/guireporter.py
+++ b/reporters/guireporter.py
@@ -3,7 +3,7 @@
import sys
from pylint.interfaces import IReporter
-from pylint.reporters import BaseReporter
+from pylint.reporters import BaseReporter, Message
from logilab.common.ureports import TextWriter
@@ -16,16 +16,12 @@ class GUIReporter(BaseReporter):
def __init__(self, gui, output=sys.stdout):
"""init"""
BaseReporter.__init__(self, output)
- self.msgs = []
self.gui = gui
def add_message(self, msg_id, location, msg):
"""manage message of different type and in the context of path"""
- filename, module, obj, line, col_offset = location
- msg = Message(self, msg_id, location, msg)
- full_msg = [msg.C, msg_id, filename, module, obj, str(line), msg]
- self.msgs += [[sigle, module, obj, str(line)]]
- self.gui.msg_queue.put(full_msg)
+ message = Message(self, msg_id, location, msg)
+ self.gui.msg_queue.put(message)
def _display(self, layout):
"""launch layouts display"""
diff --git a/test/input/func_non_iterator_returned_py30.py b/test/input/func_non_iterator_returned_py30.py
new file mode 100644
index 0000000..6915768
--- /dev/null
+++ b/test/input/func_non_iterator_returned_py30.py
@@ -0,0 +1,52 @@
+"""Check non-iterators returned by __iter__ """
+
+# pylint: disable=too-few-public-methods
+
+__revision__ = 0
+
+class FirstGoodIterator(object):
+ """ yields in iterator. """
+
+ def __iter__(self):
+ for index in range(10):
+ yield index
+
+class SecondGoodIterator(object):
+ """ __iter__ and next """
+
+ def __iter__(self):
+ return self
+
+ def __next__(self): # pylint: disable=no-self-use
+ """ Infinite iterator, but still an iterator """
+ return 1
+
+class ThirdGoodIterator(object):
+ """ Returns other iterator, not the current instance """
+
+ def __iter__(self):
+ return SecondGoodIterator()
+
+class FourthGoodIterator(object):
+ """ __iter__ returns iter(...) """
+
+ def __iter__(self):
+ return iter(range(10))
+
+class FirstBadIterator(object):
+ """ __iter__ returns a list """
+
+ def __iter__(self):
+ return []
+
+class SecondBadIterator(object):
+ """ __iter__ without next """
+
+ def __iter__(self):
+ return self
+
+class ThirdBadIterator(object):
+ """ __iter__ returns an instance of another non-iterator """
+
+ def __iter__(self):
+ return SecondBadIterator()
diff --git a/test/input/func_non_iterator_returned_py_30.py b/test/input/func_non_iterator_returned_py_30.py
new file mode 100644
index 0000000..ff75941
--- /dev/null
+++ b/test/input/func_non_iterator_returned_py_30.py
@@ -0,0 +1,52 @@
+"""Check non-iterators returned by __iter__ """
+
+# pylint: disable=too-few-public-methods
+
+__revision__ = 0
+
+class FirstGoodIterator(object):
+ """ yields in iterator. """
+
+ def __iter__(self):
+ for index in range(10):
+ yield index
+
+class SecondGoodIterator(object):
+ """ __iter__ and next """
+
+ def __iter__(self):
+ return self
+
+ def next(self): # pylint: disable=no-self-use
+ """ Infinite iterator, but still an iterator """
+ return 1
+
+class ThirdGoodIterator(object):
+ """ Returns other iterator, not the current instance """
+
+ def __iter__(self):
+ return SecondGoodIterator()
+
+class FourthGoodIterator(object):
+ """ __iter__ returns iter(...) """
+
+ def __iter__(self):
+ return iter(range(10))
+
+class FirstBadIterator(object):
+ """ __iter__ returns a list """
+
+ def __iter__(self):
+ return []
+
+class SecondBadIterator(object):
+ """ __iter__ without next """
+
+ def __iter__(self):
+ return self
+
+class ThirdBadIterator(object):
+ """ __iter__ returns an instance of another non-iterator """
+
+ def __iter__(self):
+ return SecondBadIterator()
diff --git a/test/input/func_useless_else_on_loop.py b/test/input/func_useless_else_on_loop.py
index 79be8e6..cec5a74 100644
--- a/test/input/func_useless_else_on_loop.py
+++ b/test/input/func_useless_else_on_loop.py
@@ -3,7 +3,7 @@
__revision__ = 0
def test_return_for():
- """else + return is accetable."""
+ """else + return is not accetable."""
for i in range(10):
if i % 2:
return i
@@ -11,7 +11,7 @@ def test_return_for():
print 'math is broken'
def test_return_while():
- """else + return is accetable."""
+ """else + return is not accetable."""
while True:
return 1
else:
diff --git a/test/messages/func_non_iterator_returned_py30.txt b/test/messages/func_non_iterator_returned_py30.txt
new file mode 100644
index 0000000..b41b14b
--- /dev/null
+++ b/test/messages/func_non_iterator_returned_py30.txt
@@ -0,0 +1,3 @@
+W: 39:FirstBadIterator.__iter__: __iter__ returns non-iterator
+W: 45:SecondBadIterator.__iter__: __iter__ returns non-iterator
+W: 51:ThirdBadIterator.__iter__: __iter__ returns non-iterator \ No newline at end of file
diff --git a/test/messages/func_non_iterator_returned_py_30.txt b/test/messages/func_non_iterator_returned_py_30.txt
new file mode 100644
index 0000000..b41b14b
--- /dev/null
+++ b/test/messages/func_non_iterator_returned_py_30.txt
@@ -0,0 +1,3 @@
+W: 39:FirstBadIterator.__iter__: __iter__ returns non-iterator
+W: 45:SecondBadIterator.__iter__: __iter__ returns non-iterator
+W: 51:ThirdBadIterator.__iter__: __iter__ returns non-iterator \ No newline at end of file
diff --git a/test/unittest_lint.py b/test/unittest_lint.py
index a65eb01..0b847c2 100644
--- a/test/unittest_lint.py
+++ b/test/unittest_lint.py
@@ -26,22 +26,12 @@ from logilab.common.compat import reload
from pylint import config
from pylint.lint import PyLinter, Run, UnknownMessage, preprocess_options, \
ArgumentPreprocessingError
-from pylint.utils import sort_msgs, PyLintASTWalker, MSG_STATE_SCOPE_CONFIG, \
- MSG_STATE_SCOPE_MODULE, tokenize_module
+from pylint.utils import MSG_STATE_SCOPE_CONFIG, MSG_STATE_SCOPE_MODULE, \
+ PyLintASTWalker, MessageDefinition, build_message_def, tokenize_module
from pylint.testutils import TestReporter
from pylint.reporters import text
from pylint import checkers
-class SortMessagesTC(TestCase):
-
- def test(self):
- l = ['E0501', 'E0503', 'F0002', 'I0201', 'W0540',
- 'R0202', 'F0203', 'R0220', 'W0321', 'I0001']
- self.assertEqual(sort_msgs(l), ['E0501', 'E0503',
- 'W0321', 'W0540',
- 'R0202', 'R0220',
- 'I0001', 'I0201',
- 'F0002', 'F0203',])
class GetNoteMessageTC(TestCase):
def test(self):
@@ -83,11 +73,40 @@ class PyLinterTC(TestCase):
checkers.initialize(self.linter)
self.linter.set_reporter(TestReporter())
+ def test_check_message_id(self):
+ self.assertIsInstance(self.linter.check_message_id('F0001'),
+ MessageDefinition)
+ self.assertRaises(UnknownMessage,
+ self.linter.check_message_id, 'YB12')
+
def test_message_help(self):
- msg = self.linter.get_message_help('F0001', checkerref=True)
- expected = ':F0001 (fatal):\n Used when an error occurred preventing the analysis of a module (unable to\n find it for instance). This message belongs to the master checker.'
- self.assertMultiLineEqual(msg, expected)
- self.assertRaises(UnknownMessage, self.linter.get_message_help, 'YB12')
+ msg = self.linter.check_message_id('F0001')
+ self.assertMultiLineEqual(
+ ''':F0001 (fatal):
+ Used when an error occurred preventing the analysis of a module (unable to
+ find it for instance). This message belongs to the master checker.''',
+ msg.format_help(checkerref=True))
+ self.assertMultiLineEqual(
+ ''':F0001 (fatal):
+ Used when an error occurred preventing the analysis of a module (unable to
+ find it for instance).''',
+ msg.format_help(checkerref=False))
+
+ def test_message_help_minmax(self):
+ # build the message manually to be python version independant
+ msg = build_message_def(self.linter._checkers['typecheck'][0],
+ 'E1122', checkers.typecheck.MSGS['E1122'])
+ self.assertMultiLineEqual(
+ ''':E1122 (duplicate-keyword-arg): *Duplicate keyword argument %r in function call*
+ Used when a function call passes the same keyword argument multiple times.
+ This message belongs to the typecheck checker. It can't be emitted when using
+ Python >= 2.6.''',
+ msg.format_help(checkerref=True))
+ self.assertMultiLineEqual(
+ ''':E1122 (duplicate-keyword-arg): *Duplicate keyword argument %r in function call*
+ Used when a function call passes the same keyword argument multiple times.
+ This message can't be emitted when using Python >= 2.6.''',
+ msg.format_help(checkerref=False))
def test_enable_message(self):
linter = self.linter
diff --git a/test/unittest_reporting.py b/test/unittest_reporting.py
index d338670..8fda31d 100644
--- a/test/unittest_reporting.py
+++ b/test/unittest_reporting.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE).
+# Copyright (c) 2003-2013 LOGILAB S.A. (Paris, FRANCE).
# 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
@@ -12,23 +12,13 @@
# this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
-import sys
import os
-import tempfile
-from shutil import rmtree
-from os import getcwd, chdir
-from os.path import join, basename, dirname, isdir, abspath
+from os.path import join, dirname, abspath
from cStringIO import StringIO
-from logilab.common.testlib import TestCase, unittest_main, create_files
-from logilab.common.compat import reload
+from logilab.common.testlib import TestCase, unittest_main
-from pylint import config
-from pylint.lint import PyLinter, Run, UnknownMessage, preprocess_options, \
- ArgumentPreprocessingError
-from pylint.utils import sort_msgs, PyLintASTWalker, MSG_STATE_SCOPE_CONFIG, \
- MSG_STATE_SCOPE_MODULE, tokenize_module
-from pylint.testutils import TestReporter
+from pylint.lint import PyLinter
from pylint import checkers
from pylint.reporters.text import TextReporter
@@ -46,12 +36,6 @@ class PyLinterTC(TestCase):
os.environ.pop('PYLINTRC', None)
def test_template_option(self):
- # self.linter.set_reporter(TextReporter())
- expected = ( '************* Module 0123\n'
- 'C0301:001\n'
- 'C0301:002\n'
- )
-
output = StringIO()
self.linter.reporter.set_output(output)
self.linter.set_option('msg-template', '{msg_id}:{line:03d}')
@@ -59,7 +43,10 @@ class PyLinterTC(TestCase):
self.linter.set_current_module('0123')
self.linter.add_message('C0301', line=1, args=(1, 2))
self.linter.add_message('line-too-long', line=2, args=(3, 4))
- self.assertMultiLineEqual(output.getvalue(), expected)
+ self.assertMultiLineEqual(output.getvalue(),
+ '************* Module 0123\n'
+ 'C0301:001\n'
+ 'C0301:002\n')
if __name__ == '__main__':
diff --git a/utils.py b/utils.py
index 1895a75..3dc8410 100644
--- a/utils.py
+++ b/utils.py
@@ -20,8 +20,8 @@ main pylint class
import re
import sys
import tokenize
-from warnings import warn
import os
+from warnings import warn
from os.path import dirname, basename, splitext, exists, isdir, join, normpath
from logilab.common.interface import implements
@@ -43,7 +43,6 @@ class EmptyReport(Exception):
"""raised when a report is empty and so should not be displayed"""
-
MSG_TYPES = {
'I' : 'info',
'C' : 'convention',
@@ -77,17 +76,6 @@ class WarningScope(object):
NODE = 'node-based-msg'
-def sort_msgs(msgids):
- """sort message identifiers according to their category first"""
- msgs = {}
- for msg in msgids:
- msgs.setdefault(msg[0], []).append(msg)
- result = []
- for m_id in _MSG_ORDER:
- if m_id in msgs:
- result.extend( sorted(msgs[m_id]) )
- return result
-
def get_module_and_frameid(node):
"""return the module name and the frame id in the module"""
frame = node.frame()
@@ -122,18 +110,78 @@ def tokenize_module(module):
return list(tokenize.generate_tokens(readline))
return list(tokenize.tokenize(readline))
+def build_message_def(checker, msgid, msg_tuple):
+ if implements(checker, (IRawChecker, ITokenChecker)):
+ default_scope = WarningScope.LINE
+ else:
+ default_scope = WarningScope.NODE
+ options = {}
+ if len(msg_tuple) > 3:
+ (msg, symbol, descr, options) = msg_tuple
+ elif len(msg_tuple) > 2:
+ (msg, symbol, descr) = msg_tuple[:3]
+ else:
+ # messages should have a symbol, but for backward compatibility
+ # they may not.
+ (msg, descr) = msg_tuple
+ warn("[pylint 0.26] description of message %s doesn't include "
+ "a symbolic name" % msgid, DeprecationWarning)
+ symbol = None
+ options.setdefault('scope', default_scope)
+ return MessageDefinition(checker, msgid, msg, descr, symbol, **options)
+
class MessageDefinition(object):
- def __init__(self, checker, msgid, msg, descr, symbol, scope):
+ def __init__(self, checker, msgid, msg, descr, symbol, scope,
+ minversion=None, maxversion=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)
self.msgid = msgid
self.msg = msg
self.descr = descr
- self.checker = checker
self.symbol = symbol
self.scope = scope
+ self.minversion = minversion
+ self.maxversion = maxversion
+
+ def may_be_emitted(self):
+ """return True if message may be emitted using the current interpreter"""
+ if self.minversion is not None and self.minversion > sys.version_info:
+ return False
+ if self.maxversion is not None and self.maxversion <= sys.version_info:
+ return False
+ return True
+
+ def format_help(self, checkerref=False):
+ """return the help string for the given message id"""
+ desc = self.descr
+ if checkerref:
+ desc += ' This message belongs to the %s checker.' % \
+ self.checker.name
+ title = self.msg
+ if self.symbol:
+ symbol_part = ' (%s)' % self.symbol
+ else:
+ symbol_part = ''
+ if self.minversion or self.maxversion:
+ restr = []
+ if self.minversion:
+ restr.append('< %s' % '.'.join([str(n) for n in self.minversion]))
+ if self.maxversion:
+ restr.append('>= %s' % '.'.join([str(n) for n in self.maxversion]))
+ restr = ' or '.join(restr)
+ if checkerref:
+ desc += " It can't be emitted when using Python %s." % restr
+ else:
+ desc += " This message can't be emitted when using Python %s." % restr
+ desc = normalize_text(' '.join(desc.split()), indent=' ')
+ if title != '%s':
+ title = title.splitlines()[0]
+ return ':%s%s: *%s*\n%s' % (self.msgid, symbol_part, title, desc)
+ return ':%s%s:\n%s' % (self.msgid, symbol_part, desc)
+
class MessagesHandlerMixIn(object):
"""a mix-in class containing all the messages related methods for the main
@@ -162,65 +210,23 @@ class MessagesHandlerMixIn(object):
message ids should be a string of len 4, where the two first characters
are the checker id and the two last the message id in this checker
"""
- msgs_dict = checker.msgs
chkid = None
-
- for msgid, msg_tuple in msgs_dict.iteritems():
- if implements(checker, (IRawChecker, ITokenChecker)):
- scope = WarningScope.LINE
- else:
- scope = WarningScope.NODE
- if len(msg_tuple) > 2:
- (msg, msgsymbol, msgdescr) = msg_tuple[:3]
- assert msgsymbol not in self._messages_by_symbol, \
- 'Message symbol %r is already defined' % msgsymbol
- if len(msg_tuple) > 3:
- if 'scope' in msg_tuple[3]:
- scope = msg_tuple[3]['scope']
- if 'minversion' in msg_tuple[3]:
- minversion = msg_tuple[3]['minversion']
- if minversion > sys.version_info:
- self._msgs_state[msgid] = False
- continue
- if 'maxversion' in msg_tuple[3]:
- maxversion = msg_tuple[3]['maxversion']
- if maxversion <= sys.version_info:
- self._msgs_state[msgid] = False
- continue
- else:
- # messages should have a symbol, but for backward compatibility
- # they may not.
- (msg, msgdescr) = msg_tuple
- warn("[pylint 0.26] description of message %s doesn't include "
- "a symbolic name" % msgid, DeprecationWarning)
- msgsymbol = None
+ for msgid, msg_tuple in checker.msgs.iteritems():
+ msg = build_message_def(checker, msgid, msg_tuple)
+ assert msg.symbol not in self._messages_by_symbol, \
+ 'Message symbol %r is already defined' % msg.symbol
# avoid duplicate / malformed ids
- assert msgid not in self._messages, \
+ assert msg.msgid not in self._messages, \
'Message id %r is already defined' % msgid
- assert chkid is None or chkid == msgid[1:3], \
+ assert chkid is None or chkid == msg.msgid[1:3], \
'Inconsistent checker part in message id %r' % msgid
- chkid = msgid[1:3]
- msg = MessageDefinition(checker, msgid, msg, msgdescr, msgsymbol, scope)
- self._messages[msgid] = msg
- self._messages_by_symbol[msgsymbol] = msg
- self._msgs_by_category.setdefault(msgid[0], []).append(msgid)
-
- def get_message_help(self, msgid, checkerref=False):
- """return the help string for the given message id"""
- msg = self.check_message_id(msgid)
- desc = normalize_text(' '.join(msg.descr.split()), indent=' ')
- if checkerref:
- desc += ' This message belongs to the %s checker.' % \
- msg.checker.name
- title = msg.msg
- if msg.symbol:
- symbol_part = ' (%s)' % msg.symbol
- else:
- symbol_part = ''
- if title != '%s':
- title = title.splitlines()[0]
- return ':%s%s: *%s*\n%s' % (msg.msgid, symbol_part, title, desc)
- return ':%s%s:\n%s' % (msg.msgid, symbol_part, desc)
+ chkid = msg.msgid[1:3]
+ if not msg.may_be_emitted():
+ self._msgs_state[msg.msgid] = False
+ continue
+ self._messages[msg.msgid] = msg
+ self._messages_by_symbol[msg.symbol] = msg
+ self._msgs_by_category.setdefault(msg.msgid[0], []).append(msg.msgid)
def disable(self, msgid, scope='package', line=None):
"""don't output message of the given id"""
@@ -417,7 +423,7 @@ class MessagesHandlerMixIn(object):
"""display help messages for the given message identifiers"""
for msgid in msgids:
try:
- print self.get_message_help(msgid, True)
+ print self.check_message_id(msgid).format_help(checkerref=True)
print
except UnknownMessage, ex:
print ex
@@ -467,8 +473,10 @@ class MessagesHandlerMixIn(object):
title = ('%smessages' % prefix).capitalize()
print title
print '~' * len(title)
- for msgid in sort_msgs(msgs.iterkeys()):
- print self.get_message_help(msgid, False)
+ for msgid, msg in sorted(msgs.iteritems(),
+ key=lambda (k,v): (_MSG_ORDER.index(k[0]), k)):
+ msg = build_message_def(checker, msgid, msg)
+ print msg.format_help(checkerref=False)
print
if reports:
title = ('%sreports' % prefix).capitalize()
@@ -486,7 +494,7 @@ class MessagesHandlerMixIn(object):
msgids.append(msgid)
msgids.sort()
for msgid in msgids:
- print self.get_message_help(msgid, False)
+ print self.check_message_id(msgid).format_help(checkerref=False)
print