summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSylvain Th?nault <thenault@gmail.com>2013-10-03 16:27:26 +0200
committerSylvain Th?nault <thenault@gmail.com>2013-10-03 16:27:26 +0200
commit0a53cc2d8e60ade303893d19ff1b0d2e8be70ad4 (patch)
tree5e8d2cda54a8649b04e1d231d537566adc988906
parent8c1788f1cec10a874aa5f398832f71d98e44c366 (diff)
parent8cfc7f6be64bc107beea841f61680c7111d3d9bf (diff)
downloadpylint-0a53cc2d8e60ade303893d19ff1b0d2e8be70ad4.tar.gz
Merged in TobiasRzepka/pylint (pull request #58)
#51: Building pylint Windows installer for Python3 fails
-rw-r--r--ChangeLog2
-rw-r--r--__main__.py3
-rw-r--r--doc/Makefile4
-rw-r--r--doc/contribute.rst2
-rw-r--r--doc/output.rst24
-rw-r--r--doc/run.rst22
-rw-r--r--doc/tutorial.rst123
-rw-r--r--test/input/func_noerror_defined_and_used_on_same_line_py27.py2
-rw-r--r--test/test_misc.py48
-rw-r--r--test/unittest_lint.py73
-rw-r--r--test/unittest_reporting.py29
-rw-r--r--utils.py160
12 files changed, 258 insertions, 234 deletions
diff --git a/ChangeLog b/ChangeLog
index 3662602..c7a65af 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,6 +2,8 @@ ChangeLog for Pylint
====================
--
+ * 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
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/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/contribute.rst b/doc/contribute.rst
index 2468933..6a45dea 100644
--- a/doc/contribute.rst
+++ b/doc/contribute.rst
@@ -135,7 +135,7 @@ sorted. E.g on Unix system, you may generate it using::
Also, here are a few naming convention which are used:
-* Python files starting with 'func_noerror_' don't have any message file
+* Python files starting with 'func_noerror' don't have any message file
associated as they are expected to provide no output at all
* You may provide different input files (and associated output) depending on the
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/doc/run.rst b/doc/run.rst
index 7360c3e..4b0ccfa 100644
--- a/doc/run.rst
+++ b/doc/run.rst
@@ -97,18 +97,14 @@ hand tune the configuration.
Other useful global options include:
---zope Initialize Zope products before starting
---ignore=file Add <file> (may be a directory) to the black
- list. It should be a base name, not a path.
- You may set this option multiple times.
---statistics=y_or_n Compute statistics on collected data.
---persistent=y_or_n Pickle collected data for later comparisons.
---comment=y_or_n Add a comment according to your evaluation note.
---parseable=y_or_n Use a parseable output format.
---html=y_or_n Use HTML as output format instead of text.
---list-msgs Generate pylint's messages.
---full-documentation Generate pylint's full documentation, in reST format.
---include_ids=y_or_n Show numeric ids of messages (like 'C0301')
---symbols=y_or_n Show symbolic ids of messsages (like 'line-too-long')
+--ignore=file Add <file> (may be a directory) to the black
+ list. It should be a base name, not a path.
+ You may set this option multiple times.
+--persistent=y_or_n Pickle collected data for later comparisons.
+--output-format=<format> Select output format (text, html, custom).
+--msg-template=<template> Modifiy text output message template.
+--list-msgs Generate pylint's messages.
+--full-documentation Generate pylint's full documentation, in reST
+ format.
diff --git a/doc/tutorial.rst b/doc/tutorial.rst
index 5561c50..cbe4cb5 100644
--- a/doc/tutorial.rst
+++ b/doc/tutorial.rst
@@ -69,7 +69,6 @@ A couple of the options that we'll focus on here are: ::
Reports:
--files-output=<y_or_n>
--reports=<y_or_n>
- --include-ids=<y_or_n>
--output-format=<format>
Also pay attention to the last bit of help output. This gives you a hint of what
@@ -138,16 +137,16 @@ If we run this:
robertk01 Desktop$ pylint simplecaeser.py
No config file found, using default configuration
************* Module simplecaeser
- C: 1: Missing docstring
- W: 3: Uses of a deprecated module 'string'
- C: 5: Invalid name "shift" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C: 6: Invalid name "choice" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C: 7: Invalid name "word" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C: 8: Invalid name "letters" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C: 9: Invalid name "encoded" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C: 16: Operator not preceded by a space
- encoded=encoded + letters[x]
- ^
+ C: 1, 0: Missing module docstring (missing-docstring)
+ W: 3, 0: Uses of a deprecated module 'string' (deprecated-module)
+ C: 5, 0: Invalid constant name "shift" (invalid-name)
+ C: 6, 0: Invalid constant name "choice" (invalid-name)
+ C: 7, 0: Invalid constant name "word" (invalid-name)
+ C: 8, 0: Invalid constant name "letters" (invalid-name)
+ C: 9, 0: Invalid constant name "encoded" (invalid-name)
+ C: 16,12: Operator not preceded by a space
+ encoded=encoded + letters[x]
+ ^ (no-space-before-operator)
Report
@@ -221,17 +220,17 @@ If we run this:
Messages
--------
- +-----------+-----------+
- |message id |occurrences |
- +===========+===========+
- |C0103 |5 |
- +-----------+-----------+
- |W0402 |1 |
- +-----------+-----------+
- |C0322 |1 |
- +-----------+-----------+
- |C0111 |1 |
- +-----------+-----------+
+ +-------------------------+------------+
+ |message id |occurrences |
+ +=========================+============+
+ |invalid-name |5 |
+ +-------------------------+------------+
+ |no-space-before-operator |1 |
+ +-------------------------+------------+
+ |missing-docstring |1 |
+ +-------------------------+------------+
+ |deprecated-module |1 |
+ +-------------------------+------------+
@@ -244,59 +243,42 @@ Wow. That's a lot of stuff. The first part is the 'messages' section while the
second part is the 'report' section. There are two points I want to tackle here.
First point is that all the tables of statistics (i.e. the report) are a bit
-overwhelming so I want to silence them. To do that, I will use the "--reports=n" option.
+overwhelming so I want to silence them. To do that, I will use the
+"--reports=n" option.
+
+.. tip:: Many of Pylint's commonly used command line options have shortcuts.
+ for example, "--reports=n" can be abbreviated to "-rn". Pylint's man page lists
+ all these shortcuts.
Second, previous experience taught me that the default output for the messages
needed a bit more info. We can see the first line is: ::
- "C: 1: Missing docstring"
+ "C: 1: Missing docstring (missing-docstring)"
This basically means that line 1 violates a convention 'C'. It's telling me I
really should have a docstring. I agree, but what if I didn't fully understand
what rule I violated. Knowing only that I violated a convention isn't much help
-if I'm a newbie. So let's turn on a bit more info by using the option
-"--include-ids=y".
-
-.. tip:: Many of Pylint's commonly used command line options have shortcuts.
- for example, "--reports=n" can be abbreviated to "-r n", and "--include-ids=y"
- can be abbreviated to "-i y". Pylint's man page lists all these shortcuts.
-
-Let's do it again!
+if I'm a newbie. Another information there is the message symbol between parens,
+`missing-docstring` here.
-.. sourcecode:: bash
-
- robertk01 Desktop$ pylint --reports=n --include-ids=y simplecaeser.py
- No config file found, using default configuration
- ************* Module simplecaeser
- C0111: 1: Missing docstring
- W0402: 3: Uses of a deprecated module 'string'
- C0103: 5: Invalid name "shift" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0103: 6: Invalid name "choice" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0103: 7: Invalid name "word" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0103: 8: Invalid name "letters" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0103: 9: Invalid name "encoded" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0322: 16: Operator not preceded by a space
- encoded=encoded + letters[x]
-
-Oooh. I like that better. Now I know that I violated the convention number
-C0111 and now I can read up a bit more about that. Let's go back to the
+If I want to read up a bit more about that, I can go back to the
command line and try this:
.. sourcecode:: bash
- robertk01 Desktop$ pylint --help-msg=C0111
+ robertk01 Desktop$ pylint --help-msg=missing-docstring
No config file found, using default configuration
- :C0111: *Missing docstring*
+ :missing-docstring (C0111): *Missing docstring*
Used when a module, function, class or method has no docstring. Some special
methods like __init__ doesn't necessary require a docstring. This message
belongs to the basic checker.
-Yeah, ok. That one was a bit of a no-brainer but I have run into error messages
+Yeah, ok. That one was a bit of a no-brainer but I have run into error messages
that left me with no clue about what went wrong, simply because I was unfamiliar
with the underlying mechanism of code theory. One error that puzzled my newbie
mind was: ::
- :R0902: *Too many instance attributes (%s/%s)*
+ :too-many-instance-attributes (R0902): *Too many instance attributes (%s/%s)*
I get it now thanks to Pylint pointing it out to me. If you don't get that one,
pour a fresh cup of coffee and look into it - let your programmer mind grow!
@@ -309,13 +291,13 @@ Now that we got some configuration stuff out of the way, let's see what we can
do with the remaining warnings.
If we add a docstring to describe what the code is meant to do that will help.
-I'm also going to be a bit cowboy and ignore the W0402 message because I like to
-take risks in life. A deprecation warning means that future versions of Python
-may not support that code so my code may break in the future. There are 5 C0103
-messages that we will get to later. Lastly, I violated the convention of using
-spaces around an operator such as "=" so I'll fix that too. To sum up, I'll add
-a docstring to line 2, put spaces around the = sign on line 16 and use the
-"--disable=W0402" to ignore the deprecation warning.
+I'm also going to be a bit cowboy and ignore the `deprecated-module` message
+because I like to take risks in life. A deprecation warning means that future
+versions of Python may not support that code so my code may break in the future.
+There are 5 `invalid-name` messages that we will get to later. Lastly, I violated the
+convention of using spaces around an operator such as "=" so I'll fix that too.
+To sum up, I'll add a docstring to line 2, put spaces around the = sign on line
+16 and use the `--disable=deprecated-module` to ignore the deprecation warning.
Here is the updated code:
@@ -349,20 +331,21 @@ Here is the updated code:
26
27 print encoded
-And here is what happens when we run it with our --disable=W0402 option:
+And here is what happens when we run it with our `--disable=deprecated-module`
+option:
.. sourcecode:: bash
- robertk01 Desktop$ pylint --reports=n --include-ids=y --disable=W0402 simplecaeser.py
+ robertk01 Desktop$ pylint --reports=n --disable=deprecated-module simplecaeser.py
No config file found, using default configuration
************* Module simplecaeser
- C0103: 7: Invalid name "shift" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0103: 8: Invalid name "choice" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0103: 9: Invalid name "word" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0103: 10: Invalid name "letters" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
- C0103: 11: Invalid name "encoded" (should match (([A-Z_][A-Z1-9_]*)|(__.*__))$)
+ C: 7, 0: Invalid constant name "shift" (invalid-name)
+ C: 8, 0: Invalid constant name "choice" (invalid-name)
+ C: 9, 0: Invalid constant name "word" (invalid-name)
+ C: 10, 0: Invalid constant name "letters" (invalid-name)
+ C: 11, 0: Invalid constant name "encoded" (invalid-name)
-Nice! We're down to just the C0103 messages.
+Nice! We're down to just the `invalid-name` messages.
There are fairly well defined conventions around naming things like instance
variables, functions, classes, etc. The conventions focus on the use of
@@ -380,12 +363,12 @@ of all lowercase. The appropriate rule would be something like:
"should match [a-z\_][a-z0-9\_]{2,30}$". Notice the lowercase letters in the
regular expression (a-z versus A-Z).
-If we run that rule using a --const-rgx='[a-z\_][a-z0-9\_]{2,30}$' option, it
+If we run that rule using a `--const-rgx='[a-z\_][a-z0-9\_]{2,30}$'` option, it
will now be quite quiet:
.. sourcecode:: bash
- robertk01 Desktop$ pylint --reports=n --include-ids=y --disable=W0402 --const-rgx='[a-z_][a-z0-9_]{2,30}$' simplecaeser.py
+ robertk01 Desktop$ pylint --reports=n --disable=deprecated-module --const-rgx='[a-z_][a-z0-9_]{2,30}$' simplecaeser.py
No config file found, using default configuration
Regular expressions can be quite a beast so take my word on this particular
diff --git a/test/input/func_noerror_defined_and_used_on_same_line_py27.py b/test/input/func_noerror_defined_and_used_on_same_line_py27.py
index 5a75722..11b1a2d 100644
--- a/test/input/func_noerror_defined_and_used_on_same_line_py27.py
+++ b/test/input/func_noerror_defined_and_used_on_same_line_py27.py
@@ -1,5 +1,7 @@
#pylint: disable=C0111,C0321
"""pylint complains about 'index' being used before definition"""
+__revision__ = 1
+
with open('f') as f, open(f.read()) as g:
print g.read()
diff --git a/test/test_misc.py b/test/test_misc.py
index 68f3e4a..d2c03c0 100644
--- a/test/test_misc.py
+++ b/test/test_misc.py
@@ -17,44 +17,50 @@ Tests for the misc checker.
"""
import tempfile
+import os
+import contextlib
from logilab.common.testlib import unittest_main
from astroid import test_utils
from pylint.checkers import misc
from pylint.testutils import CheckerTestCase, Message
+@contextlib.contextmanager
+def create_file_backed_module(code):
+ # Can't use tempfile.NamedTemporaryFile here
+ # because on Windows the file must be closed before writing to it,
+ # see http://bugs.python.org/issue14243
+ fd, tmp = tempfile.mkstemp()
+ os.write(fd, code)
+
+ try:
+ module = test_utils.build_module(code)
+ module.file = tmp
+ yield module
+ finally:
+ os.close(fd)
+ os.remove(tmp)
+
class FixmeTest(CheckerTestCase):
CHECKER_CLASS = misc.EncodingChecker
- def create_file_backed_module(self, code):
- tmp = tempfile.NamedTemporaryFile()
- tmp.write(code)
- tmp.flush()
- module = test_utils.build_module(code)
- module.file = tmp.name
- # Just make sure to keep a reference to the file
- # so it isn't deleted.
- module._tmpfile = tmp
- return module
-
def test_fixme(self):
- module = self.create_file_backed_module(
+ with create_file_backed_module(
"""a = 1
- # FIXME
- """)
- with self.assertAddsMessages(
- Message(msg_id='W0511', line=2, args=u'FIXME')):
- self.checker.process_module(module)
+ # FIXME """) as module:
+ with self.assertAddsMessages(
+ Message(msg_id='W0511', line=2, args=u'FIXME')):
+ self.checker.process_module(module)
def test_emtpy_fixme_regex(self):
self.checker.config.notes = []
- module = self.create_file_backed_module(
+ with create_file_backed_module(
"""a = 1
# fixme
- """)
- with self.assertNoMessages():
- self.checker.process_module(module)
+ """) as module:
+ with self.assertNoMessages():
+ self.checker.process_module(module)
if __name__ == '__main__':
diff --git a/test/unittest_lint.py b/test/unittest_lint.py
index a65eb01..7612ef4 100644
--- a/test/unittest_lint.py
+++ b/test/unittest_lint.py
@@ -26,22 +26,16 @@ 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',])
+if sys.platform == 'win32':
+ HOME = 'USERPROFILE'
+else:
+ HOME = 'HOME'
class GetNoteMessageTC(TestCase):
def test(self):
@@ -83,11 +77,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
@@ -369,9 +392,9 @@ class ConfigTC(TestCase):
def test_pylintrc(self):
fake_home = tempfile.mkdtemp('fake-home')
- home = os.environ['HOME']
+ home = os.environ[HOME]
try:
- os.environ['HOME'] = fake_home
+ os.environ[HOME] = fake_home
self.assertEqual(config.find_pylintrc(), None)
os.environ['PYLINTRC'] = join(tempfile.gettempdir(), '.pylintrc')
self.assertEqual(config.find_pylintrc(), None)
@@ -379,7 +402,7 @@ class ConfigTC(TestCase):
self.assertEqual(config.find_pylintrc(), None)
finally:
os.environ.pop('PYLINTRC', '')
- os.environ['HOME'] = home
+ os.environ[HOME] = home
rmtree(fake_home, ignore_errors=True)
reload(config)
@@ -397,12 +420,12 @@ class ConfigTC(TestCase):
'a/b/c/__init__.py', 'a/b/c/d/__init__.py'], chroot)
os.chdir(chroot)
fake_home = tempfile.mkdtemp('fake-home')
- home = os.environ['HOME']
+ home = os.environ[HOME]
try:
- os.environ['HOME'] = fake_home
+ os.environ[HOME] = fake_home
self.assertEqual(config.find_pylintrc(), None)
finally:
- os.environ['HOME'] = home
+ os.environ[HOME] = home
os.rmdir(fake_home)
results = {'a' : join(chroot, 'a', 'pylintrc'),
'a/b' : join(chroot, 'a', 'b', 'pylintrc'),
@@ -427,8 +450,8 @@ class ConfigTC(TestCase):
chdir(cdir)
fake_home = tempfile.mkdtemp('fake-home')
- home = os.environ['HOME']
- os.environ['HOME'] = fake_home
+ home = os.environ[HOME]
+ os.environ[HOME] = fake_home
try:
create_files(['a/pylintrc', 'a/b/pylintrc', 'a/b/c/d/__init__.py'], chroot)
os.chdir(chroot)
@@ -442,7 +465,7 @@ class ConfigTC(TestCase):
os.chdir(join(chroot, basedir))
self.assertEqual(config.find_pylintrc(), expected)
finally:
- os.environ['HOME'] = home
+ os.environ[HOME] = home
rmtree(fake_home, ignore_errors=True)
os.chdir(HERE)
rmtree(chroot)
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..875ac10 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:
+ msgid = '%s (%s)' % (self.symbol, self.msgid)
+ else:
+ msgid = self.msgid
+ 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*\n%s' % (msgid, title, desc)
+ return ':%s:\n%s' % (msgid, 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"""
@@ -396,9 +402,9 @@ class MessagesHandlerMixIn(object):
self.stats[msg_cat] += 1
self.stats['by_module'][self.current_name][msg_cat] += 1
try:
- self.stats['by_msg'][msgid] += 1
+ self.stats['by_msg'][msg_info.symbol] += 1
except KeyError:
- self.stats['by_msg'][msgid] = 1
+ self.stats['by_msg'][msg_info.symbol] = 1
# expand message ?
msg = msg_info.msg
if args:
@@ -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