~ubuntu-branches/ubuntu/trusty/pylint/trusty

« back to all changes in this revision

Viewing changes to checkers/imports.py

  • Committer: Bazaar Package Importer
  • Author(s): Sandro Tosi, Julien Lavergne, Alexandre Fayolle, Sandro Tosi
  • Date: 2009-09-14 23:52:18 UTC
  • mfrom: (7.1.1 sid)
  • Revision ID: james.westby@ubuntu.com-20090914235218-o3rtlhbskylm3l6d
[ Julien Lavergne ]
* Python 2.6 transition, thanks Alessio Treglia for the patch; Closes: #530509
 - Use --install-layout=deb for setup.py install.
 - Replace site-packages by *-packages.
 - Build-depends on python >= 2.5.4-1~ for --install-layout=deb.

[ Alexandre Fayolle ]
 * debian/rules: set NO_SETUPTOOLS when calling python setup.py

[ Sandro Tosi ]
* New upstream release
  - fix a false positive on E0611; thanks to Yann Dirson for the report;
    Closes: #546522
* debian/control
  - removed Conflicts and Replaces, no more needed
  - bump Standards-Version to 3.8.3 (no changes needed)
  - bump versioned dependencies on python-logilab-common and
    python-logilab-astng (to correctly handle migratation from pycentral to
    pysupport, hence the urgency)
* debian/README.source
  - removed, not needed

Show diffs side-by-side

added added

removed removed

Lines of Context:
13
13
# You should have received a copy of the GNU General Public License along with
14
14
# this program; if not, write to the Free Software Foundation, Inc.,
15
15
# 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 
"""imports checkers for Python code
17
 
"""
 
16
"""imports checkers for Python code"""
18
17
 
19
18
from logilab.common.graph import get_cycles, DotBackend
20
 
from logilab.common.modutils import is_standard_module, is_relative, \
21
 
     get_module_part
 
19
from logilab.common.modutils import is_standard_module
22
20
from logilab.common.ureports import VerbatimText, Paragraph
23
21
from logilab.common.compat import sorted, enumerate
24
22
 
25
23
from logilab import astng
26
24
from logilab.astng.infutils import are_exclusive
27
25
 
28
 
from pylint.utils import expand_modules
29
26
from pylint.interfaces import IASTNGChecker
30
27
from pylint.checkers import BaseChecker, EmptyReport
31
28
 
41
38
            if base == node.modname and level == node.level and \
42
39
                   name in [iname[0] for iname in node.names]:
43
40
                return node
44
 
            
45
 
        
 
41
 
 
42
 
46
43
# utilities to represents import dependencies as tree and dot graph ###########
47
44
 
48
45
def filter_dependencies_info(dep_info, package_dir, mode='external'):
96
93
 
97
94
 
98
95
def dependencies_graph(filename, dep_info):
99
 
    """write dependencies as a dot (graphviz) file 
 
96
    """write dependencies as a dot (graphviz) file
100
97
    """
101
98
    done = {}
102
99
    printer = DotBackend(filename[:-4], rankdir = "LR")
126
123
# the import checker itself ###################################################
127
124
 
128
125
MSGS = {
129
 
    'F0401': ('Unable to import %r (%s)' ,
 
126
    'F0401': ('Unable to import %r' ,
130
127
              'Used when pylint has been unable to import a module.'),
131
128
    'R0401': ('Cyclic import (%s)',
132
129
              'Used when a cyclic import between two or more modules is \
133
130
              detected.'),
134
 
    
 
131
 
135
132
    'W0401': ('Wildcard import %s',
136
133
              'Used when `from module import *` is detected.'),
137
134
    'W0402': ('Uses of a deprecated module %r',
138
135
              'Used a module marked as deprecated is imported.'),
139
 
    'W0403': ('Relative import %r',
 
136
    'W0403': ('Relative import %r, should be %r',
140
137
              'Used when an import relative to the package directory is \
141
138
              detected.'),
142
139
    'W0404': ('Reimport %r (imported line %s)',
143
140
              'Used when a module is reimported multiple times.'),
144
141
    'W0406': ('Module import itself',
145
142
              'Used when a module is importing itself.'),
146
 
    
 
143
 
147
144
    'W0410': ('__future__ import is not the first non docstring statement',
148
145
              'Python 2.5 and greater require __future__ import to be the \
149
146
              first non docstring statement in the module.'),
150
147
    }
151
148
 
152
149
class ImportsChecker(BaseChecker):
153
 
    """checks for                                                              
154
 
    * external modules dependencies                                            
155
 
    * relative / wildcard imports                                                         
156
 
    * cyclic imports                                                           
 
150
    """checks for
 
151
    * external modules dependencies
 
152
    * relative / wildcard imports
 
153
    * cyclic imports
157
154
    * uses of deprecated modules
158
155
    """
159
 
    
 
156
 
160
157
    __implements__ = IASTNGChecker
161
158
 
162
159
    name = 'imports'
163
160
    msgs = MSGS
164
161
    priority = -2
165
 
    
 
162
 
166
163
    options = (('deprecated-modules',
167
164
                {'default' : ('regsub','string', 'TERMIOS',
168
165
                              'Bastion', 'rexec'),
192
189
                 'help' : 'Create a graph of internal dependencies in the \
193
190
given file (report R0402 must not be disabled)'}
194
191
                ),
195
 
               
 
192
 
196
193
               )
197
194
 
198
195
    def __init__(self, linter=None):
205
202
                        ('R0402', 'Modules dependencies graph',
206
203
                         self.report_dependencies_graph),
207
204
                        )
208
 
        
 
205
 
209
206
    def open(self):
210
207
        """called before visiting project (i.e set of modules)"""
211
208
        self.linter.add_stats(dependencies={})
212
209
        self.linter.add_stats(cycles=[])
213
210
        self.stats = self.linter.stats
214
211
        self.import_graph = {}
215
 
        
 
212
 
216
213
    def close(self):
217
214
        """called before visiting project (i.e set of modules)"""
218
215
        # don't try to compute cycles if the associated message is disabled
219
216
        if self.linter.is_message_enabled('R0401'):
220
217
            for cycle in get_cycles(self.import_graph):
221
218
                self.add_message('R0401', args=' -> '.join(cycle))
222
 
         
 
219
 
223
220
    def visit_import(self, node):
224
221
        """triggered when an import statement is seen"""
 
222
        modnode = node.root()
225
223
        for name, _ in node.names:
226
 
            if self._module_not_exists(node, name):
 
224
            importedmodnode = self.get_imported_module(modnode, node, name)
 
225
            if importedmodnode is None:
227
226
                continue
228
 
            self._check_deprecated(node, name)
229
 
            relative = self._check_relative(node, name)
230
 
            self._imported_module(node, name, relative)
231
 
            # handle reimport
 
227
            self._check_relative_import(modnode, node, importedmodnode, name)
 
228
            self._add_imported_module(node, importedmodnode.name)
 
229
            self._check_deprecated_module(node, name)
232
230
            self._check_reimport(node, name)
233
 
        
 
231
 
234
232
 
235
233
    def visit_from(self, node):
236
234
        """triggered when a from statement is seen"""
237
235
        basename = node.modname
238
 
        if self._module_not_exists(node, basename):
239
 
            return
240
236
        if basename == '__future__':
241
237
            # check if this is the first non-docstring statement in the module
242
238
            prev = node.previous_sibling()
245
241
                if not (isinstance(prev, astng.From)
246
242
                       and prev.modname == '__future__'):
247
243
                    self.add_message('W0410', node=node)
248
 
        self._check_deprecated(node, basename)
249
 
        level = node.level
250
 
        if level > 0: # explicit relative import (leading dots)
251
 
            relative = True
252
 
        else:
253
 
            relative = self._check_relative(node, basename)
 
244
            return
 
245
        modnode = node.root()
 
246
        importedmodnode = self.get_imported_module(modnode, node, basename)
 
247
        if importedmodnode is None:
 
248
            return
 
249
        self._check_relative_import(modnode, node, importedmodnode, basename)
 
250
        self._check_deprecated_module(node, basename)
254
251
        for name, _ in node.names:
255
252
            if name == '*':
256
253
                self.add_message('W0401', args=basename, node=node)
257
254
                continue
258
 
            # handle reimport
259
 
            self._check_reimport(node, name, basename, level)
260
 
            # analyze dependencies
261
 
            fullname = '.' * level + '%s.%s' % (basename, name)
262
 
            fullname = get_module_part(fullname,context_file=node.root().file)
263
 
            self._imported_module(node, fullname, relative)
264
 
 
265
 
    def _module_not_exists(self, node, modname):
266
 
        """check if module exists and possibly add message"""
267
 
        errors = expand_modules([modname], [])[1]
268
 
        if not errors or is_relative(modname, node.root().file):
 
255
            self._add_imported_module(node, '%s.%s' % (importedmodnode.name, name))
 
256
            self._check_reimport(node, name, basename, node.level)
 
257
 
 
258
    def get_imported_module(self, modnode, importnode, modname):
 
259
        try:
 
260
            return importnode.do_import_module(modname)
 
261
        except astng.InferenceError, ex:
 
262
            if str(ex).startswith('module importing itself'): # XXX
 
263
                return modnode
 
264
            else:
 
265
                self.add_message("F0401", args=modname, node=importnode)
 
266
                return
 
267
 
 
268
    def _check_relative_import(self, modnode, importnode, importedmodnode,
 
269
                               importedasname):
 
270
        """check relative import. node is either an Import or From node, modname
 
271
        the imported module name.
 
272
        """
 
273
        if importedmodnode.file is None:
 
274
            return False # built-in module
 
275
        if modnode is importedmodnode:
 
276
            return False # module importing itself
 
277
        if modnode.absolute_import_activated() or getattr(importnode, 'level', None):
269
278
            return False
270
 
        error = errors[0]
271
 
        if error["key"] == "F0001":
272
 
            args = (error["mod"], error["ex"])
273
 
            self.add_message("F0401", args=args, node=node)
274
 
            return True
275
 
        assert error["key"] == "F0003"
276
 
        return False
 
279
        if importedmodnode.name != importedasname:
 
280
            # this must be a relative import...
 
281
            self.add_message('W0403', args=(importedasname, importedmodnode.name),
 
282
                             node=importnode)
277
283
 
278
 
    def _imported_module(self, node, mod_path, relative):
279
 
        """notify an imported module, used to analyze dependencies
280
 
        """
 
284
    def _add_imported_module(self, node, importedmodname):
 
285
        """notify an imported module, used to analyze dependencies"""
281
286
        context_name = node.root().name
282
 
        if relative:
283
 
            context_parts = context_name.split('.')
284
 
            if mod_path.startswith('.'):
285
 
                while mod_path.startswith('.'):
286
 
                    mod_path = mod_path[1:]
287
 
                    del context_parts[-1] # one level upwards
288
 
                context_parts.append(mod_path)
289
 
            else:
290
 
                context_parts[-1] = mod_path
291
 
            mod_path = '.'.join(context_parts)
292
 
        if context_name == mod_path:
 
287
        if context_name == importedmodname:
293
288
            # module importing itself !
294
289
            self.add_message('W0406', node=node)
295
 
        elif not is_standard_module(mod_path):
 
290
        elif not is_standard_module(importedmodname):
296
291
            # handle dependencies
297
 
            mod_paths = self.stats['dependencies'].setdefault(mod_path, [])
298
 
            if not context_name in mod_paths:
299
 
                mod_paths.append(context_name)
300
 
            if is_standard_module( mod_path, (self.package_dir(),) ):
 
292
            importedmodnames = self.stats['dependencies'].setdefault(
 
293
                importedmodname, set())
 
294
            if not context_name in importedmodnames:
 
295
                importedmodnames.add(context_name)
 
296
            if is_standard_module( importedmodname, (self.package_dir(),) ):
301
297
                # update import graph
302
 
                mgraph = self.import_graph.setdefault(context_name, [])
303
 
                if not mod_path in mgraph:
304
 
                    mgraph.append(mod_path)
 
298
                mgraph = self.import_graph.setdefault(context_name, set())
 
299
                if not importedmodname in mgraph:
 
300
                    mgraph.add(importedmodname)
305
301
 
306
 
    def _check_relative(self, node, mod_path):
307
 
        """check relative import module"""
308
 
        context_file = node.root().file
309
 
        relative = is_relative(mod_path, context_file)
310
 
        if relative:
311
 
            self.add_message('W0403', args=mod_path, node=node)
312
 
        return relative
313
 
    
314
 
    def _check_deprecated(self, node, mod_path):
 
302
    def _check_deprecated_module(self, node, mod_path):
315
303
        """check if the module is deprecated"""
 
304
        # XXX rewrite
316
305
        for mod_name in self.config.deprecated_modules:
317
306
            if mod_path.startswith(mod_name) and \
318
307
                   (len(mod_path) == len(mod_name)
319
308
                    or mod_path[len(mod_name)] == '.'):
320
309
                self.add_message('W0402', node=node, args=mod_path)
321
 
    
 
310
 
322
311
    def _check_reimport(self, node, name, basename=None, level=0):
323
 
        """check if the import is necessary (i.e. not already done)
324
 
        """
 
312
        """check if the import is necessary (i.e. not already done)"""
 
313
        # XXX rewrite
325
314
        frame = node.frame()
326
315
        first = get_first_import(frame, name, basename, level)
327
316
        if isinstance(first, (astng.Import, astng.From)) and first is not node \
338
327
                self.add_message('W0404', node=node,
339
328
                                 args=(name, first.fromlineno))
340
329
 
341
 
        
 
330
 
342
331
    def report_external_dependencies(self, sect, _, dummy):
343
 
        """return a verbatim layout for displaying dependencies
344
 
        """
 
332
        """return a verbatim layout for displaying dependencies"""
345
333
        dep_info = make_tree_defs(self._external_dependencies_info().items())
346
334
        if not dep_info:
347
335
            raise EmptyReport()
350
338
 
351
339
    def report_dependencies_graph(self, sect, _, dummy):
352
340
        """write dependencies as a dot (graphviz) file"""
353
 
        dep_info = self.stats['dependencies']        
 
341
        dep_info = self.stats['dependencies']
354
342
        if not dep_info or not (self.config.import_graph
355
343
                                or self.config.ext_import_graph
356
344
                                or self.config.int_import_graph):
366
354
        if filename:
367
355
            make_graph(filename, self._internal_dependencies_info(),
368
356
                       sect, 'internal ')
369
 
            
 
357
 
370
358
    def _external_dependencies_info(self):
371
359
        """return cached external dependencies information or build and
372
360
        cache them
375
363
            self.__ext_dep_info = filter_dependencies_info(
376
364
                self.stats['dependencies'], self.package_dir(), 'external')
377
365
        return self.__ext_dep_info
378
 
        
 
366
 
379
367
    def _internal_dependencies_info(self):
380
368
        """return cached internal dependencies information or build and
381
369
        cache them
384
372
            self.__int_dep_info = filter_dependencies_info(
385
373
                self.stats['dependencies'], self.package_dir(), 'internal')
386
374
        return self.__int_dep_info
387
 
        
388
 
            
 
375
 
 
376
 
389
377
def register(linter):
390
378
    """required method to auto register this checker """
391
379
    linter.register_checker(ImportsChecker(linter))