most import completions work now, but some other tests don't

This commit is contained in:
David Halter
2012-07-16 20:40:05 +02:00
parent e779cc8c97
commit 45413a18b9
8 changed files with 201 additions and 35 deletions

View File

@@ -2,6 +2,8 @@
This is a compatibility module, to make it possible to use jedi also with older This is a compatibility module, to make it possible to use jedi also with older
python versions. python versions.
""" """
def is_py3k():
return sys.hexversion >= 0x03000000
import sys import sys
# next was defined in python 2.6, in python 3 obj.next won't be possible # next was defined in python 2.6, in python 3 obj.next won't be possible
@@ -58,7 +60,7 @@ except NameError:
return s.decode("utf-8") return s.decode("utf-8")
# exec function # exec function
if sys.hexversion >= 0x03000000: if is_py3k():
def exec_function(source, global_map): def exec_function(source, global_map):
exec(source, global_map) exec(source, global_map)
else: else:
@@ -67,7 +69,7 @@ else:
# tokenize function # tokenize function
import tokenize import tokenize
if sys.hexversion >= 0x03000000: if is_py3k():
tokenize_func = tokenize.tokenize tokenize_func = tokenize.tokenize
else: else:
tokenize_func = tokenize.generate_tokens tokenize_func = tokenize.generate_tokens
@@ -79,7 +81,7 @@ except ImportError:
from io import BytesIO from io import BytesIO
# hasattr function used because python # hasattr function used because python
if sys.hexversion >= 0x03000000: if is_py3k():
hasattr = hasattr hasattr = hasattr
else: else:
def hasattr(obj, name): def hasattr(obj, name):

View File

@@ -27,7 +27,6 @@ import itertools
import copy import copy
import parsing import parsing
import modules
import debug import debug
import builtin import builtin
import imports import imports
@@ -793,7 +792,8 @@ def get_defined_names_for_position(obj, position=None, start_scope=None):
return names_new return names_new
def get_names_for_scope(scope, position=None, star_search=True): def get_names_for_scope(scope, position=None, star_search=True,
include_builtin=True):
""" """
Get all completions possible for the current scope. Get all completions possible for the current scope.
The star search option is only here to provide an optimization. Otherwise The star search option is only here to provide an optimization. Otherwise
@@ -820,9 +820,10 @@ def get_names_for_scope(scope, position=None, star_search=True):
for g in get_names_for_scope(s, star_search=False): for g in get_names_for_scope(s, star_search=False):
yield g yield g
# Add builtins to the global scope. # Add builtins to the global scope.
builtin_scope = builtin.Builtin.scope if include_builtin:
yield builtin_scope, builtin_scope.get_defined_names() builtin_scope = builtin.Builtin.scope
yield builtin_scope, builtin_scope.get_defined_names()
def get_scopes_for_name(scope, name_str, position=None, search_global=False): def get_scopes_for_name(scope, name_str, position=None, search_global=False):

View File

@@ -133,7 +133,7 @@ def complete(source, line, column, source_path):
:param col: The column to complete in. :param col: The column to complete in.
:type col: int :type col: int
:param source_path: The path in the os, the current module is in. :param source_path: The path in the os, the current module is in.
:type source_path: int :type source_path: str
:return: list of Completion objects. :return: list of Completion objects.
:rtype: list :rtype: list
@@ -186,8 +186,7 @@ def prepare_goto(source, position, source_path, is_like_search):
user_stmt = f.parser.user_stmt user_stmt = f.parser.user_stmt
if isinstance(user_stmt, parsing.Import): if isinstance(user_stmt, parsing.Import):
scopes = [imports.ImportPath(user_stmt, is_like_search, scopes = [imports.ImportPath(user_stmt, is_like_search)]
evaluate.follow_path)]
else: else:
# just parse one statement, take it and evaluate it # just parse one statement, take it and evaluate it
r = parsing.PyFuzzyParser(path, source_path) r = parsing.PyFuzzyParser(path, source_path)

157
imports.py Normal file
View File

@@ -0,0 +1,157 @@
import os
import pkgutil
import imp
import builtin
import modules
import debug
import parsing
import evaluate
class ModuleNotFound(Exception):
pass
class ImportPath(object):
global_namespace = object()
def __init__(self, import_stmt, is_like_search=False):
""" replace """
#print import_stmt
self.import_path = []
if import_stmt.from_ns:
self.import_path += import_stmt.from_ns.names
if import_stmt.namespace:
self.import_path += import_stmt.namespace.names
if is_like_search:
# drop one path part, because that is used by the like search
self.import_path.pop()
self.file_path = os.path.dirname(import_stmt.get_parent_until().path)
def get_defined_names(self):
names = []
for scope in self.follow():
if scope is ImportPath.global_namespace:
names += self.get_module_names()
names += self.get_module_names([self.file_path])
else:
for s, n in evaluate.get_names_for_scope(scope,
include_builtin=False):
names += n
if isinstance(scope, parsing.Module) \
and scope.path.endswith('__init__.py'):
names += \
self.get_module_names([os.path.dirname(scope.path)])
return names
def get_module_names(self, search_path=None):
names = []
for module_loader, name, is_pkg in pkgutil.iter_modules(search_path):
names.append(parsing.Name([name], (float('inf'), float('inf')),
(float('inf'), float('inf'))))
return names
def follow(self):
"""
"""
if self.import_path:
scope, rest = self.follow_file_system()
if rest:
scopes = evaluate.follow_path(iter(rest), scope)
else:
scopes = [scope]
new = []
for scope in scopes:
new += remove_star_imports(scope)
scopes += new
else:
scopes = [ImportPath.global_namespace]
debug.dbg('after import', scopes)
return scopes
def follow_file_system(self):
"""
Find a module with a path (of the module, like usb.backend.libusb10).
TODO: relative imports
"""
def follow_str(ns, string):
debug.dbg('follow_module', ns, string)
if ns:
path = [ns[1]]
else:
path = None
debug.dbg('search_module', string, path, self.file_path)
try:
i = imp.find_module(string, path)
except ImportError:
# find builtins (ommit path):
i = imp.find_module(string, builtin.module_find_path)
return i
# TODO handle relative paths - they are included in the import object
current_namespace = None
builtin.module_find_path.insert(0, self.file_path)
# now execute those paths
rest = []
for i, s in enumerate(self.import_path):
try:
current_namespace = follow_str(current_namespace, s)
except ImportError:
if current_namespace:
rest = self.import_path[i:]
else:
raise ModuleNotFound(
'The module you searched has not been found')
builtin.module_find_path.pop(0)
path = current_namespace[1]
is_package_directory = current_namespace[2][2] == imp.PKG_DIRECTORY
f = None
if is_package_directory or current_namespace[0]:
# is a directory module
if is_package_directory:
path += '/__init__.py'
with open(path) as f:
source = f.read()
else:
source = current_namespace[0].read()
if path.endswith('.py'):
f = modules.Module(path, source)
else:
f = builtin.Parser(path=path)
else:
f = builtin.Parser(name=path)
return f.parser.top, rest
def strip_imports(scopes):
"""
Here we strip the imports - they don't get resolved necessarily.
Really used anymore?
"""
result = []
for s in scopes:
if isinstance(s, parsing.Import):
try:
result += ImportPath(s).follow()
except ModuleNotFound:
debug.warning('Module not found: ' + str(s))
else:
result.append(s)
return result
def remove_star_imports(scope):
"""
"""
modules = strip_imports(i for i in scope.get_imports() if i.star)
new = []
for m in modules:
new += remove_star_imports(m)
modules += new
# Filter duplicate modules.
return set(modules)

View File

@@ -1,10 +1,8 @@
from __future__ import with_statement from __future__ import with_statement
import re import re
import tokenize import tokenize
import imp
import os import os
import debug
import parsing import parsing
import builtin import builtin
@@ -12,10 +10,6 @@ files = {}
load_module_cb = None load_module_cb = None
class ModuleNotFound(Exception):
pass
class Module(builtin.CachedModule): class Module(builtin.CachedModule):
""" """
Manages all files, that are parsed and caches them. Manages all files, that are parsed and caches them.

View File

@@ -28,7 +28,8 @@ Ignored statements:
- print (no use for it, just slows down) - print (no use for it, just slows down)
- exec (dangerous - not controllable) - exec (dangerous - not controllable)
""" """
from _compatibility import next, literal_eval, tokenize_func, BytesIO, property from _compatibility import (next, literal_eval, tokenize_func, BytesIO,
property, is_py3k)
import tokenize import tokenize
import re import re
@@ -937,6 +938,8 @@ class PyFuzzyParser(object):
self.user_position = user_position self.user_position = user_position
self.user_stmt = None self.user_stmt = None
self.code = code + '\n' # end with \n, because the parser needs it self.code = code + '\n' # end with \n, because the parser needs it
if is_py3k():
self.code = self.code.encode()
# initialize global Scope # initialize global Scope
self.top = Module(module_path) self.top = Module(module_path)
@@ -977,7 +980,7 @@ class PyFuzzyParser(object):
self.user_stmt = i self.user_stmt = i
else: else:
self.user_stmt = i self.user_stmt = i
print 'up', self.user_stmt #print 'up', self.user_stmt
def _parsedotname(self, pre_used_token=None): def _parsedotname(self, pre_used_token=None):
@@ -1281,7 +1284,7 @@ class PyFuzzyParser(object):
:raises: IndentationError :raises: IndentationError
""" """
buf = BytesIO(self.code.encode()) buf = BytesIO(self.code)
self.gen = tokenize_func(buf.readline) self.gen = tokenize_func(buf.readline)
self.currentscope = self.scope self.currentscope = self.scope

View File

@@ -60,5 +60,11 @@ func_with_import().sleep
#? ['sqlite3'] #? ['sqlite3']
import sqlite import sqlite
#? ['time'] #? ['classes']
from datetime import import classes
#? ['timedelta']
from datetime import timedelta
#? ['Cursor']
from sqlite3 import Cursor

View File

@@ -18,7 +18,7 @@ if only_line is not None:
#functions.set_debug_function(functions.debug.print_to_stdout) #functions.set_debug_function(functions.debug.print_to_stdout)
def run_completion_test(correct, source, line_nr, line): def run_completion_test(correct, source, line_nr, line, path):
""" """
Runs tests for completions. Runs tests for completions.
Return if the test was a fail or not, with 1 for fail and 0 for success. Return if the test was a fail or not, with 1 for fail and 0 for success.
@@ -26,8 +26,7 @@ def run_completion_test(correct, source, line_nr, line):
# lines start with 1 and column is just the last (makes no # lines start with 1 and column is just the last (makes no
# difference for testing) # difference for testing)
try: try:
completions = functions.complete(source, line_nr, len(line), completions = functions.complete(source, line_nr, len(line), path)
completion_test_dir)
except (Exception, functions.evaluate.MultiLevelAttributeError): except (Exception, functions.evaluate.MultiLevelAttributeError):
print('test @%s: %s' % (line_nr - 1, line)) print('test @%s: %s' % (line_nr - 1, line))
print(traceback.format_exc()) print(traceback.format_exc())
@@ -42,14 +41,13 @@ def run_completion_test(correct, source, line_nr, line):
return 0 return 0
def run_definition_test(correct, source, line_nr, line, correct_start): def run_definition_test(correct, source, line_nr, line, correct_start, path):
""" """
Runs tests for definitions. Runs tests for definitions.
Return if the test was a fail or not, with 1 for fail and 0 for success. Return if the test was a fail or not, with 1 for fail and 0 for success.
""" """
def defs(line_nr, indent): def defs(line_nr, indent):
return set(functions.get_definitions(source, line_nr, indent, return set(functions.get_definitions(source, line_nr, indent, path))
completion_test_dir))
try: try:
result = defs(line_nr, len(line)) result = defs(line_nr, len(line))
except (Exception, functions.evaluate.MultiLevelAttributeError): except (Exception, functions.evaluate.MultiLevelAttributeError):
@@ -79,7 +77,7 @@ def run_definition_test(correct, source, line_nr, line, correct_start):
return 0 return 0
def run_test(source): def run_test(source, f_name):
""" """
This is the completion test for some cases. The tests are not unit test This is the completion test for some cases. The tests are not unit test
like, they are rather integration tests. like, they are rather integration tests.
@@ -88,8 +86,11 @@ def run_test(source):
row symbolizes the cursor. row symbolizes the cursor.
For example: For example:
#? ['ab'] >>> #? ['ab']
ab = 3; a >>> ab = 3; a
>>> #? int()
>>> ab = 3; ab
""" """
fails = 0 fails = 0
tests = 0 tests = 0
@@ -100,11 +101,13 @@ def run_test(source):
if correct: if correct:
# if a list is wanted, use the completion test, otherwise the # if a list is wanted, use the completion test, otherwise the
# get_definition test # get_definition test
path = completion_test_dir + os.path.sep + f_name
if correct.startswith('['): if correct.startswith('['):
fails += run_completion_test(correct, source, line_nr, line) fails += run_completion_test(correct, source, line_nr, line,
path)
else: else:
fails += run_definition_test(correct, source, line_nr, line, fails += run_definition_test(correct, source, line_nr, line,
start) start, path)
correct = None correct = None
tests += 1 tests += 1
else: else:
@@ -138,7 +141,7 @@ def test_dir(completion_test_dir, third_party=False):
continue continue
path = os.path.join(completion_test_dir, f_name) path = os.path.join(completion_test_dir, f_name)
f = open(path) f = open(path)
num_tests, fails = run_test(f.read()) num_tests, fails = run_test(f.read(), f_name)
s = 'run %s tests with %s fails (%s)' % (num_tests, fails, s = 'run %s tests with %s fails (%s)' % (num_tests, fails,
f_name) f_name)
if fails: if fails:
@@ -152,7 +155,8 @@ summary = []
tests_pass = True tests_pass = True
test_dir(completion_test_dir) test_dir(completion_test_dir)
test_dir(completion_test_dir + '/thirdparty', third_party=True) completion_test_dir += '/thirdparty'
test_dir(completion_test_dir, third_party=True)
print('\nSummary:') print('\nSummary:')
for s in summary: for s in summary: