Closes #104572: symbolic warning names in output (by Martin Pool)

triggered whatever the format using a command line option

authorMartin Pool <mbp@google.com>
changesetb9dab5059b81
branchdefault
phasepublic
hiddenno
parent revision#b40e3b4bc006 [format checker] check for anomalous backslash escape (new W1401, W1402). Closes #104571
child revision#6144a29a48c5 stop using assert_, rather use assertTrue / assertFalse
files modified by this revision
README
checkers/base.py
checkers/classes.py
checkers/design_analysis.py
checkers/exceptions.py
checkers/format.py
checkers/imports.py
checkers/logging.py
checkers/misc.py
checkers/newstyle.py
checkers/similar.py
checkers/string_format.py
checkers/typecheck.py
checkers/variables.py
doc/FAQ.txt
doc/manual.txt
lint.py
reporters/__init__.py
reporters/guireporter.py
reporters/html.py
reporters/text.py
test/input/func_docstring.py
test/unittest_lint.py
utils.py
# HG changeset patch
# User Martin Pool <mbp@google.com>
# Date 1348067743 -7200
# Wed Sep 19 17:15:43 2012 +0200
# Node ID b9dab5059b81ed2b5f85e74b8b4b14a7c14143db
# Parent b40e3b4bc00692ccb503520cb689bc8d03d33cbd
Closes #104572: symbolic warning names in output (by Martin Pool)

triggered whatever the format using a command line option

diff --git a/README b/README
@@ -63,9 +63,10 @@
1  * Benjamin Niemann: patch to allow block level enabling/disabling of messages
2  * Nathaniel Manista: suspicious lambda checking
3  * Wolfgang Grafen, Axel Muller, Fabio Zadrozny, Pierre Rouleau,
4    Maarten ter Huurne, Mirko Friedenhagen (among others):
5    bug reports, feedback, feature requests...
6 -* Martin Pool (Google): warnings for anomalous backslashes
7 +* Martin Pool (Google): warnings for anomalous backslashes, symbolic names
8 +  for messages (like 'unused')
9  * All the Logilab's team: daily use, bug reports, feature requests
10  * Other people have contributed by their feedback, if I've forgotten
11    you, send me a note !
diff --git a/checkers/base.py b/checkers/base.py
@@ -122,31 +122,39 @@
12      name = 'basic'
13 
14  class BasicErrorChecker(_BasicChecker):
15      msgs = {
16      'E0100': ('__init__ method is a generator',
17 +              'init-is-generator',
18                'Used when the special class method __init__ is turned into a '
19                'generator by a yield in its body.'),
20      'E0101': ('Explicit return in __init__',
21 +              'return-in-init',
22                'Used when the special class method __init__ has an explicit \
23                return value.'),
24      'E0102': ('%s already defined line %s',
25 +              'function-redefined',
26                'Used when a function / class / method is redefined.'),
27      'E0103': ('%r not properly in loop',
28 +              'not-in-loop',
29                'Used when break or continue keywords are used outside a loop.'),
30 
31      'E0104': ('Return outside function',
32 +              'return-outside-function',
33                'Used when a "return" statement is found outside a function or '
34                'method.'),
35      'E0105': ('Yield outside function',
36 +              'yield-outside-function',
37                'Used when a "yield" statement is found outside a function or '
38                'method.'),
39      'E0106': ('Return with argument inside generator',
40 +              'return-arg-in-generator',
41                'Used when a "return" statement with an argument is found '
42                'outside in a generator function or method (e.g. with some '
43                '"yield" statements).'),
44      'E0107': ("Use of the non-existent %s operator",
45 +              'nonexistent-operator',
46                "Used when you attempt to use the C-style pre-increment or"
47                "pre-decrement operator -- and ++, which doesn't exist in Python."),
48      }
49 
50      def __init__(self, linter):
@@ -240,58 +248,71 @@
51      __implements__ = IASTNGChecker
52 
53      name = 'basic'
54      msgs = {
55      'W0101': ('Unreachable code',
56 +              'unreachable',
57                'Used when there is some code behind a "return" or "raise" \
58                statement, which will never be accessed.'),
59      'W0102': ('Dangerous default value %s as argument',
60 +              'dangerous-default-value',
61                'Used when a mutable value as list or dictionary is detected in \
62                a default value for an argument.'),
63      'W0104': ('Statement seems to have no effect',
64 +              'pointless-statement',
65                'Used when a statement doesn\'t have (or at least seems to) \
66                any effect.'),
67      'W0105': ('String statement has no effect',
68 +              'pointless-string-statement',
69                'Used when a string is used as a statement (which of course \
70                has no effect). This is a particular case of W0104 with its \
71                own message so you can easily disable it if you\'re using \
72                those strings as documentation, instead of comments.'),
73      'W0106': ('Expression "%s" is assigned to nothing',
74 +              'expression-not-assigned',
75                'Used when an expression that is not a function call is assigned\
76                to nothing. Probably something else was intended.'),
77      'W0108': ('Lambda may not be necessary',
78 +              'unnecessary-lambda',
79                'Used when the body of a lambda expression is a function call \
80                on the same argument list as the lambda itself; such lambda \
81                expressions are in all but a few cases replaceable with the \
82                function being called in the body of the lambda.'),
83      'W0109': ("Duplicate key %r in dictionary",
84 +              'duplicate-key',
85                "Used when a dictionary expression binds the same key multiple \
86                times."),
87      'W0122': ('Use of the exec statement',
88 +              'exec-statement',
89                'Used when you use the "exec" statement, to discourage its \
90                usage. That doesn\'t mean you can not use it !'),
91 
92      'W0141': ('Used builtin function %r',
93 +              'bad-builtin',
94                'Used when a black listed builtin function is used (see the '
95                'bad-function option). Usual black listed functions are the ones '
96                'like map, or filter , where Python offers now some cleaner '
97                'alternative like list comprehension.'),
98      'W0142': ('Used * or ** magic',
99 +              'star-args',
100                'Used when a function or method is called using `*args` or '
101                '`**kwargs` to dispatch arguments. This doesn\'t improve '
102                'readability and should be used with care.'),
103      'W0150': ("%s statement in finally block may swallow exception",
104 +              'lost-exception',
105                "Used when a break or a return statement is found inside the \
106                finally clause of a try...finally block: the exceptions raised \
107                in the try clause will be silently swallowed instead of being \
108                re-raised."),
109      'W0199': ('Assert called on a 2-uple. Did you mean \'assert x,y\'?',
110 +              'assert-on-tuple',
111                'A call of assert on a tuple will always evaluate to true if '
112                'the tuple is not empty, and will always evaluate to false if '
113                'it is.'),
114 
115      'C0121': ('Missing required attribute "%s"', # W0103
116 +              'missing-module-attribute',
117                'Used when an attribute required for modules is missing.'),
118 
119      }
120 
121      options = (('required-attributes',
@@ -556,13 +577,15 @@
122 
123 
124  class NameChecker(_BasicChecker):
125      msgs = {
126      'C0102': ('Black listed name "%s"',
127 +              'blacklisted-name',
128                'Used when the name is listed in the black list (unauthorized \
129                names).'),
130      'C0103': ('Invalid name "%s" (should match %s)',
131 +              'invalid-name',
132                'Used when the name doesn\'t match the regular expression \
133                associated to its type (constant, variable, class...).'),
134 
135      }
136      options = (('module-rgx',
@@ -707,14 +730,16 @@
137 
138 
139  class DocStringChecker(_BasicChecker):
140      msgs = {
141      'C0111': ('Missing docstring', # W0131
142 +              'missing-docstring',
143                'Used when a module, function, class or method has no docstring.\
144                Some special methods like __init__ doesn\'t necessary require a \
145                docstring.'),
146      'C0112': ('Empty docstring', # W0132
147 +              'empty-docstring',
148                'Used when a module, function, class or method has an empty \
149                docstring (it would be too easy ;).'),
150      }
151      options = (('no-docstring-rgx',
152                  {'default' : NO_REQUIRED_DOC_RGX,
@@ -766,10 +791,11 @@
153 
154 
155  class PassChecker(_BasicChecker):
156      """check is the pass statement is really necessary"""
157      msgs = {'W0107': ('Unnecessary pass statement',
158 +                      'unnecessary-pass',
159                        'Used when a "pass" statement that can be avoided is '
160                        'encountered.)'),
161              }
162 
163      def visit_pass(self, node):
diff --git a/checkers/classes.py b/checkers/classes.py
@@ -36,91 +36,112 @@
164      return False
165 
166 
167  MSGS = {
168      'F0202': ('Unable to check methods signature (%s / %s)',
169 +              'method-check-failed',
170                'Used when PyLint has been unable to check methods signature \
171                compatibility for an unexpected reason. Please report this kind \
172                if you don\'t make sense of it.'),
173 
174      'E0202': ('An attribute affected in %s line %s hide this method',
175 +              'method-hidden',
176                'Used when a class defines a method which is hidden by an '
177                'instance attribute from an ancestor class or set by some '
178                'client code.'),
179      'E0203': ('Access to member %r before its definition line %s',
180 +              'access-member-before-definition',
181                'Used when an instance member is accessed before it\'s actually\
182                assigned.'),
183      'W0201': ('Attribute %r defined outside __init__',
184 +              'attribute-defined-outside-init',
185                'Used when an instance attribute is defined outside the __init__\
186                method.'),
187 
188      'W0212': ('Access to a protected member %s of a client class', # E0214
189 +              'protected-access',
190                'Used when a protected member (i.e. class member with a name \
191                beginning with an underscore) is access outside the class or a \
192                descendant of the class where it\'s defined.'),
193 
194      'E0211': ('Method has no argument',
195 +              'no-method-argument',
196                'Used when a method which should have the bound instance as \
197                first argument has no argument defined.'),
198      'E0213': ('Method should have "self" as first argument',
199 +              'no-self-argument',
200                'Used when a method has an attribute different the "self" as\
201                first argument. This is considered as an error since this is\
202                a so common convention that you shouldn\'t break it!'),
203      'C0202': ('Class method %s should have %s as first argument', # E0212
204 +              'bad-classmethod-argument',
205                'Used when a class method has a first argument named differently '
206                'than the value specified in valid-classmethod-first-arg option '
207                '(default to "cls"), recommended to easily differentiate them '
208                'from regular instance methods.'),
209      'C0203': ('Metaclass method %s should have %s as first argument', # E0214
210 +              'bad-mcs-method-argument',
211                'Used when a metaclass method has a first agument named '
212                'differently than the value specified in valid-classmethod-first'
213                '-arg option (default to "cls"), recommended to easily '
214                'differentiate them from regular instance methods.'),
215      'C0204': ('Metaclass class method %s should have %s as first argument',
216 +              'bad-mcs-classmethod-argument',
217                'Used when a metaclass class method has a first argument named '
218                'differently than the value specified in valid-metaclass-'
219                'classmethod-first-arg option (default to "mcs"), recommended to '
220                'easily differentiate them from regular instance methods.'),
221 
222      'W0211': ('Static method with %r as first argument',
223 +              'bad-staticmethod-argument',
224                'Used when a static method has "self" or a value specified in '
225                'valid-classmethod-first-arg option or '
226                'valid-metaclass-classmethod-first-arg option as first argument.'
227                ),
228      'R0201': ('Method could be a function',
229 +              'no-self-use',
230                'Used when a method doesn\'t use its bound instance, and so could\
231                be written as a function.'
232                ),
233 
234      'E0221': ('Interface resolved to %s is not a class',
235 +              'interface-is-not-class',
236                'Used when a class claims to implement an interface which is not \
237                a class.'),
238      'E0222': ('Missing method %r from %s interface',
239 +              'missing-interface-method',
240                'Used when a method declared in an interface is missing from a \
241                class implementing this interface'),
242      'W0221': ('Arguments number differs from %s method',
243 +              'arguments-differ',
244                'Used when a method has a different number of arguments than in \
245                the implemented interface or in an overridden method.'),
246      'W0222': ('Signature differs from %s method',
247 +              'signature-differs',
248                'Used when a method signature is different than in the \
249                implemented interface or in an overridden method.'),
250      'W0223': ('Method %r is abstract in class %r but is not overridden',
251 +              'abstract-method',
252                'Used when an abstract method (i.e. raise NotImplementedError) is \
253                not overridden in concrete class.'
254                ),
255      'F0220': ('failed to resolve interfaces implemented by %s (%s)', # W0224
256 +              'unresolved-interface',
257                'Used when a PyLint as failed to find interfaces implemented by \
258                 a class'),
259 
260 
261      'W0231': ('__init__ method from base class %r is not called',
262 +              'super-init-not-called',
263                'Used when an ancestor class method has an __init__ method \
264                which is not called by a derived class.'),
265      'W0232': ('Class has no __init__ method',
266 +              'no-init',
267                'Used when a class has no __init__ method, neither its parent \
268                classes.'),
269      'W0233': ('__init__ method from a non direct base class %r is called',
270 +              'non-parent-init-called',
271                'Used when an __init__ method is called on a class which is not \
272                in the direct ancestors for the analysed class.'),
273 
274      }
275 
diff --git a/checkers/design_analysis.py b/checkers/design_analysis.py
@@ -41,42 +41,54 @@
276      return False
277 
278 
279  MSGS = {
280      'R0901': ('Too many ancestors (%s/%s)',
281 +              'too-many-ancestors',
282                'Used when class has too many parent classes, try to reduce \
283                this to get a more simple (and so easier to use) class.'),
284      'R0902': ('Too many instance attributes (%s/%s)',
285 +              'too-many-instance-attributes',
286                'Used when class has too many instance attributes, try to reduce \
287                this to get a more simple (and so easier to use) class.'),
288      'R0903': ('Too few public methods (%s/%s)',
289 +              'too-few-public-methods',
290                'Used when class has too few public methods, so be sure it\'s \
291                really worth it.'),
292      'R0904': ('Too many public methods (%s/%s)',
293 +              'too-many-public-methods',
294                'Used when class has too many public methods, try to reduce \
295                this to get a more simple (and so easier to use) class.'),
296 
297      'R0911': ('Too many return statements (%s/%s)',
298 +              'too-many-return-statements',
299                'Used when a function or method has too many return statement, \
300                making it hard to follow.'),
301      'R0912': ('Too many branches (%s/%s)',
302 +              'too-many-branches',
303                'Used when a function or method has too many branches, \
304                making it hard to follow.'),
305      'R0913': ('Too many arguments (%s/%s)',
306 +              'too-many-arguments',
307                'Used when a function or method takes too many arguments.'),
308      'R0914': ('Too many local variables (%s/%s)',
309 +              'too-many-locals',
310                'Used when a function or method has too many local variables.'),
311      'R0915': ('Too many statements (%s/%s)',
312 +              'too-many-statements',
313                'Used when a function or method has too many statements. You \
314                should then split it in smaller functions / methods.'),
315 
316      'R0921': ('Abstract class not referenced',
317 +              'abstract-class-not-used',
318                'Used when an abstract class is not used as ancestor anywhere.'),
319      'R0922': ('Abstract class is only referenced %s times',
320 +              'abstract-class-little-used',
321                'Used when an abstract class is used less than X times as \
322                ancestor.'),
323      'R0923': ('Interface not implemented',
324 +              'interface-not-implemented',
325                'Used when an interface class is not implemented anywhere.'),
326      }
327 
328 
329  class MisdesignChecker(BaseChecker):
diff --git a/checkers/exceptions.py b/checkers/exceptions.py
@@ -27,40 +27,49 @@
330 
331 
332  OVERGENERAL_EXCEPTIONS = ('Exception',)
333 
334  MSGS = {
335 -    'E0701': (
336 -    'Bad except clauses order (%s)',
337 -    'Used when except clauses are not in the correct order (from the \
338 -    more specific to the more generic). If you don\'t fix the order, \
339 -    some exceptions may not be catched by the most specific handler.'),
340 +    'E0701': ('Bad except clauses order (%s)',
341 +              'bad-except-order',
342 +              'Used when except clauses are not in the correct order (from the '
343 +              'more specific to the more generic). If you don\'t fix the order, '
344 +              'some exceptions may not be catched by the most specific handler.'),
345      'E0702': ('Raising %s while only classes, instances or string are allowed',
346 +              'raising-bad-type',
347                'Used when something which is neither a class, an instance or a \
348                string is raised (i.e. a `TypeError` will be raised).'),
349      'E0710': ('Raising a new style class which doesn\'t inherit from BaseException',
350 +              'raising-non-exception',
351                'Used when a new style class which doesn\'t inherit from \
352                 BaseException is raised.'),
353      'E0711': ('NotImplemented raised - should raise NotImplementedError',
354 +              'notimplemented-raised',
355                'Used when NotImplemented is raised instead of \
356                NotImplementedError'),
357 
358      'W0701': ('Raising a string exception',
359 +              'raising-string',
360                'Used when a string exception is raised.'),
361      'W0702': ('No exception type(s) specified',
362 +              'bare-except',
363                'Used when an except clause doesn\'t specify exceptions type to \
364                catch.'),
365      'W0703': ('Catching too general exception %s',
366 +              'broad-except',
367                'Used when an except catches a too general exception, \
368                possibly burying unrelated errors.'),
369      'W0704': ('Except doesn\'t do anything',
370 +              'pointless-except',
371                'Used when an except clause does nothing but "pass" and there is\
372                no "else" clause.'),
373      'W0710': ('Exception doesn\'t inherit from standard "Exception" class',
374 +              'nonstandard-exception',
375                'Used when a custom exception class is raised but doesn\'t \
376                inherit from the builtin "Exception" class.'),
377      'W0711': ('Exception to catch is the result of a binary "%s" operation',
378 +              'binary-op-exception',
379                'Used when the exception to catch is of the form \
380                "except A or B:".  If intending to catch multiple, \
381                rewrite as "except (A, B):"'),
382      }
383 
diff --git a/checkers/format.py b/checkers/format.py
@@ -35,46 +35,58 @@
384  from pylint.checkers import BaseRawChecker
385  from pylint.checkers.utils import check_messages
386 
387  MSGS = {
388      'C0301': ('Line too long (%s/%s)',
389 +              'line-too-long',
390                'Used when a line is longer than a given number of characters.'),
391      'C0302': ('Too many lines in module (%s)', # was W0302
392 +              'too-many-lines',
393                'Used when a module has too much lines, reducing its readability.'
394                ),
395 
396      'W0311': ('Bad indentation. Found %s %s, expected %s',
397 +              'bad-indentation',
398                'Used when an unexpected number of indentation\'s tabulations or '
399                'spaces has been found.'),
400      'W0312': ('Found indentation with %ss instead of %ss',
401 +              'mixed-indentation',
402                'Used when there are some mixed tabs and spaces in a module.'),
403      'W0301': ('Unnecessary semicolon', # was W0106
404 +              'unnecessary-semicolon',
405                'Used when a statement is ended by a semi-colon (";"), which \
406                isn\'t necessary (that\'s python, not C ;).'),
407      'C0321': ('More than one statement on a single line',
408 +              'multiple-statements',
409                'Used when more than on statement are found on the same line.'),
410      'C0322': ('Operator not preceded by a space\n%s',
411 +              'no-space-before-operator',
412                'Used when one of the following operator (!= | <= | == | >= | < '
413                '| > | = | \\+= | -= | \\*= | /= | %) is not preceded by a space.'),
414      'C0323': ('Operator not followed by a space\n%s',
415 +              'no-space-after-operator',
416                'Used when one of the following operator (!= | <= | == | >= | < '
417                '| > | = | \\+= | -= | \\*= | /= | %) is not followed by a space.'),
418      'C0324': ('Comma not followed by a space\n%s',
419 +              'no-space-after-comma',
420                'Used when a comma (",") is not followed by a space.'),
421      }
422 
423  if sys.version_info < (3, 0):
424 
425      MSGS.update({
426      'W0331': ('Use of the <> operator',
427 +              'old-ne-operator',
428                'Used when the deprecated "<>" operator is used instead \
429                of "!=".'),
430      'W0332': ('Use of "l" as long integer identifier',
431 +              'lowercase-l-suffix',
432                'Used when a lower case "l" is used to mark a long integer. You '
433                'should use a upper case "L" since the letter "l" looks too much '
434                'like the digit "1"'),
435      'W0333': ('Use of the `` operator',
436 +              'backtick',
437                'Used when the deprecated "``" (backtick) operator is used '
438                'instead  of the str() function.'),
439      })
440 
441  # simple quoted string rgx
@@ -363,14 +375,16 @@
442      """Check string literals"""
443 
444      msgs = {
445          'W1401': ('Anomalous backslash in string: \'%s\'. '
446                    'String constant might be missing an r prefix.',
447 +                  'anomalous-backslash-in-string',
448                    'Used when a backslash is in a literal string but not as an '
449                    'escape.'),
450          'W1402': ('Anomalous Unicode escape in byte string: \'%s\'. '
451                    'String constant might be missing an r or u prefix.',
452 +                  'anomalous-unicode-escape-in-string',
453                    'Used when an escape like \\u is encountered in a byte '
454                    'string where it has no effect.'),
455          }
456      name = 'string_constant'
457      __implements__ = (IRawChecker, IASTNGChecker)
diff --git a/checkers/imports.py b/checkers/imports.py
@@ -126,28 +126,36 @@
458 
459  # the import checker itself ###################################################
460 
461  MSGS = {
462      'F0401': ('Unable to import %s',
463 +              'import-error',
464                'Used when pylint has been unable to import a module.'),
465      'R0401': ('Cyclic import (%s)',
466 +              'cyclic-import',
467                'Used when a cyclic import between two or more modules is \
468                detected.'),
469 
470      'W0401': ('Wildcard import %s',
471 +              'wildcard-import',
472                'Used when `from module import *` is detected.'),
473      'W0402': ('Uses of a deprecated module %r',
474 +              'deprecated-module',
475                'Used a module marked as deprecated is imported.'),
476      'W0403': ('Relative import %r, should be %r',
477 +              'relative-import',
478                'Used when an import relative to the package directory is \
479                detected.'),
480      'W0404': ('Reimport %r (imported line %s)',
481 +              'reimported',
482                'Used when a module is reimported multiple times.'),
483      'W0406': ('Module import itself',
484 +              'import-self',
485                'Used when a module is importing itself.'),
486 
487      'W0410': ('__future__ import is not the first non docstring statement',
488 +              'misplaced-future',
489                'Python 2.5 and greater require __future__ import to be the \
490                first non docstring statement in the module.'),
491      }
492 
493  class ImportsChecker(BaseChecker):
diff --git a/checkers/logging.py b/checkers/logging.py
@@ -20,28 +20,33 @@
494  from pylint.checkers import utils
495 
496 
497  MSGS = {
498      'W1201': ('Specify string format arguments as logging function parameters',
499 +             'logging-not-lazy',
500               'Used when a logging statement has a call form of '
501               '"logging.<logging method>(format_string % (format_args...))". '
502               'Such calls should leave string interpolation to the logging '
503               'method itself and be written '
504               '"logging.<logging method>(format_string, format_args...)" '
505               'so that the program may avoid incurring the cost of the '
506               'interpolation in those cases in which no message will be '
507               'logged. For more, see '
508               'http://www.python.org/dev/peps/pep-0282/.'),
509      'E1200': ('Unsupported logging format character %r (%#02x) at index %d',
510 +              'logging-unsupported-format',
511                'Used when an unsupported format character is used in a logging\
512                statement format string.'),
513      'E1201': ('Logging format string ends in middle of conversion specifier',
514 +              'logging-format-truncated',
515                'Used when a logging statement format string terminates before\
516                the end of a conversion specifier.'),
517      'E1205': ('Too many arguments for logging format string',
518 +              'logging-too-many-args',
519                'Used when a logging format string is given too few arguments.'),
520      'E1206': ('Not enough arguments for logging format string',
521 +              'logging-too-few-args',
522                'Used when a logging format string is given too many arguments'),
523      }
524 
525 
526  CHECKED_CONVENIENCE_FUNCTIONS = set([
diff --git a/checkers/misc.py b/checkers/misc.py
@@ -23,10 +23,11 @@
527  from pylint.checkers import BaseChecker
528 
529 
530  MSGS = {
531      'W0511': ('%s',
532 +              'fixme',
533                'Used when a warning note as FIXME or XXX is detected.'),
534      }
535 
536  class EncodingChecker(BaseChecker):
537      """checks for:
diff --git a/checkers/newstyle.py b/checkers/newstyle.py
@@ -22,17 +22,21 @@
538  from pylint.checkers import BaseChecker
539  from pylint.checkers.utils import check_messages
540 
541  MSGS = {
542      'E1001': ('Use of __slots__ on an old style class',
543 +              'slots-on-old-class',
544                'Used when an old style class uses the __slots__ attribute.'),
545      'E1002': ('Use of super on an old style class',
546 +              'super-on-old-class',
547                'Used when an old style class uses the super builtin.'),
548      'E1003': ('Bad first argument %r given to super class',
549 +              'bad-super-call',
550                'Used when another argument than the current class is given as \
551                first argument of the super builtin.'),
552      'W1001': ('Use of "property" on an old style class',
553 +              'property-on-old-class',
554                'Used when PyLint detect the use of the builtin "property" \
555                on an old style class while this is relying on new style \
556                classes features'),
557      }
558 
diff --git a/checkers/similar.py b/checkers/similar.py
@@ -195,10 +195,11 @@
559                  index.setdefault(line, []).append( line_no )
560          return index
561 
562 
563  MSGS = {'R0801': ('Similar lines in %s files\n%s',
564 +                  'duplicate-code',
565                    'Indicates that a set of similar lines has been detected \
566                    among multiple file. This usually means that the code should \
567                    be refactored to avoid this duplication.')}
568 
569  def report_similarities(sect, stats, old_stats):
diff --git a/checkers/string_format.py b/checkers/string_format.py
@@ -22,38 +22,47 @@
570  from pylint.checkers import utils
571 
572 
573  MSGS = {
574      'E1300': ("Unsupported format character %r (%#02x) at index %d",
575 +              "bad-format-character",
576                "Used when a unsupported format character is used in a format\
577                string."),
578      'E1301': ("Format string ends in middle of conversion specifier",
579 +              "truncated-format-string",
580                "Used when a format string terminates before the end of a \
581                conversion specifier."),
582      'E1302': ("Mixing named and unnamed conversion specifiers in format string",
583 +              "mixed-format-string",
584                "Used when a format string contains both named (e.g. '%(foo)d') \
585                and unnamed (e.g. '%d') conversion specifiers.  This is also \
586                used when a named conversion specifier contains * for the \
587                minimum field width and/or precision."),
588      'E1303': ("Expected mapping for format string, not %s",
589 +              "format-needs-mapping",
590                "Used when a format string that uses named conversion specifiers \
591                is used with an argument that is not a mapping."),
592      'W1300': ("Format string dictionary key should be a string, not %s",
593 +              "bad-format-string-key",
594                "Used when a format string that uses named conversion specifiers \
595                is used with a dictionary whose keys are not all strings."),
596      'W1301': ("Unused key %r in format string dictionary",
597 +              "unused-format-string-key",
598                "Used when a format string that uses named conversion specifiers \
599                is used with a dictionary that conWtains keys not required by the \
600                format string."),
601      'E1304': ("Missing key %r in format string dictionary",
602 +              "missing-format-string-key",
603                "Used when a format string that uses named conversion specifiers \
604                is used with a dictionary that doesn't contain all the keys \
605                required by the format string."),
606      'E1305': ("Too many arguments for format string",
607 +              "too-many-format-args",
608                "Used when a format string that uses unnamed conversion \
609                specifiers is given too few arguments."),
610      'E1306': ("Not enough arguments for format string",
611 +              "too-few-format-args",
612                "Used when a format string that uses unnamed conversion \
613                specifiers is given too many arguments"),
614      }
615 
616  OTHER_NODES = (astng.Const, astng.List, astng.Backquote,
diff --git a/checkers/typecheck.py b/checkers/typecheck.py
@@ -26,37 +26,47 @@
617  from pylint.checkers import BaseChecker
618  from pylint.checkers.utils import safe_infer, is_super, check_messages
619 
620  MSGS = {
621      'E1101': ('%s %r has no %r member',
622 +              'no-member',
623                'Used when a variable is accessed for an unexistent member.'),
624      'E1102': ('%s is not callable',
625 +              'not-callable',
626                'Used when an object being called has been inferred to a non \
627                callable object'),
628      'E1103': ('%s %r has no %r member (but some types could not be inferred)',
629 +              'maybe-no-member',
630                'Used when a variable is accessed for an unexistent member, but \
631                astng was not able to interpret all possible types of this \
632                variable.'),
633      'E1111': ('Assigning to function call which doesn\'t return',
634 +              'assignment-from-no-return',
635                'Used when an assignment is done on a function call but the \
636                inferred function doesn\'t return anything.'),
637      'W1111': ('Assigning to function call which only returns None',
638 +              'assignment-from-none',
639                'Used when an assignment is done on a function call but the \
640                inferred function returns nothing but None.'),
641 
642      'E1120': ('No value passed for parameter %s in function call',
643 +              'no-value-for-parameter',
644                'Used when a function call passes too few arguments.'),
645      'E1121': ('Too many positional arguments for function call',
646 +              'too-many-function-args',
647                'Used when a function call passes too many positional \
648                arguments.'),
649      'E1122': ('Duplicate keyword argument %r in function call',
650 +              'duplicate-keyword-arg',
651                'Used when a function call passes the same keyword argument \
652                multiple times.'),
653      'E1123': ('Passing unexpected keyword argument %r in function call',
654 +              'unexpected-keyword-arg',
655                'Used when a function call passes a keyword argument that \
656                doesn\'t correspond to one of the function\'s parameter names.'),
657      'E1124': ('Multiple values passed for parameter %r in function call',
658 +              'redundant-keyword-arg',
659                'Used when a function call would result in assigning multiple \
660                values to a function parameter, one value from a positional \
661                argument and one from a keyword argument.'),
662      }
663 
diff --git a/checkers/variables.py b/checkers/variables.py
@@ -52,52 +52,68 @@
664      return None
665 
666 
667  MSGS = {
668      'E0601': ('Using variable %r before assignment',
669 +              'used-before-assignment',
670                'Used when a local variable is accessed before it\'s \
671                assignment.'),
672      'E0602': ('Undefined variable %r',
673 +              'undefined-variable',
674                'Used when an undefined variable is accessed.'),
675      'E0603': ('Undefined variable name %r in __all__',
676 +              'undefined-all-variable',
677                'Used when an undefined variable name is referenced in __all__.'),
678      'E0611': ('No name %r in module %r',
679 +              'no-name-in-module',
680                'Used when a name cannot be found in a module.'),
681 
682      'W0601': ('Global variable %r undefined at the module level',
683 +              'global-variable-undefined',
684                'Used when a variable is defined through the "global" statement \
685                but the variable is not defined in the module scope.'),
686      'W0602': ('Using global for %r but no assignment is done',
687 +              'global-variable-not-assigned',
688                'Used when a variable is defined through the "global" statement \
689                but no assignment to this variable is done.'),
690      'W0603': ('Using the global statement', # W0121
691 +              'global-statement',
692                'Used when you use the "global" statement to update a global \
693                variable. PyLint just try to discourage this \
694                usage. That doesn\'t mean you can not use it !'),
695      'W0604': ('Using the global statement at the module level', # W0103
696 +              'global-at-module-level',
697                'Used when you use the "global" statement at the module level \
698                since it has no effect'),
699      'W0611': ('Unused import %s',
700 +              'unused-import',
701                'Used when an imported module or variable is not used.'),
702      'W0612': ('Unused variable %r',
703 +              'unused-variable',
704                'Used when a variable is defined but not used.'),
705      'W0613': ('Unused argument %r',
706 +              'unused-argument',
707                'Used when a function or method argument is not used.'),
708      'W0614': ('Unused import %s from wildcard import',
709 +              'unused-wildcard-import',
710                'Used when an imported module or variable is not used from a \
711                \'from X import *\' style import.'),
712 
713      'W0621': ('Redefining name %r from outer scope (line %s)',
714 +              'redefined-outer-name',
715                'Used when a variable\'s name hide a name defined in the outer \
716                scope.'),
717      'W0622': ('Redefining built-in %r',
718 +              'redefined-builtin',
719                'Used when a variable or function override a built-in.'),
720      'W0623': ('Redefining name %r from %s in exception handler',
721 +              'redefine-in-handler',
722                'Used when an exception handler assigns the exception \
723                 to an existing name'),
724 
725      'W0631': ('Using possibly undefined loop variable %r',
726 +              'undefined-loop-variable',
727                'Used when an loop variable (i.e. defined by a for loop or \
728                a list comprehension or a generator expression) is used outside \
729                the loop.'),
730      }
731 
diff --git a/doc/FAQ.txt b/doc/FAQ.txt
@@ -241,10 +241,19 @@
732 
733      disable= W0401, # because I do not want it
734       E0202, # I have a good reason, trust me
735       C0302  # that's it
736 
737 +4.6 Do I have to remember all these numbers?
738 +--------------------------------------------
739 +
740 +No, starting from 0.25.3, you can use symbolic names for messages::
741 +
742 +    # pylint: disable=fixme, line-too-long
743 +
744 +You can show these symbols in the output with the `-sy` option.
745 +
746  5. Classes and Inheritance
747  ==========================
748 
749  5.1 When is pylint considering a class as an interface?
750  -------------------------------------------------------
diff --git a/doc/manual.txt b/doc/manual.txt
@@ -311,10 +311,12 @@
751  --comment=y_or_n        Add a comment according to your evaluation note.
752  --parseable=y_or_n      Use a parseable output format.
753  --html=y_or_n           Use HTML as output format instead of text.
754  --list-msgs             Generate pylint's messages.
755  --full-documentation    Generate pylint's full documentation, in reST format.
756 +--include_ids=y_or_n    Show numeric ids of messages (like 'C0301')
757 +--symbols=y_or_n        Show symbolic ids of messsages (like 'line-too-long')
758 
759  .. _features: features.html
760 
761  Daily pylint usage
762  ------------------
diff --git a/lint.py b/lint.py
@@ -82,51 +82,63 @@
763 
764  # Python Linter class #########################################################
765 
766  MSGS = {
767      'F0001': ('%s',
768 +              'fatal',
769                'Used when an error occurred preventing the analysis of a \
770                module (unable to find it for instance).'),
771      'F0002': ('%s: %s',
772 +              'astng-error',
773                'Used when an unexpected error occurred while building the ASTNG \
774                representation. This is usually accompanied by a traceback. \
775                Please report such errors !'),
776      'F0003': ('ignored builtin module %s',
777 +              'ignored-builtin-module',
778                'Used to indicate that the user asked to analyze a builtin module\
779                which has been skipped.'),
780      'F0004': ('unexpected inferred value %s',
781 +              'unexpected-inferred-value',
782                'Used to indicate that some value of an unexpected type has been \
783                inferred.'),
784      'F0010': ('error while code parsing: %s',
785 +              'parse-error',
786                'Used when an exception occured while building the ASTNG \
787                 representation which could be handled by astng.'),
788 
789 -
790      'I0001': ('Unable to run raw checkers on built-in module %s',
791 +              'raw-checker-failed',
792                'Used to inform that a built-in module has not been checked \
793                using the raw checkers.'),
794 
795      'I0010': ('Unable to consider inline option %r',
796 +              'bad-inline-option',
797                'Used when an inline option is either badly formatted or can\'t \
798                be used inside modules.'),
799 
800      'I0011': ('Locally disabling %s',
801 +              'locally-disabled',
802                'Used when an inline option disables a message or a messages \
803                category.'),
804      'I0012': ('Locally enabling %s',
805 +              'locally-enabled',
806                'Used when an inline option enables a message or a messages \
807                category.'),
808      'I0013': ('Ignoring entire file',
809 +              'file-ignored',
810                'Used to inform that the file will not be checked'),
811 
812 
813      'E0001': ('%s',
814 +              'syntax-error',
815                'Used when a syntax error is raised for a module.'),
816 
817      'E0011': ('Unrecognized file option %r',
818 +              'unrecognized-inline-option',
819                'Used when an unknown inline option is encountered.'),
820      'E0012': ('Bad option value %r',
821 +              'bad-option-value',
822                'Used when a bad value for an inline option is encountered.'),
823      }
824 
825 
826  class PyLinter(OptionsManagerMixIn, MessagesHandlerMixIn, ReportsHandlerMixIn,
@@ -182,10 +194,16 @@
827                   {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0,
828                    'short': 'i',
829                    'group': 'Reports',
830                    'help' : 'Include message\'s id in output'}),
831 
832 +                ('symbols',
833 +                 {'type' : 'yn', 'metavar' : '<y_or_n>', 'default' : 0,
834 +                  'short': 's',
835 +                  'group': 'Reports',
836 +                  'help' : 'Include symbolic ids of messages in output'}),
837 +
838                  ('files-output',
839                   {'default': 0, 'type' : 'yn', 'metavar' : '<y_or_n>',
840                    'group': 'Reports', 'level': 1,
841                    'help' : 'Put messages in a separate file for each module / \
842  package specified on the command line instead of printing them on stdout. \
@@ -481,10 +499,11 @@
843      def check(self, files_or_modules):
844          """main checking entry: check a list of files or modules from their
845          name.
846          """
847          self.reporter.include_ids = self.config.include_ids
848 +        self.reporter.symbols = self.config.symbols
849          if not isinstance(files_or_modules, (list, tuple)):
850              files_or_modules = (files_or_modules,)
851          walker = PyLintASTWalker(self)
852          checkers = self.prepare_checkers()
853          rawcheckers = [c for c in checkers if implements(c, IRawChecker)
diff --git a/reporters/__init__.py b/reporters/__init__.py
@@ -29,22 +29,42 @@
854 
855  class EmptyReport(Exception):
856      """raised when a report is empty and so should not be displayed"""
857 
858  class BaseReporter:
859 -    """base class for reporters"""
860 +    """base class for reporters
861 +
862 +    symbols: show short symbolic names for messages.
863 +    """
864 
865      extension = ''
866 
867      def __init__(self, output=None):
868          self.linter = None
869          self.include_ids = None
870 +        self.symbols = None
871          self.section = 0
872          self.out = None
873          self.out_encoding = None
874          self.set_output(output)
875 
876 +    def make_sigle(self, msg_id):
877 +        """generate a short prefix for a message.
878 +
879 +        The sigle can include the id, the symbol, or both, or it can just be
880 +        the message class.
881 +        """
882 +        if self.include_ids:
883 +            sigle = msg_id
884 +        else:
885 +            sigle = msg_id[0]
886 +        if self.symbols:
887 +            symbol = self.linter.check_message_id(msg_id).symbol
888 +            if symbol:
889 +                sigle += '(%s)' % symbol
890 +        return sigle
891 +
892      def set_output(self, output=None):
893          """set output stream"""
894          self.out = output or sys.stdout
895          # py3k streams handle their encoding :
896          if sys.version_info >= (3, 0):
diff --git a/reporters/guireporter.py b/reporters/guireporter.py
@@ -20,15 +20,11 @@
897          self.gui = gui
898 
899      def add_message(self, msg_id, location, msg):
900          """manage message of different type and in the context of path"""
901          module, obj, line, col_offset = location[1:]
902 -        if self.include_ids:
903 -            sigle = msg_id
904 -        else:
905 -            sigle = msg_id[0]
906 -
907 +        sigle = self.make_sigle(msg_id)
908          full_msg = [sigle, module, obj, str(line), msg]
909          self.msgs += [[sigle, module, obj, str(line)]]
910          self.gui.msg_queue.put(full_msg)
911 
912      def _display(self, layout):
diff --git a/reporters/html.py b/reporters/html.py
@@ -34,14 +34,11 @@
913          self.msgs = []
914 
915      def add_message(self, msg_id, location, msg):
916          """manage message of different type and in the context of path"""
917          module, obj, line, col_offset = location[1:]
918 -        if self.include_ids:
919 -            sigle = msg_id
920 -        else:
921 -            sigle = msg_id[0]
922 +        sigle = self.make_sigle(msg_id)
923          self.msgs += [sigle, module, obj, str(line), str(col_offset), escape(msg)]
924 
925      def set_output(self, output=None):
926          """set output stream
927 
diff --git a/reporters/text.py b/reporters/text.py
@@ -54,14 +54,11 @@
928                  self._modules[module] = 1
929              else:
930                  self.writeln('************* %s' % module)
931          if obj:
932              obj = ':%s' % obj
933 -        if self.include_ids:
934 -            sigle = msg_id
935 -        else:
936 -            sigle = msg_id[0]
937 +        sigle = self.make_sigle(msg_id)
938          self.writeln('%s:%3s,%s%s: %s' % (sigle, line, col_offset, obj, msg))
939 
940      def _display(self, layout):
941          """launch layouts display"""
942          print >> self.out
@@ -86,14 +83,11 @@
943      def add_message(self, msg_id, location, msg):
944          """manage message of different type and in the context of path"""
945          path, _, obj, line, _ = location
946          if obj:
947              obj = ', %s' % obj
948 -        if self.include_ids:
949 -            sigle = msg_id
950 -        else:
951 -            sigle = msg_id[0]
952 +        sigle = self.make_sigle(msg_id)
953          if self._prefix:
954              path = path.replace(self._prefix, '')
955          self.writeln(self.line_format % locals())
956 
957  class VSTextReporter(ParseableTextReporter):
@@ -143,13 +137,10 @@
958                                         color, style)
959              self.writeln(modsep)
960              self._modules[module] = 1
961          if obj:
962              obj = ':%s' % obj
963 -        if self.include_ids:
964 -            sigle = msg_id
965 -        else:
966 -            sigle = msg_id[0]
967 +        sigle = self.make_sigle(msg_id)
968          color, style = self._get_decoration(sigle)
969          msg = colorize_ansi(msg, color, style)
970          sigle = colorize_ansi(sigle, color, style)
971          self.writeln('%s:%3s%s: %s' % (sigle, line, obj, msg))
diff --git a/test/input/func_docstring.py b/test/input/func_docstring.py
@@ -18,31 +18,34 @@
972      # missing docstring
973 
974  ##     class BBBB:
975  ##         # missing docstring
976  ##         pass
977 -    
978 +
979  ##     class CCCC:
980  ##         """yeah !"""
981  ##         def method1(self):
982  ##             pass
983 
984  ##         def method2(self):
985  ##             """ yeah !"""
986  ##             pass
987 -    
988 +
989      def method1(self):
990          pass
991 -    
992 +
993      def method2(self):
994          """ yeah !"""
995          pass
996 
997      def __init__(self):
998          pass
999 -    
1000 +
1001  class DDDD(AAAA):
1002      """yeah !"""
1003 
1004      def __init__(self):
1005          AAAA.__init__(self)
1006 - 
1007 +
1008 +# pylint: disable=missing-docstring
1009 +def function4():
1010 +    pass
diff --git a/test/unittest_lint.py b/test/unittest_lint.py
@@ -175,10 +175,40 @@
1011          self.assert_(not linter.is_message_enabled('E1101', 70))
1012          self.assert_(linter.is_message_enabled('E1101', 72))
1013          self.assert_(linter.is_message_enabled('E1101', 75))
1014          self.assert_(linter.is_message_enabled('E1101', 77))
1015 
1016 +    def test_enable_by_symbol(self):
1017 +        """messages can be controlled by symbolic names.
1018 +
1019 +        The state is consistent across symbols and numbers.
1020 +        """
1021 +        linter = self.linter
1022 +        linter.open()
1023 +        linter.set_current_module('toto')
1024 +        self.assertTrue(linter.is_message_enabled('W0101'))
1025 +        self.assertTrue(linter.is_message_enabled('unreachable'))
1026 +        self.assertTrue(linter.is_message_enabled('W0102'))
1027 +        self.assertTrue(linter.is_message_enabled('dangerous-default-value'))
1028 +        linter.disable('unreachable', scope='package')
1029 +        linter.disable('dangerous-default-value', scope='module', line=1)
1030 +        self.assertFalse(linter.is_message_enabled('W0101'))
1031 +        self.assertFalse(linter.is_message_enabled('unreachable'))
1032 +        self.assertFalse(linter.is_message_enabled('W0102', 1))
1033 +        self.assertFalse(linter.is_message_enabled('dangerous-default-value', 1))
1034 +        linter.set_current_module('tutu')
1035 +        self.assertFalse(linter.is_message_enabled('W0101'))
1036 +        self.assertFalse(linter.is_message_enabled('unreachable'))
1037 +        self.assertTrue(linter.is_message_enabled('W0102'))
1038 +        self.assertTrue(linter.is_message_enabled('dangerous-default-value'))
1039 +        linter.enable('unreachable', scope='package')
1040 +        linter.enable('dangerous-default-value', scope='module', line=1)
1041 +        self.assertTrue(linter.is_message_enabled('W0101'))
1042 +        self.assertTrue(linter.is_message_enabled('unreachable'))
1043 +        self.assertTrue(linter.is_message_enabled('W0102', 1))
1044 +        self.assertTrue(linter.is_message_enabled('dangerous-default-value', 1))
1045 +
1046      def test_list_messages(self):
1047          sys.stdout = StringIO()
1048          try:
1049              # just invoke it, don't check the output
1050              self.linter.list_messages()
diff --git a/utils.py b/utils.py
@@ -17,10 +17,11 @@
1051  """some various utilities and helper classes, most of them used in the
1052  main pylint class
1053  """
1054 
1055  import sys
1056 +from warnings import warn
1057  from os import linesep
1058  from os.path import dirname, basename, splitext, exists, isdir, join, normpath
1059 
1060  from logilab.common.modutils import modpath_from_file, get_module_files, \
1061                                      file_from_modpath
@@ -91,27 +92,30 @@
1062          return id
1063      return MSG_TYPES_LONG.get(id)
1064 
1065 
1066  class Message:
1067 -    def __init__(self, checker, msgid, msg, descr):
1068 +    def __init__(self, checker, msgid, msg, descr, symbol):
1069          assert len(msgid) == 5, 'Invalid message id %s' % msgid
1070          assert msgid[0] in MSG_TYPES, \
1071                 'Bad message type %s in %r' % (msgid[0], msgid)
1072          self.msgid = msgid
1073          self.msg = msg
1074          self.descr = descr
1075          self.checker = checker
1076 +        self.symbol = symbol
1077 
1078  class MessagesHandlerMixIn:
1079      """a mix-in class containing all the messages related methods for the main
1080      lint class
1081      """
1082 
1083      def __init__(self):
1084          # dictionary of registered messages
1085          self._messages = {}
1086 +        # dictionary from string symbolic id to Message object.
1087 +        self._messages_by_symbol = {}
1088          self._msgs_state = {}
1089          self._module_msgs_state = {} # None
1090          self._msgs_by_category = {}
1091          self.msg_status = 0
1092 
@@ -124,32 +128,49 @@
1093          message ids should be a string of len 4, where the two first characters
1094          are the checker id and the two last the message id in this checker
1095          """
1096          msgs_dict = checker.msgs
1097          chkid = None
1098 -        for msgid, (msg, msgdescr) in msgs_dict.iteritems():
1099 +        for msgid, msg_tuple in msgs_dict.iteritems():
1100 +            if len(msg_tuple) == 3:
1101 +                (msg, msgsymbol, msgdescr) = msg_tuple
1102 +                assert msgsymbol not in self._messages_by_symbol, \
1103 +                    'Message symbol %r is already defined' % msgsymbol
1104 +            else:
1105 +                # messages should have a symbol, but for backward compatibility
1106 +                # they may not.
1107 +                (msg, msgdescr) = msg_tuple
1108 +                warn("[pylint 0.26] description of message %s doesn't include "
1109 +                     "a symbolic name" % msgid, DeprecationWarning)
1110 +                msgsymbol = None
1111              # avoid duplicate / malformed ids
1112              assert msgid not in self._messages, \
1113                     'Message id %r is already defined' % msgid
1114              assert chkid is None or chkid == msgid[1:3], \
1115                     'Inconsistent checker part in message id %r' % msgid
1116              chkid = msgid[1:3]
1117 -            self._messages[msgid] = Message(checker, msgid, msg, msgdescr)
1118 +            msg = Message(checker, msgid, msg, msgdescr, msgsymbol)
1119 +            self._messages[msgid] = msg
1120 +            self._messages_by_symbol[msgsymbol] = msg
1121              self._msgs_by_category.setdefault(msgid[0], []).append(msgid)
1122 
1123      def get_message_help(self, msgid, checkerref=False):
1124          """return the help string for the given message id"""
1125          msg = self.check_message_id(msgid)
1126          desc = normalize_text(' '.join(msg.descr.split()), indent='  ')
1127          if checkerref:
1128              desc += ' This message belongs to the %s checker.' % \
1129                     msg.checker.name
1130          title = msg.msg
1131 +        if msg.symbol:
1132 +            symbol_part = ' (%s)' % msg.symbol
1133 +        else:
1134 +            symbol_part = ''
1135          if title != '%s':
1136              title = title.splitlines()[0]
1137 -            return ':%s: *%s*\n%s' % (msg.msgid, title, desc)
1138 -        return ':%s:\n%s' % (msg.msgid, desc)
1139 +            return ':%s%s: *%s*\n%s' % (msg.msgid, symbol_part, title, desc)
1140 +        return ':%s%s:\n%s' % (msg.msgid, symbol_part, desc)
1141 
1142      def disable(self, msgid, scope='package', line=None):
1143          """don't output message of the given id"""
1144          assert scope in ('package', 'module')
1145          # msgid is a category?
@@ -166,11 +187,11 @@
1146              return
1147          # msgid is report id?
1148          if msgid.lower().startswith('rp'):
1149              self.disable_report(msgid)
1150              return
1151 -        # msgid is a msgid.
1152 +        # msgid is a symbolic or numeric msgid.
1153          msg = self.check_message_id(msgid)
1154          if scope == 'module':
1155              assert line > 0
1156              try:
1157                  self._module_msgs_state[msg.msgid][line] = False
@@ -203,11 +224,11 @@
1158              return
1159          # msgid is report id?
1160          if msgid.lower().startswith('rp'):
1161              self.enable_report(msgid)
1162              return
1163 -        # msgid is a msgid.
1164 +        # msgid is a symbolic or numeric msgid.
1165          msg = self.check_message_id(msgid)
1166          if scope == 'module':
1167              assert line > 0
1168              try:
1169                  self._module_msgs_state[msg.msgid][line] = True
@@ -219,21 +240,32 @@
1170              msgs[msg.msgid] = True
1171              # sync configuration object
1172              self.config.enable = [mid for mid, val in msgs.iteritems() if val]
1173 
1174      def check_message_id(self, msgid):
1175 -        """raise UnknownMessage if the message id is not defined"""
1176 +        """returns the Message object for this message.
1177 +
1178 +        msgid may be either a numeric or symbolic id.
1179 +
1180 +        Raises UnknownMessage if the message id is not defined.
1181 +        """
1182 +        if msgid in self._messages_by_symbol:
1183 +            return self._messages_by_symbol[msgid]
1184          msgid = msgid.upper()
1185          try:
1186              return self._messages[msgid]
1187          except KeyError:
1188              raise UnknownMessage('No such message id %s' % msgid)
1189 
1190      def is_message_enabled(self, msgid, line=None):
1191          """return true if the message associated to the given message id is
1192          enabled
1193 +
1194 +        msgid may be either a numeric or symbolic message id.
1195          """
1196 +        if msgid in self._messages_by_symbol:
1197 +            msgid = self._messages_by_symbol[msgid].msgid
1198          if line is None:
1199              return self._msgs_state.get(msgid, True)
1200          try:
1201              return self._module_msgs_state[msgid][line]
1202          except (KeyError, TypeError):
@@ -523,6 +555,5 @@
1203          # recurse on children
1204          for child in astng.get_children():
1205              self.walk(child)
1206          for cb in self.leave_events.get(cid, ()):
1207              cb(astng)
1208 -