diff --git a/evaluate.py b/evaluate.py index a53edf4e..3c69f7ce 100644 --- a/evaluate.py +++ b/evaluate.py @@ -1509,7 +1509,7 @@ def goto(scopes, search_name=None, statement_path_offset=1): if isinstance(s, imports.ImportPath): try: s = s.follow()[0] - except imports.ModuleNotFound: + except IndexError: continue else: if not isinstance(s, parsing.Module): diff --git a/functions.py b/functions.py index 7b36ff73..8da0a3b7 100644 --- a/functions.py +++ b/functions.py @@ -193,21 +193,14 @@ def _prepare_goto(position, source_path, module, goto_path, if isinstance(user_stmt, parsing.Import): import_names = user_stmt.get_all_import_names() count = 0 - found_count = None + kill_count = -1 for i in import_names: for name_part in i.names: count += 1 - if name_part.start_pos <= position <= name_part.end_pos: - found_count = count - if found_count is None: - found_count = count - if is_like_search: - # is_like_search will decrease also one, so change this here. - found_count += 1 - else: - return [] + if position <= name_part.end_pos: + kill_count += 1 scopes = [imports.ImportPath(user_stmt, is_like_search, - kill_count=(count - found_count), direct_resolve=True)] + kill_count=kill_count, direct_resolve=True)] else: # just parse one statement, take it and evaluate it r = parsing.PyFuzzyParser(goto_path, source_path, no_docstr=True) @@ -244,10 +237,7 @@ def get_definition(source, line, column, source_path): for s in scopes.copy(): if isinstance(s, imports.ImportPath): scopes.remove(s) - try: - scopes.update(resolve_import_paths(set(s.follow()))) - except imports.ModuleNotFound: - pass + scopes.update(resolve_import_paths(set(s.follow()))) return scopes pos = (line, column) diff --git a/imports.py b/imports.py index 9f0968ec..b762fdd2 100644 --- a/imports.py +++ b/imports.py @@ -21,8 +21,14 @@ class ImportPath(object): """ An ImportPath is the path of a `parsing.Import` object. """ - class GlobalNamespace(object): - pass + class _GlobalNamespace(object): + def get_defined_names(self): + return [] + + def get_imports(self): + return [] + + GlobalNamespace = _GlobalNamespace() def __init__(self, import_stmt, is_like_search=False, kill_count=0, direct_resolve=False): @@ -45,7 +51,6 @@ class ImportPath(object): for i in range(kill_count + int(is_like_search)): self.import_path.pop() - def __repr__(self): return '<%s: %s>' % (self.__class__.__name__, self.import_stmt) @@ -79,8 +84,13 @@ class ImportPath(object): names = [] for scope in self.follow(): if scope is ImportPath.GlobalNamespace: - names += self.get_module_names() - names += self.get_module_names([self.file_path]) + if self.import_stmt.relative_count == 0: + names += self.get_module_names() + + path = os.path.abspath(self.file_path) + for i in range(self.import_stmt.relative_count - 1): + path = os.path.dirname(path) + names += self.get_module_names([path]) else: if on_import_stmt and isinstance(scope, parsing.Module) \ and scope.path.endswith('__init__.py'): @@ -117,7 +127,13 @@ class ImportPath(object): Returns the imported modules. """ if self.import_path: - scope, rest = self._follow_file_system() + try: + scope, rest = self._follow_file_system() + except ModuleNotFound: + debug.warning('Module not found: ' + str(self.import_stmt)) + return [] + return [ImportPath.GlobalNamespace] + if len(rest) > 1 or rest and self.is_like_search: scopes = [] elif rest: @@ -214,10 +230,7 @@ def strip_imports(scopes): if isinstance(s, parsing.Import): # this is something like a statement following. evaluate.statement_path.append(s) - try: - result += ImportPath(s).follow() - except ModuleNotFound: - debug.warning('Module not found: ' + str(s)) + result += ImportPath(s).follow() else: result.append(s) return result diff --git a/parsing.py b/parsing.py index 28d47e13..ec714934 100644 --- a/parsing.py +++ b/parsing.py @@ -515,7 +515,7 @@ class Import(Simple): :type defunct: bool """ def __init__(self, start_pos, end_pos, namespace, alias=None, \ - from_ns=None, star=False, relative_count=None, defunct=False): + from_ns=None, star=False, relative_count=0, defunct=False): super(Import, self).__init__(start_pos, end_pos) self.namespace = namespace @@ -1138,6 +1138,10 @@ class PyFuzzyParser(object): else: token_type, tok = pre_used_token + if token_type != tokenize.NAME and tok != '*': + # token maybe a name or star + return (None, token_type, tok) + append((tok, self.start_pos)) first_pos = self.start_pos while True: @@ -1175,6 +1179,8 @@ class PyFuzzyParser(object): while True: defunct = False token_type, tok = self.next() + if token_type == tokenize.ENDMARKER: + break if brackets and tok == '\n': self.next() if tok == '(': # python allows only one `(` in the statement. @@ -1421,15 +1427,16 @@ class PyFuzzyParser(object): else: n, token_type, tok = self._parsedotname(self.current) tok_list.pop() # removed last entry, because we add Name - tok_list.append(n) - if tok == '(': - # it must be a function - used_funcs.append(n) - else: - used_vars.append(n) - if string and re.match(r'[\w\d\'"]', string[-1]): - string += ' ' - string += ".".join(n.names) + if n: + tok_list.append(n) + if tok == '(': + # it must be a function + used_funcs.append(n) + else: + used_vars.append(n) + if string and re.match(r'[\w\d\'"]', string[-1]): + string += ' ' + string += ".".join(n.names) continue elif '=' in tok and not tok in ['>=', '<=', '==', '!=']: # there has been an assignement -> change vars @@ -1474,7 +1481,7 @@ class PyFuzzyParser(object): except AttributeError: debug.warning('return in non-function') - if list_comp or tok in always_break: + if tok in always_break: self.gen.push_back(self._current_full) return stmt, tok @@ -1584,9 +1591,11 @@ class PyFuzzyParser(object): debug.warning("from: syntax error@%s" % self.start_pos[0]) defunct = True + if tok != 'import': + self.gen.push_back(self._current_full) names = self._parseimportlist() for name, alias, defunct2 in names: - star = name.names[0] == '*' + star = name is not None and name.names[0] == '*' if star: name = None i = Import(first_pos, self.end_pos, name, alias, mod, @@ -1596,6 +1605,7 @@ class PyFuzzyParser(object): if not names: i = Import(first_pos, self.end_pos, mod, defunct=True, relative_count=relative_count) + print i, repr(mod) self._check_user_stmt(i) self.scope.add_import(i) self.freshscope = False diff --git a/test/completion/imports.py b/test/completion/imports.py index b79cc911..ac2b8c47 100644 --- a/test/completion/imports.py +++ b/test/completion/imports.py @@ -172,6 +172,10 @@ mod1. #? str() imp_tree.a + +# ----------------- +# special positions -> edge cases +# ----------------- import datetime #? 6 datetime @@ -187,3 +191,13 @@ from import_tree. import pkg #? 18 ['pkg'] from import_tree.p import pkg + +#? 17 ['import_tree'] +from .import_tree import +#? 10 ['run'] +from ..run import +#? ['run'] +from .. import run + +#? [] +from not_a_module import diff --git a/test/regression.py b/test/regression.py index 5da107a5..0f35e508 100755 --- a/test/regression.py +++ b/test/regression.py @@ -87,6 +87,12 @@ class TestRegression(unittest.TestCase): assert self.get_def("import sys_blabla", (1, 8)) == [] assert len(self.get_def("import sys", (1, 8))) == 1 + def test_complete_on_empty_import(self): + # should just list the files in the directory + assert 10 < len(self.complete("from .", (1, 5))) < 30 + assert 10 < len(self.complete("from . import", (1, 5))) < 30 + assert 10 < len(self.complete("from . import classes", (1, 5))) < 30 + def test_new(self): """ This is just to try out things, removing or deleting it is ok. """ s = ("def abc(): pass\n"