diff options
| author | cpopa <devnull@localhost> | 2013-09-26 13:12:45 +0300 |
|---|---|---|
| committer | cpopa <devnull@localhost> | 2013-09-26 13:12:45 +0300 |
| commit | 7d46a3cd8b4563fd0d93b6ea14fe9a3ec90c647d (patch) | |
| tree | 32ea3b034c86ea363539d4268bea166b9a0166f9 | |
| parent | aef858eedb5bef81e32600f53504e73818e297ef (diff) | |
| parent | 023b225765346f2026f714aec31ee57c4b8da5b5 (diff) | |
| download | pylint-7d46a3cd8b4563fd0d93b6ea14fe9a3ec90c647d.tar.gz | |
Merge with default.
| -rw-r--r-- | ChangeLog | 12 | ||||
| -rw-r--r-- | __main__.py | 3 | ||||
| -rw-r--r-- | checkers/classes.py | 36 | ||||
| -rw-r--r-- | doc/Makefile | 4 | ||||
| -rw-r--r-- | doc/output.rst | 24 | ||||
| -rwxr-xr-x | epylint.py | 25 | ||||
| -rw-r--r-- | gui.py | 30 | ||||
| -rw-r--r-- | lint.py | 2 | ||||
| -rw-r--r-- | reporters/guireporter.py | 10 | ||||
| -rw-r--r-- | test/input/func_non_iterator_returned_py30.py | 52 | ||||
| -rw-r--r-- | test/input/func_non_iterator_returned_py_30.py | 52 | ||||
| -rw-r--r-- | test/input/func_useless_else_on_loop.py | 4 | ||||
| -rw-r--r-- | test/messages/func_non_iterator_returned_py30.txt | 3 | ||||
| -rw-r--r-- | test/messages/func_non_iterator_returned_py_30.txt | 3 | ||||
| -rw-r--r-- | test/unittest_lint.py | 51 | ||||
| -rw-r--r-- | test/unittest_reporting.py | 29 | ||||
| -rw-r--r-- | utils.py | 156 |
17 files changed, 338 insertions, 158 deletions
@@ -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 @@ -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() @@ -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): @@ -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__': @@ -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 |
