Add test and code for handling __all__ with pylint. Closes #4685.

authorFELD Boris <lothiraldan@gmail.com>
changeset4d59dfcc0acc
branchdefault
phasepublic
hiddenno
parent revision#4b75e06211ab Improve checking of metaclass methods's first arg. Closes #4014.
child revision#1a27a3d9ea5e Fix false positive W0231 for missing call to object.__init__. Closes #103656
files modified by this revision
ChangeLog
checkers/variables.py
test/input/func_all.py
test/messages/func_all.txt
# HG changeset patch
# User FELD Boris <lothiraldan@gmail.com>
# Date 1348058345 -7200
# Wed Sep 19 14:39:05 2012 +0200
# Node ID 4d59dfcc0acc66356d430cbedcde4eab60be9555
# Parent 4b75e06211ab7e321e28b16d5b6309ee85c74a4b
Add test and code for handling __all__ with pylint. Closes #4685.

diff --git a/ChangeLog b/ChangeLog
@@ -3,16 +3,19 @@
1 
2  --
3      * #100707: check for boolop being used as exception class, introducing
4        new W0711 message (patch by Tim Hatch)
5 
6 +    * #4014: improve checking of metaclass methods first args, introducing
7 +      new C0204 message (patch by lothiraldan@gmail.com finalized by sthenault)
8 +
9 +    * #4685: check for consistency of a module's __all__ variable,
10 +      introducing new E0603 message
11 +
12      * #100654: fix grammatical error for W0332 message (using 'l' as
13        long int identifier)
14 
15 -    * #4014: Improve checking of metaclass methods first args, introducing
16 -      new C0204 message (patch by lothiraldan@gmail.com finalized by sthenault)
17 -
18      * fix cross-interpreter issue (non compatible access to __builtins__)
19 
20      * stop including tests files in distribution, they causes crash when
21        installed with python3 (#72022, #82417, #76910)
22 
diff --git a/checkers/variables.py b/checkers/variables.py
@@ -1,6 +1,6 @@
23 -# Copyright (c) 2003-2011 LOGILAB S.A. (Paris, FRANCE).
24 +# Copyright (c) 2003-2012 LOGILAB S.A. (Paris, FRANCE).
25  # http://www.logilab.fr/ -- mailto:contact@logilab.fr
26  #
27  # This program is free software; you can redistribute it and/or modify it under
28  # the terms of the GNU General Public License as published by the Free Software
29  # Foundation; either version 2 of the License, or (at your option) any later
@@ -56,10 +56,12 @@
30      'E0601': ('Using variable %r before assignment',
31                'Used when a local variable is accessed before it\'s \
32                assignment.'),
33      'E0602': ('Undefined variable %r',
34                'Used when an undefined variable is accessed.'),
35 +    'E0603': ('Undefined variable name %r in __all__',
36 +              'Used when an undefined variable name is referenced in __all__.'),
37      'E0611': ('No name %r in module %r',
38                'Used when a name cannot be found in a module.'),
39 
40      'W0601': ('Global variable %r undefined at the module level',
41                'Used when a variable is defined through the "global" statement \
@@ -103,10 +105,11 @@
42      """checks for
43      * unused variables / imports
44      * undefined variables
45      * redefinition of variable from builtins or from an outer scope
46      * use of variable before assignment
47 +    * __all__ consistency
48      """
49 
50      __implements__ = IASTNGChecker
51 
52      name = 'variables'
@@ -150,10 +153,21 @@
53      def leave_module(self, node):
54          """leave module: check globals
55          """
56          assert len(self._to_consume) == 1
57          not_consumed = self._to_consume.pop()[0]
58 +        # attempt to check for __all__ if defined
59 +        if '__all__' in node.locals:
60 +            assigned = node.igetattr('__all__').next()
61 +            for elt in getattr(assigned, 'elts', ()):
62 +                elt_name = elt.value
63 +                # If elt is in not_consumed, remove it from not_consumed
64 +                if elt_name in not_consumed:
65 +                    del not_consumed[elt_name]
66 +                    continue
67 +                if elt_name not in node.locals:
68 +                    self.add_message('E0603', args=elt_name, node=elt)
69          # don't check unused imports in __init__ files
70          if not self.config.init_import and node.package:
71              return
72          for name, stmts in not_consumed.items():
73              stmt = stmts[0]
@@ -373,11 +387,11 @@
74                  self.add_message('W0623', args=args, node=name)
75 
76      def visit_assname(self, node):
77          if isinstance(node.ass_type(), astng.AugAssign):
78              self.visit_name(node)
79 -          
80 +
81      def visit_delname(self, node):
82          self.visit_name(node)
83 
84      def visit_name(self, node):
85          """check that a name is defined if the current scope and doesn't
diff --git a/test/input/func_all.py b/test/input/func_all.py
@@ -0,0 +1,45 @@
86 +"""Test Pylint's use of __all__.
87 +
88 +* NonExistant is not defined in this module, and it is listed in
89 +  __all__. An error is expected.
90 +
91 +* This module imports path and republished it in __all__. No errors
92 +  are expected.
93 +"""
94 +#  pylint: disable=R0903,R0201,W0612
95 +
96 +__revision__ = 0
97 +
98 +from os import path
99 +
100 +__all__ = [
101 +    'Dummy',
102 +    'NonExistant',
103 +    'path',
104 +    'func',
105 +    'inner',
106 +    'InnerKlass']
107 +
108 +
109 +class Dummy(object):
110 +    """A class defined in this module."""
111 +    pass
112 +
113 +DUMMY = Dummy()
114 +
115 +def function():
116 +    """Function docstring
117 +    """
118 +    pass
119 +
120 +function()
121 +
122 +class Klass(object):
123 +    """A klass which contains a function"""
124 +    def func(self):
125 +        """A klass method"""
126 +        inner = None
127 +
128 +    class InnerKlass(object):
129 +        """A inner klass"""
130 +        pass
diff --git a/test/messages/func_all.txt b/test/messages/func_all.txt
@@ -0,0 +1,4 @@
131 +E: 17: Undefined variable name 'NonExistant' in __all__
132 +E: 19: Undefined variable name 'func' in __all__
133 +E: 20: Undefined variable name 'inner' in __all__
134 +E: 21: Undefined variable name 'InnerKlass' in __all__