diff options
-rw-r--r-- | README | 3 | ||||
-rw-r--r-- | checkers/base.py | 26 | ||||
-rw-r--r-- | checkers/classes.py | 21 | ||||
-rw-r--r-- | checkers/design_analysis.py | 12 | ||||
-rw-r--r-- | checkers/exceptions.py | 19 | ||||
-rw-r--r-- | checkers/format.py | 14 | ||||
-rw-r--r-- | checkers/imports.py | 8 | ||||
-rw-r--r-- | checkers/logging.py | 5 | ||||
-rw-r--r-- | checkers/misc.py | 1 | ||||
-rw-r--r-- | checkers/newstyle.py | 4 | ||||
-rw-r--r-- | checkers/similar.py | 1 | ||||
-rw-r--r-- | checkers/string_format.py | 9 | ||||
-rw-r--r-- | checkers/typecheck.py | 10 | ||||
-rw-r--r-- | checkers/variables.py | 16 | ||||
-rw-r--r-- | doc/FAQ.txt | 9 | ||||
-rw-r--r-- | doc/manual.txt | 2 | ||||
-rw-r--r-- | lint.py | 21 | ||||
-rw-r--r-- | reporters/__init__.py | 22 | ||||
-rw-r--r-- | reporters/guireporter.py | 6 | ||||
-rw-r--r-- | reporters/html.py | 5 | ||||
-rw-r--r-- | reporters/text.py | 15 | ||||
-rw-r--r-- | test/input/func_docstring.py | 13 | ||||
-rw-r--r-- | test/unittest_lint.py | 30 | ||||
-rw-r--r-- | utils.py | 49 |
24 files changed, 278 insertions, 43 deletions
@@ -65,7 +65,8 @@ order doesn't matter... * Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau, Maarten ter Huurne, Mirko Friedenhagen (among others): bug reports, feedback, feature requests... -* Martin Pool (Google): warnings for anomalous backslashes +* Martin Pool (Google): warnings for anomalous backslashes, symbolic names + for messages (like 'unused') * All the Logilab's team: daily use, bug reports, feature requests * Other people have contributed by their feedback, if I've forgotten you, send me a note ! diff --git a/checkers/base.py b/checkers/base.py index c08168d..08ea0cc 100644 --- a/checkers/base.py +++ b/checkers/base.py @@ -124,27 +124,35 @@ class _BasicChecker(BaseChecker): class BasicErrorChecker(_BasicChecker): msgs = { 'E0100': ('__init__ method is a generator', + 'init-is-generator', 'Used when the special class method __init__ is turned into a ' 'generator by a yield in its body.'), 'E0101': ('Explicit return in __init__', + 'return-in-init', 'Used when the special class method __init__ has an explicit \ return value.'), 'E0102': ('%s already defined line %s', + 'function-redefined', 'Used when a function / class / method is redefined.'), 'E0103': ('%r not properly in loop', + 'not-in-loop', 'Used when break or continue keywords are used outside a loop.'), 'E0104': ('Return outside function', + 'return-outside-function', 'Used when a "return" statement is found outside a function or ' 'method.'), 'E0105': ('Yield outside function', + 'yield-outside-function', 'Used when a "yield" statement is found outside a function or ' 'method.'), 'E0106': ('Return with argument inside generator', + 'return-arg-in-generator', 'Used when a "return" statement with an argument is found ' 'outside in a generator function or method (e.g. with some ' '"yield" statements).'), 'E0107': ("Use of the non-existent %s operator", + 'nonexistent-operator', "Used when you attempt to use the C-style pre-increment or" "pre-decrement operator -- and ++, which doesn't exist in Python."), } @@ -242,54 +250,67 @@ functions, methods name = 'basic' msgs = { 'W0101': ('Unreachable code', + 'unreachable', 'Used when there is some code behind a "return" or "raise" \ statement, which will never be accessed.'), 'W0102': ('Dangerous default value %s as argument', + 'dangerous-default-value', 'Used when a mutable value as list or dictionary is detected in \ a default value for an argument.'), 'W0104': ('Statement seems to have no effect', + 'pointless-statement', 'Used when a statement doesn\'t have (or at least seems to) \ any effect.'), 'W0105': ('String statement has no effect', + 'pointless-string-statement', 'Used when a string is used as a statement (which of course \ has no effect). This is a particular case of W0104 with its \ own message so you can easily disable it if you\'re using \ those strings as documentation, instead of comments.'), 'W0106': ('Expression "%s" is assigned to nothing', + 'expression-not-assigned', 'Used when an expression that is not a function call is assigned\ to nothing. Probably something else was intended.'), 'W0108': ('Lambda may not be necessary', + 'unnecessary-lambda', 'Used when the body of a lambda expression is a function call \ on the same argument list as the lambda itself; such lambda \ expressions are in all but a few cases replaceable with the \ function being called in the body of the lambda.'), 'W0109': ("Duplicate key %r in dictionary", + 'duplicate-key', "Used when a dictionary expression binds the same key multiple \ times."), 'W0122': ('Use of the exec statement', + 'exec-statement', 'Used when you use the "exec" statement, to discourage its \ usage. That doesn\'t mean you can not use it !'), 'W0141': ('Used builtin function %r', + 'bad-builtin', 'Used when a black listed builtin function is used (see the ' 'bad-function option). Usual black listed functions are the ones ' 'like map, or filter , where Python offers now some cleaner ' 'alternative like list comprehension.'), 'W0142': ('Used * or ** magic', + 'star-args', 'Used when a function or method is called using `*args` or ' '`**kwargs` to dispatch arguments. This doesn\'t improve ' 'readability and should be used with care.'), 'W0150': ("%s statement in finally block may swallow exception", + 'lost-exception', "Used when a break or a return statement is found inside the \ finally clause of a try...finally block: the exceptions raised \ in the try clause will be silently swallowed instead of being \ re-raised."), 'W0199': ('Assert called on a 2-uple. Did you mean \'assert x,y\'?', + 'assert-on-tuple', 'A call of assert on a tuple will always evaluate to true if ' 'the tuple is not empty, and will always evaluate to false if ' 'it is.'), 'C0121': ('Missing required attribute "%s"', # W0103 + 'missing-module-attribute', 'Used when an attribute required for modules is missing.'), } @@ -558,9 +579,11 @@ functions, methods class NameChecker(_BasicChecker): msgs = { 'C0102': ('Black listed name "%s"', + 'blacklisted-name', 'Used when the name is listed in the black list (unauthorized \ names).'), 'C0103': ('Invalid name "%s" (should match %s)', + 'invalid-name', 'Used when the name doesn\'t match the regular expression \ associated to its type (constant, variable, class...).'), @@ -709,10 +732,12 @@ class NameChecker(_BasicChecker): class DocStringChecker(_BasicChecker): msgs = { 'C0111': ('Missing docstring', # W0131 + 'missing-docstring', 'Used when a module, function, class or method has no docstring.\ Some special methods like __init__ doesn\'t necessary require a \ docstring.'), 'C0112': ('Empty docstring', # W0132 + 'empty-docstring', 'Used when a module, function, class or method has an empty \ docstring (it would be too easy ;).'), } @@ -768,6 +793,7 @@ class DocStringChecker(_BasicChecker): class PassChecker(_BasicChecker): """check is the pass statement is really necessary""" msgs = {'W0107': ('Unnecessary pass statement', + 'unnecessary-pass', 'Used when a "pass" statement that can be avoided is ' 'encountered.)'), } diff --git a/checkers/classes.py b/checkers/classes.py index 96fc183..6383001 100644 --- a/checkers/classes.py +++ b/checkers/classes.py @@ -38,87 +38,108 @@ def class_is_abstract(node): MSGS = { 'F0202': ('Unable to check methods signature (%s / %s)', + 'method-check-failed', 'Used when PyLint has been unable to check methods signature \ compatibility for an unexpected reason. Please report this kind \ if you don\'t make sense of it.'), 'E0202': ('An attribute affected in %s line %s hide this method', + 'method-hidden', 'Used when a class defines a method which is hidden by an ' 'instance attribute from an ancestor class or set by some ' 'client code.'), 'E0203': ('Access to member %r before its definition line %s', + 'access-member-before-definition', 'Used when an instance member is accessed before it\'s actually\ assigned.'), 'W0201': ('Attribute %r defined outside __init__', + 'attribute-defined-outside-init', 'Used when an instance attribute is defined outside the __init__\ method.'), 'W0212': ('Access to a protected member %s of a client class', # E0214 + 'protected-access', 'Used when a protected member (i.e. class member with a name \ beginning with an underscore) is access outside the class or a \ descendant of the class where it\'s defined.'), 'E0211': ('Method has no argument', + 'no-method-argument', 'Used when a method which should have the bound instance as \ first argument has no argument defined.'), 'E0213': ('Method should have "self" as first argument', + 'no-self-argument', 'Used when a method has an attribute different the "self" as\ first argument. This is considered as an error since this is\ a so common convention that you shouldn\'t break it!'), 'C0202': ('Class method %s should have %s as first argument', # E0212 + 'bad-classmethod-argument', 'Used when a class method has a first argument named differently ' 'than the value specified in valid-classmethod-first-arg option ' '(default to "cls"), recommended to easily differentiate them ' 'from regular instance methods.'), 'C0203': ('Metaclass method %s should have %s as first argument', # E0214 + 'bad-mcs-method-argument', 'Used when a metaclass method has a first agument named ' 'differently than the value specified in valid-classmethod-first' '-arg option (default to "cls"), recommended to easily ' 'differentiate them from regular instance methods.'), 'C0204': ('Metaclass class method %s should have %s as first argument', + 'bad-mcs-classmethod-argument', 'Used when a metaclass class method has a first argument named ' 'differently than the value specified in valid-metaclass-' 'classmethod-first-arg option (default to "mcs"), recommended to ' 'easily differentiate them from regular instance methods.'), 'W0211': ('Static method with %r as first argument', + 'bad-staticmethod-argument', 'Used when a static method has "self" or a value specified in ' 'valid-classmethod-first-arg option or ' 'valid-metaclass-classmethod-first-arg option as first argument.' ), 'R0201': ('Method could be a function', + 'no-self-use', 'Used when a method doesn\'t use its bound instance, and so could\ be written as a function.' ), 'E0221': ('Interface resolved to %s is not a class', + 'interface-is-not-class', 'Used when a class claims to implement an interface which is not \ a class.'), 'E0222': ('Missing method %r from %s interface', + 'missing-interface-method', 'Used when a method declared in an interface is missing from a \ class implementing this interface'), 'W0221': ('Arguments number differs from %s method', + 'arguments-differ', 'Used when a method has a different number of arguments than in \ the implemented interface or in an overridden method.'), 'W0222': ('Signature differs from %s method', + 'signature-differs', 'Used when a method signature is different than in the \ implemented interface or in an overridden method.'), 'W0223': ('Method %r is abstract in class %r but is not overridden', + 'abstract-method', 'Used when an abstract method (i.e. raise NotImplementedError) is \ not overridden in concrete class.' ), 'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224 + 'unresolved-interface', 'Used when a PyLint as failed to find interfaces implemented by \ a class'), 'W0231': ('__init__ method from base class %r is not called', + 'super-init-not-called', 'Used when an ancestor class method has an __init__ method \ which is not called by a derived class.'), 'W0232': ('Class has no __init__ method', + 'no-init', 'Used when a class has no __init__ method, neither its parent \ classes.'), 'W0233': ('__init__ method from a non direct base class %r is called', + '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.'), diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py index 659f191..2d40e8d 100644 --- a/checkers/design_analysis.py +++ b/checkers/design_analysis.py @@ -43,38 +43,50 @@ def class_is_abstract(klass): MSGS = { 'R0901': ('Too many ancestors (%s/%s)', + 'too-many-ancestors', 'Used when class has too many parent classes, try to reduce \ this to get a more simple (and so easier to use) class.'), 'R0902': ('Too many instance attributes (%s/%s)', + 'too-many-instance-attributes', 'Used when class has too many instance attributes, try to reduce \ this to get a more simple (and so easier to use) class.'), 'R0903': ('Too few public methods (%s/%s)', + 'too-few-public-methods', 'Used when class has too few public methods, so be sure it\'s \ really worth it.'), 'R0904': ('Too many public methods (%s/%s)', + 'too-many-public-methods', 'Used when class has too many public methods, try to reduce \ this to get a more simple (and so easier to use) class.'), 'R0911': ('Too many return statements (%s/%s)', + 'too-many-return-statements', 'Used when a function or method has too many return statement, \ making it hard to follow.'), 'R0912': ('Too many branches (%s/%s)', + 'too-many-branches', 'Used when a function or method has too many branches, \ making it hard to follow.'), 'R0913': ('Too many arguments (%s/%s)', + 'too-many-arguments', 'Used when a function or method takes too many arguments.'), 'R0914': ('Too many local variables (%s/%s)', + 'too-many-locals', 'Used when a function or method has too many local variables.'), 'R0915': ('Too many statements (%s/%s)', + 'too-many-statements', 'Used when a function or method has too many statements. You \ should then split it in smaller functions / methods.'), 'R0921': ('Abstract class not referenced', + 'abstract-class-not-used', 'Used when an abstract class is not used as ancestor anywhere.'), 'R0922': ('Abstract class is only referenced %s times', + 'abstract-class-little-used', 'Used when an abstract class is used less than X times as \ ancestor.'), 'R0923': ('Interface not implemented', + 'interface-not-implemented', 'Used when an interface class is not implemented anywhere.'), } diff --git a/checkers/exceptions.py b/checkers/exceptions.py index e94fa65..db0ebcc 100644 --- a/checkers/exceptions.py +++ b/checkers/exceptions.py @@ -29,36 +29,45 @@ from pylint.interfaces import IASTNGChecker OVERGENERAL_EXCEPTIONS = ('Exception',) MSGS = { - 'E0701': ( - 'Bad except clauses order (%s)', - 'Used when except clauses are not in the correct order (from the \ - more specific to the more generic). If you don\'t fix the order, \ - some exceptions may not be catched by the most specific handler.'), + 'E0701': ('Bad except clauses order (%s)', + 'bad-except-order', + 'Used when except clauses are not in the correct order (from the ' + 'more specific to the more generic). If you don\'t fix the order, ' + 'some exceptions may not be catched by the most specific handler.'), 'E0702': ('Raising %s while only classes, instances or string are allowed', + 'raising-bad-type', 'Used when something which is neither a class, an instance or a \ string is raised (i.e. a `TypeError` will be raised).'), 'E0710': ('Raising a new style class which doesn\'t inherit from BaseException', + 'raising-non-exception', 'Used when a new style class which doesn\'t inherit from \ BaseException is raised.'), 'E0711': ('NotImplemented raised - should raise NotImplementedError', + 'notimplemented-raised', 'Used when NotImplemented is raised instead of \ NotImplementedError'), 'W0701': ('Raising a string exception', + 'raising-string', 'Used when a string exception is raised.'), 'W0702': ('No exception type(s) specified', + 'bare-except', 'Used when an except clause doesn\'t specify exceptions type to \ catch.'), 'W0703': ('Catching too general exception %s', + 'broad-except', 'Used when an except catches a too general exception, \ possibly burying unrelated errors.'), 'W0704': ('Except doesn\'t do anything', + 'pointless-except', 'Used when an except clause does nothing but "pass" and there is\ no "else" clause.'), 'W0710': ('Exception doesn\'t inherit from standard "Exception" class', + 'nonstandard-exception', 'Used when a custom exception class is raised but doesn\'t \ inherit from the builtin "Exception" class.'), 'W0711': ('Exception to catch is the result of a binary "%s" operation', + 'binary-op-exception', 'Used when the exception to catch is of the form \ "except A or B:". If intending to catch multiple, \ rewrite as "except (A, B):"'), diff --git a/checkers/format.py b/checkers/format.py index 85eb667..67b6952 100644 --- a/checkers/format.py +++ b/checkers/format.py @@ -37,28 +37,37 @@ from pylint.checkers.utils import check_messages MSGS = { 'C0301': ('Line too long (%s/%s)', + 'line-too-long', 'Used when a line is longer than a given number of characters.'), 'C0302': ('Too many lines in module (%s)', # was W0302 + 'too-many-lines', 'Used when a module has too much lines, reducing its readability.' ), 'W0311': ('Bad indentation. Found %s %s, expected %s', + 'bad-indentation', 'Used when an unexpected number of indentation\'s tabulations or ' 'spaces has been found.'), 'W0312': ('Found indentation with %ss instead of %ss', + 'mixed-indentation', 'Used when there are some mixed tabs and spaces in a module.'), 'W0301': ('Unnecessary semicolon', # was W0106 + 'unnecessary-semicolon', 'Used when a statement is ended by a semi-colon (";"), which \ isn\'t necessary (that\'s python, not C ;).'), 'C0321': ('More than one statement on a single line', + 'multiple-statements', 'Used when more than on statement are found on the same line.'), 'C0322': ('Operator not preceded by a space\n%s', + 'no-space-before-operator', 'Used when one of the following operator (!= | <= | == | >= | < ' '| > | = | \\+= | -= | \\*= | /= | %) is not preceded by a space.'), 'C0323': ('Operator not followed by a space\n%s', + 'no-space-after-operator', 'Used when one of the following operator (!= | <= | == | >= | < ' '| > | = | \\+= | -= | \\*= | /= | %) is not followed by a space.'), 'C0324': ('Comma not followed by a space\n%s', + 'no-space-after-comma', 'Used when a comma (",") is not followed by a space.'), } @@ -66,13 +75,16 @@ if sys.version_info < (3, 0): MSGS.update({ 'W0331': ('Use of the <> operator', + 'old-ne-operator', 'Used when the deprecated "<>" operator is used instead \ of "!=".'), 'W0332': ('Use of "l" as long integer identifier', + 'lowercase-l-suffix', 'Used when a lower case "l" is used to mark a long integer. You ' 'should use a upper case "L" since the letter "l" looks too much ' 'like the digit "1"'), 'W0333': ('Use of the `` operator', + 'backtick', 'Used when the deprecated "``" (backtick) operator is used ' 'instead of the str() function.'), }) @@ -365,10 +377,12 @@ class StringConstantChecker(BaseRawChecker): msgs = { 'W1401': ('Anomalous backslash in string: \'%s\'. ' 'String constant might be missing an r prefix.', + 'anomalous-backslash-in-string', 'Used when a backslash is in a literal string but not as an ' 'escape.'), 'W1402': ('Anomalous Unicode escape in byte string: \'%s\'. ' 'String constant might be missing an r or u prefix.', + 'anomalous-unicode-escape-in-string', 'Used when an escape like \\u is encountered in a byte ' 'string where it has no effect.'), } diff --git a/checkers/imports.py b/checkers/imports.py index aba10d2..2b61cf4 100644 --- a/checkers/imports.py +++ b/checkers/imports.py @@ -128,24 +128,32 @@ def make_graph(filename, dep_info, sect, gtype): MSGS = { 'F0401': ('Unable to import %s', + 'import-error', 'Used when pylint has been unable to import a module.'), 'R0401': ('Cyclic import (%s)', + 'cyclic-import', 'Used when a cyclic import between two or more modules is \ detected.'), 'W0401': ('Wildcard import %s', + 'wildcard-import', 'Used when `from module import *` is detected.'), 'W0402': ('Uses of a deprecated module %r', + 'deprecated-module', 'Used a module marked as deprecated is imported.'), 'W0403': ('Relative import %r, should be %r', + 'relative-import', 'Used when an import relative to the package directory is \ detected.'), 'W0404': ('Reimport %r (imported line %s)', + 'reimported', 'Used when a module is reimported multiple times.'), 'W0406': ('Module import itself', + 'import-self', 'Used when a module is importing itself.'), 'W0410': ('__future__ import is not the first non docstring statement', + 'misplaced-future', 'Python 2.5 and greater require __future__ import to be the \ first non docstring statement in the module.'), } diff --git a/checkers/logging.py b/checkers/logging.py index 89899b6..9092b6c 100644 --- a/checkers/logging.py +++ b/checkers/logging.py @@ -22,6 +22,7 @@ from pylint.checkers import utils MSGS = { 'W1201': ('Specify string format arguments as logging function parameters', + 'logging-not-lazy', 'Used when a logging statement has a call form of ' '"logging.<logging method>(format_string % (format_args...))". ' 'Such calls should leave string interpolation to the logging ' @@ -32,14 +33,18 @@ MSGS = { 'logged. For more, see ' 'http://www.python.org/dev/peps/pep-0282/.'), 'E1200': ('Unsupported logging format character %r (%#02x) at index %d', + 'logging-unsupported-format', 'Used when an unsupported format character is used in a logging\ statement format string.'), 'E1201': ('Logging format string ends in middle of conversion specifier', + 'logging-format-truncated', 'Used when a logging statement format string terminates before\ the end of a conversion specifier.'), 'E1205': ('Too many arguments for logging format string', + 'logging-too-many-args', 'Used when a logging format string is given too few arguments.'), 'E1206': ('Not enough arguments for logging format string', + 'logging-too-few-args', 'Used when a logging format string is given too many arguments'), } diff --git a/checkers/misc.py b/checkers/misc.py index e8918a0..18d7586 100644 --- a/checkers/misc.py +++ b/checkers/misc.py @@ -25,6 +25,7 @@ from pylint.checkers import BaseChecker MSGS = { 'W0511': ('%s', + 'fixme', 'Used when a warning note as FIXME or XXX is detected.'), } diff --git a/checkers/newstyle.py b/checkers/newstyle.py index 7bb146d..edadad8 100644 --- a/checkers/newstyle.py +++ b/checkers/newstyle.py @@ -24,13 +24,17 @@ from pylint.checkers.utils import check_messages MSGS = { 'E1001': ('Use of __slots__ on an old style class', + 'slots-on-old-class', 'Used when an old style class uses the __slots__ attribute.'), 'E1002': ('Use of super on an old style class', + 'super-on-old-class', 'Used when an old style class uses the super builtin.'), 'E1003': ('Bad first argument %r given to super class', + 'bad-super-call', 'Used when another argument than the current class is given as \ first argument of the super builtin.'), 'W1001': ('Use of "property" on an old style class', + 'property-on-old-class', 'Used when PyLint detect the use of the builtin "property" \ on an old style class while this is relying on new style \ classes features'), diff --git a/checkers/similar.py b/checkers/similar.py index 8f88593..ed4f614 100644 --- a/checkers/similar.py +++ b/checkers/similar.py @@ -197,6 +197,7 @@ class LineSet: MSGS = {'R0801': ('Similar lines in %s files\n%s', + 'duplicate-code', 'Indicates that a set of similar lines has been detected \ among multiple file. This usually means that the code should \ be refactored to avoid this duplication.')} diff --git a/checkers/string_format.py b/checkers/string_format.py index 62ccf51..d21ad3e 100644 --- a/checkers/string_format.py +++ b/checkers/string_format.py @@ -24,34 +24,43 @@ from pylint.checkers import utils MSGS = { 'E1300': ("Unsupported format character %r (%#02x) at index %d", + "bad-format-character", "Used when a unsupported format character is used in a format\ string."), 'E1301': ("Format string ends in middle of conversion specifier", + "truncated-format-string", "Used when a format string terminates before the end of a \ conversion specifier."), 'E1302': ("Mixing named and unnamed conversion specifiers in format string", + "mixed-format-string", "Used when a format string contains both named (e.g. '%(foo)d') \ and unnamed (e.g. '%d') conversion specifiers. This is also \ used when a named conversion specifier contains * for the \ minimum field width and/or precision."), 'E1303': ("Expected mapping for format string, not %s", + "format-needs-mapping", "Used when a format string that uses named conversion specifiers \ is used with an argument that is not a mapping."), 'W1300': ("Format string dictionary key should be a string, not %s", + "bad-format-string-key", "Used when a format string that uses named conversion specifiers \ is used with a dictionary whose keys are not all strings."), 'W1301': ("Unused key %r in format string dictionary", + "unused-format-string-key", "Used when a format string that uses named conversion specifiers \ is used with a dictionary that conWtains keys not required by the \ format string."), 'E1304': ("Missing key %r in format string dictionary", + "missing-format-string-key", "Used when a format string that uses named conversion specifiers \ is used with a dictionary that doesn't contain all the keys \ required by the format string."), 'E1305': ("Too many arguments for format string", + "too-many-format-args", "Used when a format string that uses unnamed conversion \ specifiers is given too few arguments."), 'E1306': ("Not enough arguments for format string", + "too-few-format-args", "Used when a format string that uses unnamed conversion \ specifiers is given too many arguments"), } diff --git a/checkers/typecheck.py b/checkers/typecheck.py index db2f493..23bb7aa 100644 --- a/checkers/typecheck.py +++ b/checkers/typecheck.py @@ -28,33 +28,43 @@ from pylint.checkers.utils import safe_infer, is_super, check_messages MSGS = { 'E1101': ('%s %r has no %r member', + 'no-member', 'Used when a variable is accessed for an unexistent member.'), 'E1102': ('%s is not callable', + 'not-callable', 'Used when an object being called has been inferred to a non \ callable object'), 'E1103': ('%s %r has no %r member (but some types could not be inferred)', + 'maybe-no-member', 'Used when a variable is accessed for an unexistent member, but \ astng was not able to interpret all possible types of this \ variable.'), 'E1111': ('Assigning to function call which doesn\'t return', + 'assignment-from-no-return', 'Used when an assignment is done on a function call but the \ inferred function doesn\'t return anything.'), 'W1111': ('Assigning to function call which only returns None', + 'assignment-from-none', 'Used when an assignment is done on a function call but the \ inferred function returns nothing but None.'), 'E1120': ('No value passed for parameter %s in function call', + 'no-value-for-parameter', 'Used when a function call passes too few arguments.'), 'E1121': ('Too many positional arguments for function call', + 'too-many-function-args', 'Used when a function call passes too many positional \ arguments.'), 'E1122': ('Duplicate keyword argument %r in function call', + 'duplicate-keyword-arg', 'Used when a function call passes the same keyword argument \ multiple times.'), 'E1123': ('Passing unexpected keyword argument %r in function call', + 'unexpected-keyword-arg', 'Used when a function call passes a keyword argument that \ doesn\'t correspond to one of the function\'s parameter names.'), 'E1124': ('Multiple values passed for parameter %r in function call', + 'redundant-keyword-arg', 'Used when a function call would result in assigning multiple \ values to a function parameter, one value from a positional \ argument and one from a keyword argument.'), diff --git a/checkers/variables.py b/checkers/variables.py index 3826ab9..e3812fc 100644 --- a/checkers/variables.py +++ b/checkers/variables.py @@ -54,48 +54,64 @@ def overridden_method(klass, name): MSGS = { 'E0601': ('Using variable %r before assignment', + 'used-before-assignment', 'Used when a local variable is accessed before it\'s \ assignment.'), 'E0602': ('Undefined variable %r', + 'undefined-variable', 'Used when an undefined variable is accessed.'), 'E0603': ('Undefined variable name %r in __all__', + 'undefined-all-variable', 'Used when an undefined variable name is referenced in __all__.'), 'E0611': ('No name %r in module %r', + 'no-name-in-module', 'Used when a name cannot be found in a module.'), 'W0601': ('Global variable %r undefined at the module level', + 'global-variable-undefined', 'Used when a variable is defined through the "global" statement \ but the variable is not defined in the module scope.'), 'W0602': ('Using global for %r but no assignment is done', + 'global-variable-not-assigned', 'Used when a variable is defined through the "global" statement \ but no assignment to this variable is done.'), 'W0603': ('Using the global statement', # W0121 + 'global-statement', 'Used when you use the "global" statement to update a global \ variable. PyLint just try to discourage this \ usage. That doesn\'t mean you can not use it !'), 'W0604': ('Using the global statement at the module level', # W0103 + 'global-at-module-level', 'Used when you use the "global" statement at the module level \ since it has no effect'), 'W0611': ('Unused import %s', + 'unused-import', 'Used when an imported module or variable is not used.'), 'W0612': ('Unused variable %r', + 'unused-variable', 'Used when a variable is defined but not used.'), 'W0613': ('Unused argument %r', + 'unused-argument', 'Used when a function or method argument is not used.'), 'W0614': ('Unused import %s from wildcard import', + 'unused-wildcard-import', 'Used when an imported module or variable is not used from a \ \'from X import *\' style import.'), 'W0621': ('Redefining name %r from outer scope (line %s)', + 'redefined-outer-name', 'Used when a variable\'s name hide a name defined in the outer \ scope.'), 'W0622': ('Redefining built-in %r', + 'redefined-builtin', 'Used when a variable or function override a built-in.'), 'W0623': ('Redefining name %r from %s in exception handler', + 'redefine-in-handler', 'Used when an exception handler assigns the exception \ to an existing name'), 'W0631': ('Using possibly undefined loop variable %r', + 'undefined-loop-variable', 'Used when an loop variable (i.e. defined by a for loop or \ a list comprehension or a generator expression) is used outside \ the loop.'), diff --git a/doc/FAQ.txt b/doc/FAQ.txt index 56b17c2..992190a 100644 --- a/doc/FAQ.txt +++ b/doc/FAQ.txt @@ -243,6 +243,15 @@ It means that if you need to disable a lot of messages, you can use tricks like: E0202, # I have a good reason, trust me C0302 # that's it +4.6 Do I have to remember all these numbers? +-------------------------------------------- + +No, starting from 0.25.3, you can use symbolic names for messages:: + + # pylint: disable=fixme, line-too-long + +You can show these symbols in the output with the `-sy` option. + 5. Classes and Inheritance ========================== diff --git a/doc/manual.txt b/doc/manual.txt index 17be0a0..98f6065 100644 --- a/doc/manual.txt +++ b/doc/manual.txt @@ -313,6 +313,8 @@ Other useful global options include: --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') .. _features: features.html @@ -84,47 +84,59 @@ def _get_python_path(filepath): MSGS = { 'F0001': ('%s', + 'fatal', 'Used when an error occurred preventing the analysis of a \ module (unable to find it for instance).'), 'F0002': ('%s: %s', + 'astng-error', 'Used when an unexpected error occurred while building the ASTNG \ representation. This is usually accompanied by a traceback. \ Please report such errors !'), 'F0003': ('ignored builtin module %s', + 'ignored-builtin-module', 'Used to indicate that the user asked to analyze a builtin module\ which has been skipped.'), 'F0004': ('unexpected inferred value %s', + 'unexpected-inferred-value', 'Used to indicate that some value of an unexpected type has been \ inferred.'), 'F0010': ('error while code parsing: %s', + 'parse-error', 'Used when an exception occured while building the ASTNG \ representation which could be handled by astng.'), - 'I0001': ('Unable to run raw checkers on built-in module %s', + 'raw-checker-failed', 'Used to inform that a built-in module has not been checked \ using the raw checkers.'), 'I0010': ('Unable to consider inline option %r', + 'bad-inline-option', 'Used when an inline option is either badly formatted or can\'t \ be used inside modules.'), 'I0011': ('Locally disabling %s', + 'locally-disabled', 'Used when an inline option disables a message or a messages \ category.'), 'I0012': ('Locally enabling %s', + 'locally-enabled', 'Used when an inline option enables a message or a messages \ category.'), 'I0013': ('Ignoring entire file', + 'file-ignored', 'Used to inform that the file will not be checked'), 'E0001': ('%s', + 'syntax-error', 'Used when a syntax error is raised for a module.'), 'E0011': ('Unrecognized file option %r', + 'unrecognized-inline-option', 'Used when an unknown inline option is encountered.'), 'E0012': ('Bad option value %r', + 'bad-option-value', 'Used when a bad value for an inline option is encountered.'), } @@ -184,6 +196,12 @@ python modules names) to load, usually to register additional checkers.'}), 'group': 'Reports', 'help' : 'Include message\'s id in output'}), + ('symbols', + {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0, + 'short': 's', + 'group': 'Reports', + 'help' : 'Include symbolic ids of messages in output'}), + ('files-output', {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>', 'group': 'Reports', 'level': 1, @@ -483,6 +501,7 @@ This is used by the global evaluation report (RP0004).'}), name. """ self.reporter.include_ids = self.config.include_ids + self.reporter.symbols = self.config.symbols if not isinstance(files_or_modules, (list, tuple)): files_or_modules = (files_or_modules,) walker = PyLintASTWalker(self) diff --git a/reporters/__init__.py b/reporters/__init__.py index 8f54820..8be46b7 100644 --- a/reporters/__init__.py +++ b/reporters/__init__.py @@ -31,18 +31,38 @@ class EmptyReport(Exception): """raised when a report is empty and so should not be displayed""" class BaseReporter: - """base class for reporters""" + """base class for reporters + + symbols: show short symbolic names for messages. + """ extension = '' def __init__(self, output=None): self.linter = None self.include_ids = None + self.symbols = None self.section = 0 self.out = None self.out_encoding = None self.set_output(output) + def make_sigle(self, msg_id): + """generate a short prefix for a message. + + The sigle can include the id, the symbol, or both, or it can just be + the message class. + """ + if self.include_ids: + sigle = msg_id + else: + sigle = msg_id[0] + if self.symbols: + symbol = self.linter.check_message_id(msg_id).symbol + if symbol: + sigle += '(%s)' % symbol + return sigle + def set_output(self, output=None): """set output stream""" self.out = output or sys.stdout diff --git a/reporters/guireporter.py b/reporters/guireporter.py index 4e98fef..8fe6d53 100644 --- a/reporters/guireporter.py +++ b/reporters/guireporter.py @@ -22,11 +22,7 @@ class GUIReporter(BaseReporter): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" module, obj, line, col_offset = location[1:] - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] - + sigle = self.make_sigle(msg_id) full_msg = [sigle, module, obj, str(line), msg] self.msgs += [[sigle, module, obj, str(line)]] self.gui.msg_queue.put(full_msg) diff --git a/reporters/html.py b/reporters/html.py index 56efcd6..cac08b2 100644 --- a/reporters/html.py +++ b/reporters/html.py @@ -36,10 +36,7 @@ class HTMLReporter(BaseReporter): def add_message(self, msg_id, location, msg): """manage message of different type and in the context of path""" module, obj, line, col_offset = location[1:] - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] + sigle = self.make_sigle(msg_id) self.msgs += [sigle, module, obj, str(line), str(col_offset), escape(msg)] def set_output(self, output=None): diff --git a/reporters/text.py b/reporters/text.py index dd4d536..7b30c84 100644 --- a/reporters/text.py +++ b/reporters/text.py @@ -56,10 +56,7 @@ class TextReporter(BaseReporter): self.writeln('************* %s' % module) if obj: obj = ':%s' % obj - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] + sigle = self.make_sigle(msg_id) self.writeln('%s:%3s,%s%s: %s' % (sigle, line, col_offset, obj, msg)) def _display(self, layout): @@ -88,10 +85,7 @@ class ParseableTextReporter(TextReporter): path, _, obj, line, _ = location if obj: obj = ', %s' % obj - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] + sigle = self.make_sigle(msg_id) if self._prefix: path = path.replace(self._prefix, '') self.writeln(self.line_format % locals()) @@ -145,10 +139,7 @@ class ColorizedTextReporter(TextReporter): self._modules[module] = 1 if obj: obj = ':%s' % obj - if self.include_ids: - sigle = msg_id - else: - sigle = msg_id[0] + sigle = self.make_sigle(msg_id) color, style = self._get_decoration(sigle) msg = colorize_ansi(msg, color, style) sigle = colorize_ansi(sigle, color, style) diff --git a/test/input/func_docstring.py b/test/input/func_docstring.py index 2a92c87..01cd9e7 100644 --- a/test/input/func_docstring.py +++ b/test/input/func_docstring.py @@ -20,7 +20,7 @@ class AAAA: ## class BBBB: ## # missing docstring ## pass - + ## class CCCC: ## """yeah !""" ## def method1(self): @@ -29,20 +29,23 @@ class AAAA: ## def method2(self): ## """ yeah !""" ## pass - + def method1(self): pass - + def method2(self): """ yeah !""" pass def __init__(self): pass - + class DDDD(AAAA): """yeah !""" def __init__(self): AAAA.__init__(self) - + +# pylint: disable=missing-docstring +def function4(): + pass diff --git a/test/unittest_lint.py b/test/unittest_lint.py index 2ccc7c9..03e2d35 100644 --- a/test/unittest_lint.py +++ b/test/unittest_lint.py @@ -177,6 +177,36 @@ class PyLinterTC(TestCase): self.assert_(linter.is_message_enabled('E1101', 75)) self.assert_(linter.is_message_enabled('E1101', 77)) + def test_enable_by_symbol(self): + """messages can be controlled by symbolic names. + + The state is consistent across symbols and numbers. + """ + linter = self.linter + linter.open() + linter.set_current_module('toto') + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102')) + self.assertTrue(linter.is_message_enabled('dangerous-default-value')) + linter.disable('unreachable', scope='package') + linter.disable('dangerous-default-value', scope='module', line=1) + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertFalse(linter.is_message_enabled('unreachable')) + self.assertFalse(linter.is_message_enabled('W0102', 1)) + self.assertFalse(linter.is_message_enabled('dangerous-default-value', 1)) + linter.set_current_module('tutu') + self.assertFalse(linter.is_message_enabled('W0101')) + self.assertFalse(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102')) + self.assertTrue(linter.is_message_enabled('dangerous-default-value')) + linter.enable('unreachable', scope='package') + linter.enable('dangerous-default-value', scope='module', line=1) + self.assertTrue(linter.is_message_enabled('W0101')) + self.assertTrue(linter.is_message_enabled('unreachable')) + self.assertTrue(linter.is_message_enabled('W0102', 1)) + self.assertTrue(linter.is_message_enabled('dangerous-default-value', 1)) + def test_list_messages(self): sys.stdout = StringIO() try: @@ -19,6 +19,7 @@ main pylint class """ import sys +from warnings import warn from os import linesep from os.path import dirname, basename, splitext, exists, isdir, join, normpath @@ -93,7 +94,7 @@ def category_id(id): class Message: - def __init__(self, checker, msgid, msg, descr): + def __init__(self, checker, msgid, msg, descr, symbol): assert len(msgid) == 5, 'Invalid message id %s' % msgid assert msgid[0] in MSG_TYPES, \ 'Bad message type %s in %r' % (msgid[0], msgid) @@ -101,6 +102,7 @@ class Message: self.msg = msg self.descr = descr self.checker = checker + self.symbol = symbol class MessagesHandlerMixIn: """a mix-in class containing all the messages related methods for the main @@ -110,6 +112,8 @@ class MessagesHandlerMixIn: def __init__(self): # dictionary of registered messages self._messages = {} + # dictionary from string symbolic id to Message object. + self._messages_by_symbol = {} self._msgs_state = {} self._module_msgs_state = {} # None self._msgs_by_category = {} @@ -126,14 +130,27 @@ class MessagesHandlerMixIn: """ msgs_dict = checker.msgs chkid = None - for msgid, (msg, msgdescr) in msgs_dict.iteritems(): + for msgid, msg_tuple in msgs_dict.iteritems(): + if len(msg_tuple) == 3: + (msg, msgsymbol, msgdescr) = msg_tuple + assert msgsymbol not in self._messages_by_symbol, \ + 'Message symbol %r is already defined' % msgsymbol + 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 # avoid duplicate / malformed ids assert msgid not in self._messages, \ 'Message id %r is already defined' % msgid assert chkid is None or chkid == msgid[1:3], \ 'Inconsistent checker part in message id %r' % msgid chkid = msgid[1:3] - self._messages[msgid] = Message(checker, msgid, msg, msgdescr) + msg = Message(checker, msgid, msg, msgdescr, msgsymbol) + 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): @@ -144,10 +161,14 @@ class MessagesHandlerMixIn: 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*\n%s' % (msg.msgid, title, desc) - return ':%s:\n%s' % (msg.msgid, desc) + return ':%s%s: *%s*\n%s' % (msg.msgid, symbol_part, title, desc) + return ':%s%s:\n%s' % (msg.msgid, symbol_part, desc) def disable(self, msgid, scope='package', line=None): """don't output message of the given id""" @@ -168,7 +189,7 @@ class MessagesHandlerMixIn: if msgid.lower().startswith('rp'): self.disable_report(msgid) return - # msgid is a msgid. + # msgid is a symbolic or numeric msgid. msg = self.check_message_id(msgid) if scope == 'module': assert line > 0 @@ -205,7 +226,7 @@ class MessagesHandlerMixIn: if msgid.lower().startswith('rp'): self.enable_report(msgid) return - # msgid is a msgid. + # msgid is a symbolic or numeric msgid. msg = self.check_message_id(msgid) if scope == 'module': assert line > 0 @@ -221,7 +242,14 @@ class MessagesHandlerMixIn: self.config.enable = [mid for mid, val in msgs.iteritems() if val] def check_message_id(self, msgid): - """raise UnknownMessage if the message id is not defined""" + """returns the Message object for this message. + + msgid may be either a numeric or symbolic id. + + Raises UnknownMessage if the message id is not defined. + """ + if msgid in self._messages_by_symbol: + return self._messages_by_symbol[msgid] msgid = msgid.upper() try: return self._messages[msgid] @@ -231,7 +259,11 @@ class MessagesHandlerMixIn: def is_message_enabled(self, msgid, line=None): """return true if the message associated to the given message id is enabled + + msgid may be either a numeric or symbolic message id. """ + if msgid in self._messages_by_symbol: + msgid = self._messages_by_symbol[msgid].msgid if line is None: return self._msgs_state.get(msgid, True) try: @@ -525,4 +557,3 @@ class PyLintASTWalker(object): self.walk(child) for cb in self.leave_events.get(cid, ()): cb(astng) - |