Fix TypeError when processing relative imports (#61)

Fixes lp:1560134

aec68a784 added module names to error messages,
however it caused a TypeError for relative imports
that do not specify a module such as:

   from . import x

This fixes the TypeError, and also adds the necessary
leading dots for relative import error messages.

Add tests for various types of relative imports.
diff --git a/pyflakes/checker.py b/pyflakes/checker.py
index 9545cab..43acc69 100644
--- a/pyflakes/checker.py
+++ b/pyflakes/checker.py
@@ -209,7 +209,12 @@
     def __init__(self, name, source, module, real_name=None):
         self.module = module
         self.real_name = real_name or name
-        full_name = module + '.' + self.real_name
+
+        if module.endswith('.'):
+            full_name = module + self.real_name
+        else:
+            full_name = module + '.' + self.real_name
+
         super(ImportationFrom, self).__init__(name, source, full_name)
 
     def __str__(self):
@@ -244,7 +249,11 @@
         return 'from ' + self.fullName + ' import *'
 
     def __str__(self):
-        return self.name
+        # When the module ends with a ., avoid the ambiguous '..*'
+        if self.fullName.endswith('.'):
+            return self.source_statement
+        else:
+            return self.name
 
 
 class FutureImportation(ImportationFrom):
@@ -1142,6 +1151,8 @@
         else:
             self.futuresAllowed = False
 
+        module = ('.' * node.level) + (node.module or '')
+
         for alias in node.names:
             name = alias.asname or alias.name
             if node.module == '__future__':
@@ -1153,15 +1164,15 @@
                 # Only Python 2, local import * is a SyntaxWarning
                 if not PY2 and not isinstance(self.scope, ModuleScope):
                     self.report(messages.ImportStarNotPermitted,
-                                node, node.module)
+                                node, module)
                     continue
 
                 self.scope.importStarred = True
-                self.report(messages.ImportStarUsed, node, node.module)
-                importation = StarImportation(node.module, node)
+                self.report(messages.ImportStarUsed, node, module)
+                importation = StarImportation(module, node)
             else:
                 importation = ImportationFrom(name, node,
-                                              node.module, alias.name)
+                                              module, alias.name)
             self.addBinding(node, importation)
 
     def TRY(self, node):
diff --git a/pyflakes/test/test_imports.py b/pyflakes/test/test_imports.py
index 9cbd4d7..f1bb5cd 100644
--- a/pyflakes/test/test_imports.py
+++ b/pyflakes/test/test_imports.py
@@ -40,6 +40,26 @@
         assert binding.source_statement == 'import a.b as a'
         assert str(binding) == 'a.b as a'
 
+    def test_importfrom_relative(self):
+        binding = ImportationFrom('a', None, '.', 'a')
+        assert binding.source_statement == 'from . import a'
+        assert str(binding) == '.a'
+
+    def test_importfrom_relative_parent(self):
+        binding = ImportationFrom('a', None, '..', 'a')
+        assert binding.source_statement == 'from .. import a'
+        assert str(binding) == '..a'
+
+    def test_importfrom_relative_with_module(self):
+        binding = ImportationFrom('b', None, '..a', 'b')
+        assert binding.source_statement == 'from ..a import b'
+        assert str(binding) == '..a.b'
+
+    def test_importfrom_relative_with_module_as(self):
+        binding = ImportationFrom('c', None, '..a', 'b')
+        assert binding.source_statement == 'from ..a import b as c'
+        assert str(binding) == '..a.b as c'
+
     def test_importfrom_member(self):
         binding = ImportationFrom('b', None, 'a', 'b')
         assert binding.source_statement == 'from a import b'
@@ -65,6 +85,11 @@
         assert binding.source_statement == 'from a.b import *'
         assert str(binding) == 'a.b.*'
 
+    def test_importfrom_star_relative(self):
+        binding = StarImportation('.b', None)
+        assert binding.source_statement == 'from .b import *'
+        assert str(binding) == '.b.*'
+
     def test_importfrom_future(self):
         binding = FutureImportation('print_function', None, None)
         assert binding.source_statement == 'from __future__ import print_function'
@@ -77,6 +102,29 @@
         self.flakes('import fu, bar', m.UnusedImport, m.UnusedImport)
         self.flakes('from baz import fu, bar', m.UnusedImport, m.UnusedImport)
 
+    def test_unusedImport_relative(self):
+        self.flakes('from . import fu', m.UnusedImport)
+        self.flakes('from . import fu as baz', m.UnusedImport)
+        self.flakes('from .. import fu', m.UnusedImport)
+        self.flakes('from ... import fu', m.UnusedImport)
+        self.flakes('from .. import fu as baz', m.UnusedImport)
+        self.flakes('from .bar import fu', m.UnusedImport)
+        self.flakes('from ..bar import fu', m.UnusedImport)
+        self.flakes('from ...bar import fu', m.UnusedImport)
+        self.flakes('from ...bar import fu as baz', m.UnusedImport)
+
+        checker = self.flakes('from . import fu', m.UnusedImport)
+
+        error = checker.messages[0]
+        assert error.message == '%r imported but unused'
+        assert error.message_args == ('.fu', )
+
+        checker = self.flakes('from . import fu as baz', m.UnusedImport)
+
+        error = checker.messages[0]
+        assert error.message == '%r imported but unused'
+        assert error.message_args == ('.fu as baz', )
+
     def test_aliasedImport(self):
         self.flakes('import fu as FU, bar as FU',
                     m.RedefinedWhileUnused, m.UnusedImport)
@@ -94,6 +142,12 @@
         self.flakes('from baz import fu; print(fu)')
         self.flakes('import fu; del fu')
 
+    def test_usedImport_relative(self):
+        self.flakes('from . import fu; assert fu')
+        self.flakes('from .bar import fu; assert fu')
+        self.flakes('from .. import fu; assert fu')
+        self.flakes('from ..bar import fu as baz; assert baz')
+
     def test_redefinedWhileUnused(self):
         self.flakes('import fu; fu = 3', m.RedefinedWhileUnused)
         self.flakes('import fu; fu, bar = 3', m.RedefinedWhileUnused)
@@ -687,6 +741,49 @@
             pass
         ''', m.ImportStarUsed, m.UnusedImport)
 
+        checker = self.flakes('from fu import *',
+                              m.ImportStarUsed, m.UnusedImport)
+
+        error = checker.messages[0]
+        assert error.message.startswith("'from %s import *' used; unable ")
+        assert error.message_args == ('fu', )
+
+        error = checker.messages[1]
+        assert error.message == '%r imported but unused'
+        assert error.message_args == ('fu.*', )
+
+    def test_importStar_relative(self):
+        """Use of import * from a relative import is reported."""
+        self.flakes('from .fu import *', m.ImportStarUsed, m.UnusedImport)
+        self.flakes('''
+        try:
+            from .fu import *
+        except:
+            pass
+        ''', m.ImportStarUsed, m.UnusedImport)
+
+        checker = self.flakes('from .fu import *',
+                              m.ImportStarUsed, m.UnusedImport)
+
+        error = checker.messages[0]
+        assert error.message.startswith("'from %s import *' used; unable ")
+        assert error.message_args == ('.fu', )
+
+        error = checker.messages[1]
+        assert error.message == '%r imported but unused'
+        assert error.message_args == ('.fu.*', )
+
+        checker = self.flakes('from .. import *',
+                              m.ImportStarUsed, m.UnusedImport)
+
+        error = checker.messages[0]
+        assert error.message.startswith("'from %s import *' used; unable ")
+        assert error.message_args == ('..', )
+
+        error = checker.messages[1]
+        assert error.message == '%r imported but unused'
+        assert error.message_args == ('from .. import *', )
+
     @skipIf(version_info < (3,),
             'import * below module level is a warning on Python 2')
     def test_localImportStar(self):
@@ -700,6 +797,14 @@
             from fu import *
         ''', m.ImportStarNotPermitted)
 
+        checker = self.flakes('''
+        class a:
+            from .. import *
+        ''', m.ImportStarNotPermitted)
+        error = checker.messages[0]
+        assert error.message == "'from %s import *' only allowed at module level"
+        assert error.message_args == ('..', )
+
     @skipIf(version_info > (3,),
             'import * below module level is an error on Python 3')
     def test_importStarNested(self):