mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 06:24:27 +08:00
Merge branch 'dev' into bugfix/performances_degradation
# Conflicts: # test/test_regression.py
This commit is contained in:
@@ -38,5 +38,7 @@ Sid Shanker (@squidarth) <sid.p.shanker@gmail.com>
|
|||||||
Reinoud Elhorst (@reinhrst)
|
Reinoud Elhorst (@reinhrst)
|
||||||
Guido van Rossum (@gvanrossum) <guido@python.org>
|
Guido van Rossum (@gvanrossum) <guido@python.org>
|
||||||
Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro>
|
Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro>
|
||||||
|
Cristi Burcă (@scribu)
|
||||||
|
|
||||||
|
|
||||||
Note: (@user) means a github user name.
|
Note: (@user) means a github user name.
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import sys
|
|||||||
import imp
|
import imp
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import pkgutil
|
||||||
try:
|
try:
|
||||||
import importlib
|
import importlib
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -18,6 +19,18 @@ is_py35 = is_py3 and sys.version_info.minor >= 5
|
|||||||
is_py26 = not is_py3 and sys.version_info[1] < 7
|
is_py26 = not is_py3 and sys.version_info[1] < 7
|
||||||
|
|
||||||
|
|
||||||
|
class DummyFile(object):
|
||||||
|
def __init__(self, loader, string):
|
||||||
|
self.loader = loader
|
||||||
|
self.string = string
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
return self.loader.get_source(self.string)
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
del self.loader
|
||||||
|
|
||||||
|
|
||||||
def find_module_py33(string, path=None):
|
def find_module_py33(string, path=None):
|
||||||
loader = importlib.machinery.PathFinder.find_module(string, path)
|
loader = importlib.machinery.PathFinder.find_module(string, path)
|
||||||
|
|
||||||
@@ -35,30 +48,73 @@ def find_module_py33(string, path=None):
|
|||||||
try:
|
try:
|
||||||
is_package = loader.is_package(string)
|
is_package = loader.is_package(string)
|
||||||
if is_package:
|
if is_package:
|
||||||
module_path = os.path.dirname(loader.path)
|
if hasattr(loader, 'path'):
|
||||||
module_file = None
|
module_path = os.path.dirname(loader.path)
|
||||||
|
else:
|
||||||
|
# At least zipimporter does not have path attribute
|
||||||
|
module_path = os.path.dirname(loader.get_filename(string))
|
||||||
|
if hasattr(loader, 'archive'):
|
||||||
|
module_file = DummyFile(loader, string)
|
||||||
|
else:
|
||||||
|
module_file = None
|
||||||
else:
|
else:
|
||||||
module_path = loader.get_filename(string)
|
module_path = loader.get_filename(string)
|
||||||
module_file = open(module_path, 'rb')
|
module_file = DummyFile(loader, string)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# ExtensionLoader has not attribute get_filename, instead it has a
|
# ExtensionLoader has not attribute get_filename, instead it has a
|
||||||
# path attribute that we can use to retrieve the module path
|
# path attribute that we can use to retrieve the module path
|
||||||
try:
|
try:
|
||||||
module_path = loader.path
|
module_path = loader.path
|
||||||
module_file = open(loader.path, 'rb')
|
module_file = DummyFile(loader, string)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
module_path = string
|
module_path = string
|
||||||
module_file = None
|
module_file = None
|
||||||
finally:
|
finally:
|
||||||
is_package = False
|
is_package = False
|
||||||
|
|
||||||
|
if hasattr(loader, 'archive'):
|
||||||
|
module_path = loader.archive
|
||||||
|
|
||||||
return module_file, module_path, is_package
|
return module_file, module_path, is_package
|
||||||
|
|
||||||
|
|
||||||
def find_module_pre_py33(string, path=None):
|
def find_module_pre_py33(string, path=None):
|
||||||
module_file, module_path, description = imp.find_module(string, path)
|
try:
|
||||||
module_type = description[2]
|
module_file, module_path, description = imp.find_module(string, path)
|
||||||
return module_file, module_path, module_type is imp.PKG_DIRECTORY
|
module_type = description[2]
|
||||||
|
return module_file, module_path, module_type is imp.PKG_DIRECTORY
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if path is None:
|
||||||
|
path = sys.path
|
||||||
|
for item in path:
|
||||||
|
loader = pkgutil.get_importer(item)
|
||||||
|
if loader:
|
||||||
|
try:
|
||||||
|
loader = loader.find_module(string)
|
||||||
|
if loader:
|
||||||
|
is_package = loader.is_package(string)
|
||||||
|
is_archive = hasattr(loader, 'archive')
|
||||||
|
try:
|
||||||
|
module_path = loader.get_filename(string)
|
||||||
|
except AttributeError:
|
||||||
|
# fallback for py26
|
||||||
|
try:
|
||||||
|
module_path = loader._get_filename(string)
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
if is_package:
|
||||||
|
module_path = os.path.dirname(module_path)
|
||||||
|
if is_archive:
|
||||||
|
module_path = loader.archive
|
||||||
|
file = None
|
||||||
|
if not is_package or is_archive:
|
||||||
|
file = DummyFile(loader, string)
|
||||||
|
return (file, module_path, is_package)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
raise ImportError("No module named {0}".format(string))
|
||||||
|
|
||||||
|
|
||||||
find_module = find_module_py33 if is_py33 else find_module_pre_py33
|
find_module = find_module_py33 if is_py33 else find_module_pre_py33
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class Completion:
|
|||||||
self._code_lines = code_lines
|
self._code_lines = code_lines
|
||||||
|
|
||||||
# The first step of completions is to get the name
|
# The first step of completions is to get the name
|
||||||
self._like_name = helpers.get_on_completion_name(code_lines, position)
|
self._like_name = helpers.get_on_completion_name(module, code_lines, position)
|
||||||
# The actual cursor position is not what we need to calculate
|
# The actual cursor position is not what we need to calculate
|
||||||
# everything. We want the start of the name we're on.
|
# everything. We want the start of the name we're on.
|
||||||
self._position = position[0], position[1] - len(self._like_name)
|
self._position = position[0], position[1] - len(self._like_name)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from jedi.evaluate.helpers import call_of_leaf
|
|||||||
from jedi import parser
|
from jedi import parser
|
||||||
from jedi.parser import tokenize, token
|
from jedi.parser import tokenize, token
|
||||||
from jedi.cache import time_cache
|
from jedi.cache import time_cache
|
||||||
|
from jedi import common
|
||||||
|
|
||||||
|
|
||||||
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
||||||
@@ -20,12 +21,18 @@ def sorted_definitions(defs):
|
|||||||
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0))
|
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0))
|
||||||
|
|
||||||
|
|
||||||
def get_on_completion_name(lines, position):
|
def get_on_completion_name(module, lines, position):
|
||||||
line = lines[position[0] - 1]
|
leaf = module.get_leaf_for_position(position)
|
||||||
# The first step of completions is to get the name
|
if leaf is None or leaf.type in ('string', 'error_leaf'):
|
||||||
return re.search(
|
# Completions inside strings are a bit special, we need to parse the
|
||||||
r'(?!\d)\w+$|$', line[:position[1]]
|
# string. The same is true for comments and error_leafs.
|
||||||
).group(0)
|
line = lines[position[0] - 1]
|
||||||
|
# The first step of completions is to get the name
|
||||||
|
return re.search(r'(?!\d)\w+$|$', line[:position[1]]).group(0)
|
||||||
|
elif leaf.type not in ('name', 'keyword'):
|
||||||
|
return ''
|
||||||
|
|
||||||
|
return leaf.value[:position[1] - leaf.start_pos[1]]
|
||||||
|
|
||||||
|
|
||||||
def _get_code(code_lines, start_pos, end_pos):
|
def _get_code(code_lines, start_pos, end_pos):
|
||||||
@@ -44,71 +51,107 @@ class OnErrorLeaf(Exception):
|
|||||||
return self.args[0]
|
return self.args[0]
|
||||||
|
|
||||||
|
|
||||||
|
def _is_on_comment(leaf, position):
|
||||||
|
# We might be on a comment.
|
||||||
|
if leaf.type == 'endmarker':
|
||||||
|
try:
|
||||||
|
dedent = leaf.get_previous_leaf()
|
||||||
|
if dedent.type == 'dedent' and dedent.prefix:
|
||||||
|
# TODO This is needed because the fast parser uses multiple
|
||||||
|
# endmarker tokens within a file which is obviously ugly.
|
||||||
|
# This is so ugly that I'm not even commenting how it exactly
|
||||||
|
# happens, but let me tell you that I want to get rid of it.
|
||||||
|
leaf = dedent
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
comment_lines = common.splitlines(leaf.prefix)
|
||||||
|
difference = leaf.start_pos[0] - position[0]
|
||||||
|
prefix_start_pos = leaf.get_start_pos_of_prefix()
|
||||||
|
if difference == 0:
|
||||||
|
indent = leaf.start_pos[1]
|
||||||
|
elif position[0] == prefix_start_pos[0]:
|
||||||
|
indent = prefix_start_pos[1]
|
||||||
|
else:
|
||||||
|
indent = 0
|
||||||
|
line = comment_lines[-difference - 1][:position[1] - indent]
|
||||||
|
return '#' in line
|
||||||
|
|
||||||
|
|
||||||
|
def _get_code_for_stack(code_lines, module, position):
|
||||||
|
leaf = module.get_leaf_for_position(position, include_prefixes=True)
|
||||||
|
# It might happen that we're on whitespace or on a comment. This means
|
||||||
|
# that we would not get the right leaf.
|
||||||
|
if leaf.start_pos >= position:
|
||||||
|
if _is_on_comment(leaf, position):
|
||||||
|
return u('')
|
||||||
|
|
||||||
|
# If we're not on a comment simply get the previous leaf and proceed.
|
||||||
|
try:
|
||||||
|
leaf = leaf.get_previous_leaf()
|
||||||
|
except IndexError:
|
||||||
|
return u('') # At the beginning of the file.
|
||||||
|
|
||||||
|
is_after_newline = leaf.type == 'newline'
|
||||||
|
while leaf.type == 'newline':
|
||||||
|
try:
|
||||||
|
leaf = leaf.get_previous_leaf()
|
||||||
|
except IndexError:
|
||||||
|
return u('')
|
||||||
|
|
||||||
|
if leaf.type in ('indent', 'dedent'):
|
||||||
|
return u('')
|
||||||
|
elif leaf.type == 'error_leaf' or leaf.type == 'string':
|
||||||
|
# Error leafs cannot be parsed, completion in strings is also
|
||||||
|
# impossible.
|
||||||
|
raise OnErrorLeaf(leaf)
|
||||||
|
else:
|
||||||
|
if leaf == ';':
|
||||||
|
user_stmt = leaf.parent
|
||||||
|
else:
|
||||||
|
user_stmt = leaf.get_definition()
|
||||||
|
if user_stmt.parent.type == 'simple_stmt':
|
||||||
|
user_stmt = user_stmt.parent
|
||||||
|
|
||||||
|
if is_after_newline:
|
||||||
|
if user_stmt.start_pos[1] > position[1]:
|
||||||
|
# This means that it's actually a dedent and that means that we
|
||||||
|
# start without context (part of a suite).
|
||||||
|
return u('')
|
||||||
|
|
||||||
|
# This is basically getting the relevant lines.
|
||||||
|
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
|
||||||
|
|
||||||
|
|
||||||
def get_stack_at_position(grammar, code_lines, module, pos):
|
def get_stack_at_position(grammar, code_lines, module, pos):
|
||||||
"""
|
"""
|
||||||
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
|
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
|
||||||
"""
|
"""
|
||||||
user_stmt = module.get_statement_for_position(pos)
|
|
||||||
|
|
||||||
if user_stmt is not None and user_stmt.type in ('indent', 'dedent'):
|
|
||||||
code = u('')
|
|
||||||
else:
|
|
||||||
if user_stmt is None:
|
|
||||||
user_stmt = module.get_leaf_for_position(pos, include_prefixes=True)
|
|
||||||
if pos <= user_stmt.start_pos:
|
|
||||||
try:
|
|
||||||
leaf = user_stmt.get_previous_leaf()
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
user_stmt = module.get_statement_for_position(leaf.start_pos)
|
|
||||||
|
|
||||||
if user_stmt.type == 'error_leaf' or user_stmt.type == 'string':
|
|
||||||
# Error leafs cannot be parsed, completion in strings is also
|
|
||||||
# impossible.
|
|
||||||
raise OnErrorLeaf(user_stmt)
|
|
||||||
|
|
||||||
start_pos = user_stmt.start_pos
|
|
||||||
if user_stmt.first_leaf() == '@':
|
|
||||||
# TODO this once again proves that just using user_stmt.get_code
|
|
||||||
# would probably be nicer than _get_code.
|
|
||||||
# Remove the indent to have a statement that is aligned (properties
|
|
||||||
# on the same line as function)
|
|
||||||
start_pos = start_pos[0], 0
|
|
||||||
|
|
||||||
code = _get_code(code_lines, start_pos, pos)
|
|
||||||
if code == ';':
|
|
||||||
# ; cannot be parsed.
|
|
||||||
code = u('')
|
|
||||||
|
|
||||||
# Remove whitespace at the end. Necessary, because the tokenizer will parse
|
|
||||||
# an error token (there's no new line at the end in our case). This doesn't
|
|
||||||
# alter any truth about the valid tokens at that position.
|
|
||||||
code = code.rstrip('\t ')
|
|
||||||
# Remove as many indents from **all** code lines as possible.
|
|
||||||
code = dedent(code)
|
|
||||||
|
|
||||||
class EndMarkerReached(Exception):
|
class EndMarkerReached(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def tokenize_without_endmarker(code):
|
def tokenize_without_endmarker(code):
|
||||||
tokens = tokenize.source_tokens(code, use_exact_op_types=True)
|
tokens = tokenize.source_tokens(code, use_exact_op_types=True)
|
||||||
for token_ in tokens:
|
for token_ in tokens:
|
||||||
if token_[0] == token.ENDMARKER:
|
if token_.string == safeword:
|
||||||
raise EndMarkerReached()
|
raise EndMarkerReached()
|
||||||
elif token_[0] == token.DEDENT:
|
|
||||||
# Ignore those. Error statements should not contain them, if
|
|
||||||
# they do it's for cases where an indentation happens and
|
|
||||||
# before the endmarker we still see them.
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
yield token_
|
yield token_
|
||||||
|
|
||||||
p = parser.Parser(grammar, code, start_parsing=False)
|
code = _get_code_for_stack(code_lines, module, pos)
|
||||||
|
# We use a word to tell Jedi when we have reached the start of the
|
||||||
|
# completion.
|
||||||
|
# Use Z as a prefix because it's not part of a number suffix.
|
||||||
|
safeword = 'ZZZ_USER_WANTS_TO_COMPLETE_HERE_WITH_JEDI'
|
||||||
|
# Remove as many indents from **all** code lines as possible.
|
||||||
|
code = code + safeword
|
||||||
|
|
||||||
|
p = parser.ParserWithRecovery(grammar, code, start_parsing=False)
|
||||||
try:
|
try:
|
||||||
p.parse(tokenizer=tokenize_without_endmarker(code))
|
p.parse(tokenizer=tokenize_without_endmarker(code))
|
||||||
except EndMarkerReached:
|
except EndMarkerReached:
|
||||||
return Stack(p.stack)
|
return Stack(p.stack)
|
||||||
|
raise SystemError("This really shouldn't happen. There's a bug in Jedi.")
|
||||||
|
|
||||||
|
|
||||||
class Stack(list):
|
class Stack(list):
|
||||||
|
|||||||
@@ -128,8 +128,8 @@ def source_to_unicode(source, encoding=None):
|
|||||||
# UTF-8 byte-order mark
|
# UTF-8 byte-order mark
|
||||||
return 'utf-8'
|
return 'utf-8'
|
||||||
|
|
||||||
first_two_lines = re.match(r'(?:[^\n]*\n){0,2}', str(source)).group(0)
|
first_two_lines = re.match(br'(?:[^\n]*\n){0,2}', source).group(0)
|
||||||
possible_encoding = re.search(r"coding[=:]\s*([-\w.]+)",
|
possible_encoding = re.search(br"coding[=:]\s*([-\w.]+)",
|
||||||
first_two_lines)
|
first_two_lines)
|
||||||
if possible_encoding:
|
if possible_encoding:
|
||||||
return possible_encoding.group(1)
|
return possible_encoding.group(1)
|
||||||
@@ -141,8 +141,11 @@ def source_to_unicode(source, encoding=None):
|
|||||||
# only cast str/bytes
|
# only cast str/bytes
|
||||||
return source
|
return source
|
||||||
|
|
||||||
|
encoding = detect_encoding()
|
||||||
|
if not isinstance(encoding, unicode):
|
||||||
|
encoding = unicode(encoding, 'utf-8', 'replace')
|
||||||
# cast to unicode by default
|
# cast to unicode by default
|
||||||
return unicode(source, detect_encoding(), 'replace')
|
return unicode(source, encoding, 'replace')
|
||||||
|
|
||||||
|
|
||||||
def splitlines(string):
|
def splitlines(string):
|
||||||
@@ -158,3 +161,9 @@ def splitlines(string):
|
|||||||
def unite(iterable):
|
def unite(iterable):
|
||||||
"""Turns a two dimensional array into a one dimensional."""
|
"""Turns a two dimensional array into a one dimensional."""
|
||||||
return set(chain.from_iterable(iterable))
|
return set(chain.from_iterable(iterable))
|
||||||
|
|
||||||
|
|
||||||
|
def to_list(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
return list(func(*args, **kwargs))
|
||||||
|
return wrapper
|
||||||
|
|||||||
@@ -86,14 +86,9 @@ def increase_indent(func):
|
|||||||
|
|
||||||
def dbg(message, *args, **kwargs):
|
def dbg(message, *args, **kwargs):
|
||||||
""" Looks at the stack, to see if a debug message should be printed. """
|
""" Looks at the stack, to see if a debug message should be printed. """
|
||||||
if kwargs:
|
# Python 2 compatibility, because it doesn't understand default args
|
||||||
# Python 2 compatibility, because it doesn't understand default args
|
color = kwargs.pop('color', 'GREEN')
|
||||||
# after *args.
|
assert color
|
||||||
color = kwargs.get('color')
|
|
||||||
if color is None:
|
|
||||||
raise TypeError("debug.dbg doesn't support more named arguments than color")
|
|
||||||
else:
|
|
||||||
color = 'GREEN'
|
|
||||||
|
|
||||||
if debug_function and enable_notice:
|
if debug_function and enable_notice:
|
||||||
frm = inspect.stack()[1]
|
frm = inspect.stack()[1]
|
||||||
@@ -104,10 +99,15 @@ def dbg(message, *args, **kwargs):
|
|||||||
debug_function(color, i + 'dbg: ' + message % tuple(u(repr(a)) for a in args))
|
debug_function(color, i + 'dbg: ' + message % tuple(u(repr(a)) for a in args))
|
||||||
|
|
||||||
|
|
||||||
def warning(message, *args):
|
def warning(message, *args, **kwargs):
|
||||||
|
format = kwargs.pop('format', True)
|
||||||
|
assert not kwargs
|
||||||
|
|
||||||
if debug_function and enable_warning:
|
if debug_function and enable_warning:
|
||||||
i = ' ' * _debug_indent
|
i = ' ' * _debug_indent
|
||||||
debug_function('RED', i + 'warning: ' + message % tuple(u(repr(a)) for a in args))
|
if format:
|
||||||
|
message = message % tuple(u(repr(a)) for a in args)
|
||||||
|
debug_function('RED', i + 'warning: ' + message)
|
||||||
|
|
||||||
|
|
||||||
def speed(name):
|
def speed(name):
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ class Evaluator(object):
|
|||||||
return self._eval_element_not_cached(element)
|
return self._eval_element_not_cached(element)
|
||||||
return self._eval_element_cached(element)
|
return self._eval_element_cached(element)
|
||||||
|
|
||||||
@memoize_default(evaluator_is_first_arg=True)
|
@memoize_default(default=set(), evaluator_is_first_arg=True)
|
||||||
def _eval_element_cached(self, element):
|
def _eval_element_cached(self, element):
|
||||||
return self._eval_element_not_cached(element)
|
return self._eval_element_not_cached(element)
|
||||||
|
|
||||||
|
|||||||
@@ -91,7 +91,7 @@ def add(evaluator, name, jedi_obj, message=None, typ=Error, payload=None):
|
|||||||
|
|
||||||
module_path = jedi_obj.get_parent_until().path
|
module_path = jedi_obj.get_parent_until().path
|
||||||
instance = typ(name, module_path, jedi_obj.start_pos, message)
|
instance = typ(name, module_path, jedi_obj.start_pos, message)
|
||||||
debug.warning(str(instance))
|
debug.warning(str(instance), format=False)
|
||||||
evaluator.analysis.append(instance)
|
evaluator.analysis.append(instance)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ def get_faked(module, obj, name=None):
|
|||||||
doc = '"""%s"""' % obj.__doc__ # TODO need escapes.
|
doc = '"""%s"""' % obj.__doc__ # TODO need escapes.
|
||||||
suite = result.children[-1]
|
suite = result.children[-1]
|
||||||
string = pt.String(pt.zero_position_modifier, doc, (0, 0), '')
|
string = pt.String(pt.zero_position_modifier, doc, (0, 0), '')
|
||||||
new_line = pt.Whitespace('\n', (0, 0), '')
|
new_line = pt.Newline('\n', (0, 0), '')
|
||||||
docstr_node = pt.Node('simple_stmt', [string, new_line])
|
docstr_node = pt.Node('simple_stmt', [string, new_line])
|
||||||
suite.children.insert(2, docstr_node)
|
suite.children.insert(2, docstr_node)
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ from textwrap import dedent
|
|||||||
|
|
||||||
from jedi.evaluate.cache import memoize_default
|
from jedi.evaluate.cache import memoize_default
|
||||||
from jedi.parser import ParserWithRecovery, load_grammar
|
from jedi.parser import ParserWithRecovery, load_grammar
|
||||||
|
from jedi.parser.tree import Class
|
||||||
from jedi.common import indent_block
|
from jedi.common import indent_block
|
||||||
from jedi.evaluate.iterable import Array, FakeSequence, AlreadyEvaluated
|
from jedi.evaluate.iterable import Array, FakeSequence, AlreadyEvaluated
|
||||||
|
|
||||||
@@ -174,13 +175,21 @@ def _execute_array_values(evaluator, array):
|
|||||||
|
|
||||||
@memoize_default(None, evaluator_is_first_arg=True)
|
@memoize_default(None, evaluator_is_first_arg=True)
|
||||||
def follow_param(evaluator, param):
|
def follow_param(evaluator, param):
|
||||||
|
def eval_docstring(docstring):
|
||||||
|
return set(
|
||||||
|
[p for param_str in _search_param_in_docstr(docstring, str(param.name))
|
||||||
|
for p in _evaluate_for_statement_string(evaluator, param_str, module)]
|
||||||
|
)
|
||||||
func = param.parent_function
|
func = param.parent_function
|
||||||
|
module = param.get_parent_until()
|
||||||
|
|
||||||
return set(
|
types = eval_docstring(func.raw_doc)
|
||||||
[p for param_str in _search_param_in_docstr(func.raw_doc,
|
if func.name.value == '__init__':
|
||||||
str(param.name))
|
cls = func.get_parent_until(Class)
|
||||||
for p in _evaluate_for_statement_string(evaluator, param_str,
|
if cls.type == 'classdef':
|
||||||
param.get_parent_until())])
|
types |= eval_docstring(cls.raw_doc)
|
||||||
|
|
||||||
|
return types
|
||||||
|
|
||||||
|
|
||||||
@memoize_default(None, evaluator_is_first_arg=True)
|
@memoize_default(None, evaluator_is_first_arg=True)
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ def deep_ast_copy(obj, parent=None, new_elements=None):
|
|||||||
new_children = []
|
new_children = []
|
||||||
for child in obj.children:
|
for child in obj.children:
|
||||||
typ = child.type
|
typ = child.type
|
||||||
if typ in ('whitespace', 'operator', 'keyword', 'number', 'string',
|
if typ in ('newline', 'operator', 'keyword', 'number', 'string',
|
||||||
'indent', 'dedent', 'error_leaf'):
|
'indent', 'dedent', 'endmarker', 'error_leaf'):
|
||||||
# At the moment we're not actually copying those primitive
|
# At the moment we're not actually copying those primitive
|
||||||
# elements, because there's really no need to. The parents are
|
# elements, because there's really no need to. The parents are
|
||||||
# obviously wrong, but that's not an issue.
|
# obviously wrong, but that's not an issue.
|
||||||
|
|||||||
@@ -68,56 +68,49 @@ class ImportWrapper(tree.Base):
|
|||||||
|
|
||||||
@memoize_default()
|
@memoize_default()
|
||||||
def follow(self, is_goto=False):
|
def follow(self, is_goto=False):
|
||||||
if self._evaluator.recursion_detector.push_stmt(self._import):
|
module = self._evaluator.wrap(self._import.get_parent_until())
|
||||||
# check recursion
|
import_path = self._import.path_for_name(self._name)
|
||||||
return set()
|
from_import_name = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
module = self._evaluator.wrap(self._import.get_parent_until())
|
from_names = self._import.get_from_names()
|
||||||
import_path = self._import.path_for_name(self._name)
|
except AttributeError:
|
||||||
from_import_name = None
|
# Is an import_name
|
||||||
try:
|
pass
|
||||||
from_names = self._import.get_from_names()
|
else:
|
||||||
except AttributeError:
|
if len(from_names) + 1 == len(import_path):
|
||||||
# Is an import_name
|
# We have to fetch the from_names part first and then check
|
||||||
pass
|
# if from_names exists in the modules.
|
||||||
else:
|
from_import_name = import_path[-1]
|
||||||
if len(from_names) + 1 == len(import_path):
|
import_path = from_names
|
||||||
# We have to fetch the from_names part first and then check
|
|
||||||
# if from_names exists in the modules.
|
|
||||||
from_import_name = import_path[-1]
|
|
||||||
import_path = from_names
|
|
||||||
|
|
||||||
importer = Importer(self._evaluator, tuple(import_path),
|
importer = Importer(self._evaluator, tuple(import_path),
|
||||||
module, self._import.level)
|
module, self._import.level)
|
||||||
|
|
||||||
types = importer.follow()
|
types = importer.follow()
|
||||||
|
|
||||||
#if self._import.is_nested() and not self.nested_resolve:
|
#if self._import.is_nested() and not self.nested_resolve:
|
||||||
# scopes = [NestedImportModule(module, self._import)]
|
# scopes = [NestedImportModule(module, self._import)]
|
||||||
|
|
||||||
if from_import_name is not None:
|
if from_import_name is not None:
|
||||||
types = set(chain.from_iterable(
|
types = set(chain.from_iterable(
|
||||||
self._evaluator.find_types(t, unicode(from_import_name),
|
self._evaluator.find_types(t, unicode(from_import_name),
|
||||||
is_goto=is_goto)
|
is_goto=is_goto)
|
||||||
for t in types))
|
for t in types))
|
||||||
|
|
||||||
if not types:
|
if not types:
|
||||||
path = import_path + [from_import_name]
|
path = import_path + [from_import_name]
|
||||||
importer = Importer(self._evaluator, tuple(path),
|
importer = Importer(self._evaluator, tuple(path),
|
||||||
module, self._import.level)
|
module, self._import.level)
|
||||||
types = importer.follow()
|
types = importer.follow()
|
||||||
# goto only accepts `Name`
|
|
||||||
if is_goto:
|
|
||||||
types = set(s.name for s in types)
|
|
||||||
else:
|
|
||||||
# goto only accepts `Name`
|
# goto only accepts `Name`
|
||||||
if is_goto:
|
if is_goto:
|
||||||
types = set(s.name for s in types)
|
types = set(s.name for s in types)
|
||||||
|
else:
|
||||||
|
# goto only accepts `Name`
|
||||||
|
if is_goto:
|
||||||
|
types = set(s.name for s in types)
|
||||||
|
|
||||||
debug.dbg('after import: %s', types)
|
debug.dbg('after import: %s', types)
|
||||||
finally:
|
|
||||||
self._evaluator.recursion_detector.pop_stmt()
|
|
||||||
return types
|
return types
|
||||||
|
|
||||||
|
|
||||||
@@ -285,20 +278,17 @@ class Importer(object):
|
|||||||
# We can take the first element, because only the os special
|
# We can take the first element, because only the os special
|
||||||
# case yields multiple modules, which is not important for
|
# case yields multiple modules, which is not important for
|
||||||
# further imports.
|
# further imports.
|
||||||
base = list(bases)[0]
|
parent_module = list(bases)[0]
|
||||||
|
|
||||||
# This is a huge exception, we follow a nested import
|
# This is a huge exception, we follow a nested import
|
||||||
# ``os.path``, because it's a very important one in Python
|
# ``os.path``, because it's a very important one in Python
|
||||||
# that is being achieved by messing with ``sys.modules`` in
|
# that is being achieved by messing with ``sys.modules`` in
|
||||||
# ``os``.
|
# ``os``.
|
||||||
if [str(i) for i in import_path] == ['os', 'path']:
|
if [str(i) for i in import_path] == ['os', 'path']:
|
||||||
return self._evaluator.find_types(base, 'path')
|
return self._evaluator.find_types(parent_module, 'path')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# It's possible that by giving it always the sys path (and not
|
paths = parent_module.py__path__()
|
||||||
# the __path__ attribute of the parent, we get wrong results
|
|
||||||
# and nested namespace packages don't work. But I'm not sure.
|
|
||||||
paths = base.py__path__(sys_path)
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# The module is not a package.
|
# The module is not a package.
|
||||||
_add_error(self._evaluator, import_path[-1])
|
_add_error(self._evaluator, import_path[-1])
|
||||||
@@ -318,6 +308,7 @@ class Importer(object):
|
|||||||
_add_error(self._evaluator, import_path[-1])
|
_add_error(self._evaluator, import_path[-1])
|
||||||
return set()
|
return set()
|
||||||
else:
|
else:
|
||||||
|
parent_module = None
|
||||||
try:
|
try:
|
||||||
debug.dbg('search_module %s in %s', import_parts[-1], self.file_path)
|
debug.dbg('search_module %s in %s', import_parts[-1], self.file_path)
|
||||||
# Override the sys.path. It works only good that way.
|
# Override the sys.path. It works only good that way.
|
||||||
@@ -337,15 +328,18 @@ class Importer(object):
|
|||||||
if is_pkg:
|
if is_pkg:
|
||||||
# In this case, we don't have a file yet. Search for the
|
# In this case, we don't have a file yet. Search for the
|
||||||
# __init__ file.
|
# __init__ file.
|
||||||
module_path = get_init_path(module_path)
|
if module_path.endswith(('.zip', '.egg')):
|
||||||
|
source = module_file.loader.get_source(module_name)
|
||||||
|
else:
|
||||||
|
module_path = get_init_path(module_path)
|
||||||
elif module_file:
|
elif module_file:
|
||||||
source = module_file.read()
|
source = module_file.read()
|
||||||
module_file.close()
|
module_file.close()
|
||||||
|
|
||||||
if module_file is None and not module_path.endswith('.py'):
|
if module_file is None and not module_path.endswith(('.py', '.zip', '.egg')):
|
||||||
module = compiled.load_module(self._evaluator, module_path)
|
module = compiled.load_module(self._evaluator, module_path)
|
||||||
else:
|
else:
|
||||||
module = _load_module(self._evaluator, module_path, source, sys_path)
|
module = _load_module(self._evaluator, module_path, source, sys_path, parent_module)
|
||||||
|
|
||||||
if module is None:
|
if module is None:
|
||||||
# The file might raise an ImportError e.g. and therefore not be
|
# The file might raise an ImportError e.g. and therefore not be
|
||||||
@@ -408,7 +402,7 @@ class Importer(object):
|
|||||||
|
|
||||||
# namespace packages
|
# namespace packages
|
||||||
if isinstance(scope, tree.Module) and scope.path.endswith('__init__.py'):
|
if isinstance(scope, tree.Module) and scope.path.endswith('__init__.py'):
|
||||||
paths = scope.py__path__(self.sys_path_with_modifications())
|
paths = scope.py__path__()
|
||||||
names += self._get_module_names(paths)
|
names += self._get_module_names(paths)
|
||||||
|
|
||||||
if only_modules:
|
if only_modules:
|
||||||
@@ -441,10 +435,10 @@ class Importer(object):
|
|||||||
return names
|
return names
|
||||||
|
|
||||||
|
|
||||||
def _load_module(evaluator, path=None, source=None, sys_path=None):
|
def _load_module(evaluator, path=None, source=None, sys_path=None, parent_module=None):
|
||||||
def load(source):
|
def load(source):
|
||||||
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
|
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
|
||||||
if path is not None and path.endswith('.py') \
|
if path is not None and path.endswith(('.py', '.zip', '.egg')) \
|
||||||
and dotted_path not in settings.auto_import_modules:
|
and dotted_path not in settings.auto_import_modules:
|
||||||
if source is None:
|
if source is None:
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
@@ -454,7 +448,8 @@ def _load_module(evaluator, path=None, source=None, sys_path=None):
|
|||||||
p = path
|
p = path
|
||||||
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
|
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
|
||||||
save_parser(path, p)
|
save_parser(path, p)
|
||||||
return p.module
|
from jedi.evaluate.representation import ModuleWrapper
|
||||||
|
return ModuleWrapper(evaluator, p.module, parent_module)
|
||||||
|
|
||||||
if sys_path is None:
|
if sys_path is None:
|
||||||
sys_path = evaluator.sys_path
|
sys_path = evaluator.sys_path
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from jedi.evaluate import helpers
|
|||||||
from jedi.evaluate.cache import CachedMetaClass, memoize_default
|
from jedi.evaluate.cache import CachedMetaClass, memoize_default
|
||||||
from jedi.evaluate import analysis
|
from jedi.evaluate import analysis
|
||||||
from jedi.evaluate import pep0484
|
from jedi.evaluate import pep0484
|
||||||
|
from jedi import common
|
||||||
|
|
||||||
|
|
||||||
class IterableWrapper(tree.Base):
|
class IterableWrapper(tree.Base):
|
||||||
@@ -180,36 +181,44 @@ class Comprehension(IterableWrapper):
|
|||||||
"""
|
"""
|
||||||
comp_for = self._get_comp_for()
|
comp_for = self._get_comp_for()
|
||||||
# For nested comprehensions we need to search the last one.
|
# For nested comprehensions we need to search the last one.
|
||||||
|
from jedi.evaluate.representation import InstanceElement
|
||||||
|
node = self._get_comprehension().children[index]
|
||||||
|
if isinstance(node, InstanceElement):
|
||||||
|
# This seems to be a strange case that I haven't found a way to
|
||||||
|
# write tests against. However since it's my new goal to get rid of
|
||||||
|
# InstanceElement anyway, I don't care.
|
||||||
|
node = node.var
|
||||||
last_comp = list(comp_for.get_comp_fors())[-1]
|
last_comp = list(comp_for.get_comp_fors())[-1]
|
||||||
return helpers.deep_ast_copy(self._get_comprehension().children[index], parent=last_comp)
|
return helpers.deep_ast_copy(node, parent=last_comp)
|
||||||
|
|
||||||
@memoize_default()
|
|
||||||
def _iterate(self):
|
|
||||||
def nested(comp_fors):
|
|
||||||
comp_for = comp_fors[0]
|
|
||||||
input_node = comp_for.children[3]
|
|
||||||
input_types = evaluator.eval_element(input_node)
|
|
||||||
|
|
||||||
iterated = py__iter__(evaluator, input_types, input_node)
|
|
||||||
exprlist = comp_for.children[1]
|
|
||||||
for types in iterated:
|
|
||||||
evaluator.predefined_if_name_dict_dict[comp_for] = \
|
|
||||||
unpack_tuple_to_dict(evaluator, types, exprlist)
|
|
||||||
try:
|
|
||||||
for result in nested(comp_fors[1:]):
|
|
||||||
yield result
|
|
||||||
except IndexError:
|
|
||||||
iterated = evaluator.eval_element(self._eval_node())
|
|
||||||
if self.type == 'dict':
|
|
||||||
yield iterated, evaluator.eval_element(self._eval_node(2))
|
|
||||||
else:
|
|
||||||
yield iterated
|
|
||||||
finally:
|
|
||||||
del evaluator.predefined_if_name_dict_dict[comp_for]
|
|
||||||
|
|
||||||
|
def _nested(self, comp_fors):
|
||||||
evaluator = self._evaluator
|
evaluator = self._evaluator
|
||||||
comp_fors = list(self._get_comp_for().get_comp_fors())
|
comp_for = comp_fors[0]
|
||||||
for result in nested(comp_fors):
|
input_node = comp_for.children[3]
|
||||||
|
input_types = evaluator.eval_element(input_node)
|
||||||
|
|
||||||
|
iterated = py__iter__(evaluator, input_types, input_node)
|
||||||
|
exprlist = comp_for.children[1]
|
||||||
|
for i, types in enumerate(iterated):
|
||||||
|
evaluator.predefined_if_name_dict_dict[comp_for] = \
|
||||||
|
unpack_tuple_to_dict(evaluator, types, exprlist)
|
||||||
|
try:
|
||||||
|
for result in self._nested(comp_fors[1:]):
|
||||||
|
yield result
|
||||||
|
except IndexError:
|
||||||
|
iterated = evaluator.eval_element(self._eval_node())
|
||||||
|
if self.type == 'dict':
|
||||||
|
yield iterated, evaluator.eval_element(self._eval_node(2))
|
||||||
|
else:
|
||||||
|
yield iterated
|
||||||
|
finally:
|
||||||
|
del evaluator.predefined_if_name_dict_dict[comp_for]
|
||||||
|
|
||||||
|
@memoize_default(default=[])
|
||||||
|
@common.to_list
|
||||||
|
def _iterate(self):
|
||||||
|
comp_fors = tuple(self._get_comp_for().get_comp_fors())
|
||||||
|
for result in self._nested(comp_fors):
|
||||||
yield result
|
yield result
|
||||||
|
|
||||||
def py__iter__(self):
|
def py__iter__(self):
|
||||||
@@ -252,7 +261,7 @@ class ArrayMixin(object):
|
|||||||
@register_builtin_method('values', type='dict')
|
@register_builtin_method('values', type='dict')
|
||||||
def _imitate_values(self):
|
def _imitate_values(self):
|
||||||
items = self.dict_values()
|
items = self.dict_values()
|
||||||
return create_evaluated_sequence_set(self._evaluator, items, type='list')
|
return create_evaluated_sequence_set(self._evaluator, items, sequence_type='list')
|
||||||
#return set([FakeSequence(self._evaluator, [AlreadyEvaluated(items)], 'tuple')])
|
#return set([FakeSequence(self._evaluator, [AlreadyEvaluated(items)], 'tuple')])
|
||||||
|
|
||||||
@register_builtin_method('items', type='dict')
|
@register_builtin_method('items', type='dict')
|
||||||
@@ -260,7 +269,7 @@ class ArrayMixin(object):
|
|||||||
items = [set([FakeSequence(self._evaluator, (k, v), 'tuple')])
|
items = [set([FakeSequence(self._evaluator, (k, v), 'tuple')])
|
||||||
for k, v in self._items()]
|
for k, v in self._items()]
|
||||||
|
|
||||||
return create_evaluated_sequence_set(self._evaluator, *items, type='list')
|
return create_evaluated_sequence_set(self._evaluator, *items, sequence_type='list')
|
||||||
|
|
||||||
|
|
||||||
class ListComprehension(Comprehension, ArrayMixin):
|
class ListComprehension(Comprehension, ArrayMixin):
|
||||||
@@ -268,7 +277,14 @@ class ListComprehension(Comprehension, ArrayMixin):
|
|||||||
|
|
||||||
def py__getitem__(self, index):
|
def py__getitem__(self, index):
|
||||||
all_types = list(self.py__iter__())
|
all_types = list(self.py__iter__())
|
||||||
return all_types[index]
|
result = all_types[index]
|
||||||
|
if isinstance(index, slice):
|
||||||
|
return create_evaluated_sequence_set(
|
||||||
|
self._evaluator,
|
||||||
|
unite(result),
|
||||||
|
sequence_type='list'
|
||||||
|
)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
class SetComprehension(Comprehension, ArrayMixin):
|
class SetComprehension(Comprehension, ArrayMixin):
|
||||||
@@ -303,9 +319,7 @@ class DictComprehension(Comprehension, ArrayMixin):
|
|||||||
(AlreadyEvaluated(keys), AlreadyEvaluated(values)), 'tuple')
|
(AlreadyEvaluated(keys), AlreadyEvaluated(values)), 'tuple')
|
||||||
for keys, values in self._iterate())
|
for keys, values in self._iterate())
|
||||||
|
|
||||||
return create_evaluated_sequence_set(self._evaluator, items, type='list')
|
return create_evaluated_sequence_set(self._evaluator, items, sequence_type='list')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GeneratorComprehension(Comprehension, GeneratorMixin):
|
class GeneratorComprehension(Comprehension, GeneratorMixin):
|
||||||
@@ -446,7 +460,9 @@ def create_evaluated_sequence_set(evaluator, *types_order, **kwargs):
|
|||||||
``sequence_type`` is a named argument, that doesn't work in Python2. For backwards
|
``sequence_type`` is a named argument, that doesn't work in Python2. For backwards
|
||||||
compatibility reasons, we're now using kwargs.
|
compatibility reasons, we're now using kwargs.
|
||||||
"""
|
"""
|
||||||
sequence_type = kwargs.get('sequence_type')
|
sequence_type = kwargs.pop('sequence_type')
|
||||||
|
assert not kwargs
|
||||||
|
|
||||||
sets = tuple(AlreadyEvaluated(types) for types in types_order)
|
sets = tuple(AlreadyEvaluated(types) for types in types_order)
|
||||||
return set([FakeSequence(evaluator, sets, sequence_type)])
|
return set([FakeSequence(evaluator, sets, sequence_type)])
|
||||||
|
|
||||||
@@ -676,7 +692,7 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
|
|||||||
# Arguments([AlreadyEvaluated([_ArrayInstance])]) inside
|
# Arguments([AlreadyEvaluated([_ArrayInstance])]) inside
|
||||||
# Yeah... I know... It's complicated ;-)
|
# Yeah... I know... It's complicated ;-)
|
||||||
node = list(element.var_args.argument_node[0])[0].var_args.trailer
|
node = list(element.var_args.argument_node[0])[0].var_args.trailer
|
||||||
if isinstance(node, er.InstanceElement):
|
if isinstance(node, er.InstanceElement) or node is None:
|
||||||
return node
|
return node
|
||||||
return node.get_parent_until(er.FunctionExecution)
|
return node.get_parent_until(er.FunctionExecution)
|
||||||
|
|
||||||
@@ -729,11 +745,12 @@ def _check_array_additions(evaluator, compare_array, module, is_list):
|
|||||||
# Check for recursion. Possible by using 'extend' in
|
# Check for recursion. Possible by using 'extend' in
|
||||||
# combination with function calls.
|
# combination with function calls.
|
||||||
continue
|
continue
|
||||||
if compare_array in evaluator.eval_element(power):
|
try:
|
||||||
# The arrays match. Now add the results
|
if compare_array in evaluator.eval_element(power):
|
||||||
added_types |= check_additions(execution_trailer.children[1], add_name)
|
# The arrays match. Now add the results
|
||||||
|
added_types |= check_additions(execution_trailer.children[1], add_name)
|
||||||
evaluator.recursion_detector.pop_stmt()
|
finally:
|
||||||
|
evaluator.recursion_detector.pop_stmt()
|
||||||
# reset settings
|
# reset settings
|
||||||
settings.dynamic_params_for_other_modules = temp_param_add
|
settings.dynamic_params_for_other_modules = temp_param_add
|
||||||
debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA')
|
debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA')
|
||||||
@@ -777,6 +794,8 @@ class _ArrayInstance(IterableWrapper):
|
|||||||
yield types
|
yield types
|
||||||
|
|
||||||
module = self.var_args.get_parent_until()
|
module = self.var_args.get_parent_until()
|
||||||
|
if module is None:
|
||||||
|
return
|
||||||
is_list = str(self.instance.name) == 'list'
|
is_list = str(self.instance.name) == 'list'
|
||||||
additions = _check_array_additions(self._evaluator, self.instance, module, is_list)
|
additions = _check_array_additions(self._evaluator, self.instance, module, is_list)
|
||||||
if additions:
|
if additions:
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ from jedi.evaluate import iterable
|
|||||||
def recursion_decorator(func):
|
def recursion_decorator(func):
|
||||||
def run(evaluator, stmt, *args, **kwargs):
|
def run(evaluator, stmt, *args, **kwargs):
|
||||||
rec_detect = evaluator.recursion_detector
|
rec_detect = evaluator.recursion_detector
|
||||||
# print stmt, len(self.node_statements())
|
|
||||||
if rec_detect.push_stmt(stmt):
|
if rec_detect.push_stmt(stmt):
|
||||||
return set()
|
return set()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -472,7 +472,10 @@ class Class(use_metaclass(CachedMetaClass, Wrapper)):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def params(self):
|
def params(self):
|
||||||
return self.get_subscope_by_name('__init__').params
|
try:
|
||||||
|
return self.get_subscope_by_name('__init__').params
|
||||||
|
except KeyError:
|
||||||
|
return [] # object.__init__
|
||||||
|
|
||||||
def names_dicts(self, search_global, is_instance=False):
|
def names_dicts(self, search_global, is_instance=False):
|
||||||
if search_global:
|
if search_global:
|
||||||
@@ -491,7 +494,7 @@ class Class(use_metaclass(CachedMetaClass, Wrapper)):
|
|||||||
for s in self.py__mro__():
|
for s in self.py__mro__():
|
||||||
for sub in reversed(s.subscopes):
|
for sub in reversed(s.subscopes):
|
||||||
if sub.name.value == name:
|
if sub.name.value == name:
|
||||||
return sub
|
return sub
|
||||||
raise KeyError("Couldn't find subscope.")
|
raise KeyError("Couldn't find subscope.")
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
@@ -803,9 +806,10 @@ class GlobalName(helpers.FakeName):
|
|||||||
|
|
||||||
|
|
||||||
class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
||||||
def __init__(self, evaluator, module):
|
def __init__(self, evaluator, module, parent_module=None):
|
||||||
self._evaluator = evaluator
|
self._evaluator = evaluator
|
||||||
self.base = self._module = module
|
self.base = self._module = module
|
||||||
|
self._parent_module = parent_module
|
||||||
|
|
||||||
def names_dicts(self, search_global):
|
def names_dicts(self, search_global):
|
||||||
yield self.base.names_dict
|
yield self.base.names_dict
|
||||||
@@ -851,6 +855,10 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
|||||||
return helpers.FakeName(unicode(self.base.name), self, (1, 0))
|
return helpers.FakeName(unicode(self.base.name), self, (1, 0))
|
||||||
|
|
||||||
def _get_init_directory(self):
|
def _get_init_directory(self):
|
||||||
|
"""
|
||||||
|
:return: The path to the directory of a package. None in case it's not
|
||||||
|
a package.
|
||||||
|
"""
|
||||||
for suffix, _, _ in imp.get_suffixes():
|
for suffix, _, _ in imp.get_suffixes():
|
||||||
ending = '__init__' + suffix
|
ending = '__init__' + suffix
|
||||||
py__file__ = self.py__file__()
|
py__file__ = self.py__file__()
|
||||||
@@ -881,6 +889,30 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
|||||||
else:
|
else:
|
||||||
return self.py__name__()
|
return self.py__name__()
|
||||||
|
|
||||||
|
def _py__path__(self):
|
||||||
|
if self._parent_module is None:
|
||||||
|
search_path = self._evaluator.sys_path
|
||||||
|
else:
|
||||||
|
search_path = self._parent_module.py__path__()
|
||||||
|
init_path = self.py__file__()
|
||||||
|
if os.path.basename(init_path) == '__init__.py':
|
||||||
|
with open(init_path, 'rb') as f:
|
||||||
|
content = common.source_to_unicode(f.read())
|
||||||
|
# these are strings that need to be used for namespace packages,
|
||||||
|
# the first one is ``pkgutil``, the second ``pkg_resources``.
|
||||||
|
options = ('declare_namespace(__name__)', 'extend_path(__path__')
|
||||||
|
if options[0] in content or options[1] in content:
|
||||||
|
# It is a namespace, now try to find the rest of the
|
||||||
|
# modules on sys_path or whatever the search_path is.
|
||||||
|
paths = set()
|
||||||
|
for s in search_path:
|
||||||
|
other = os.path.join(s, unicode(self.name))
|
||||||
|
if os.path.isdir(other):
|
||||||
|
paths.add(other)
|
||||||
|
return list(paths)
|
||||||
|
# Default to this.
|
||||||
|
return [self._get_init_directory()]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def py__path__(self):
|
def py__path__(self):
|
||||||
"""
|
"""
|
||||||
@@ -893,33 +925,12 @@ class ModuleWrapper(use_metaclass(CachedMetaClass, tree.Module, Wrapper)):
|
|||||||
is a list of paths (strings).
|
is a list of paths (strings).
|
||||||
Raises an AttributeError if the module is not a package.
|
Raises an AttributeError if the module is not a package.
|
||||||
"""
|
"""
|
||||||
def return_value(search_path):
|
|
||||||
init_path = self.py__file__()
|
|
||||||
if os.path.basename(init_path) == '__init__.py':
|
|
||||||
|
|
||||||
with open(init_path, 'rb') as f:
|
|
||||||
content = common.source_to_unicode(f.read())
|
|
||||||
# these are strings that need to be used for namespace packages,
|
|
||||||
# the first one is ``pkgutil``, the second ``pkg_resources``.
|
|
||||||
options = ('declare_namespace(__name__)', 'extend_path(__path__')
|
|
||||||
if options[0] in content or options[1] in content:
|
|
||||||
# It is a namespace, now try to find the rest of the
|
|
||||||
# modules on sys_path or whatever the search_path is.
|
|
||||||
paths = set()
|
|
||||||
for s in search_path:
|
|
||||||
other = os.path.join(s, unicode(self.name))
|
|
||||||
if os.path.isdir(other):
|
|
||||||
paths.add(other)
|
|
||||||
return list(paths)
|
|
||||||
# Default to this.
|
|
||||||
return [path]
|
|
||||||
|
|
||||||
path = self._get_init_directory()
|
path = self._get_init_directory()
|
||||||
|
|
||||||
if path is None:
|
if path is None:
|
||||||
raise AttributeError('Only packages have __path__ attributes.')
|
raise AttributeError('Only packages have __path__ attributes.')
|
||||||
else:
|
else:
|
||||||
return return_value
|
return self._py__path__
|
||||||
|
|
||||||
@memoize_default()
|
@memoize_default()
|
||||||
def _sub_modules_dict(self):
|
def _sub_modules_dict(self):
|
||||||
|
|||||||
@@ -214,12 +214,14 @@ class Parser(object):
|
|||||||
return pt.String(self.position_modifier, value, start_pos, prefix)
|
return pt.String(self.position_modifier, value, start_pos, prefix)
|
||||||
elif type == NUMBER:
|
elif type == NUMBER:
|
||||||
return pt.Number(self.position_modifier, value, start_pos, prefix)
|
return pt.Number(self.position_modifier, value, start_pos, prefix)
|
||||||
elif type in (NEWLINE, ENDMARKER):
|
elif type == NEWLINE:
|
||||||
return pt.Whitespace(self.position_modifier, value, start_pos, prefix)
|
return pt.Newline(self.position_modifier, value, start_pos, prefix)
|
||||||
elif type == INDENT:
|
elif type == INDENT:
|
||||||
return pt.Indent(self.position_modifier, value, start_pos, prefix)
|
return pt.Indent(self.position_modifier, value, start_pos, prefix)
|
||||||
elif type == DEDENT:
|
elif type == DEDENT:
|
||||||
return pt.Dedent(self.position_modifier, value, start_pos, prefix)
|
return pt.Dedent(self.position_modifier, value, start_pos, prefix)
|
||||||
|
elif type == ENDMARKER:
|
||||||
|
return pt.EndMarker(self.position_modifier, value, start_pos, prefix)
|
||||||
else:
|
else:
|
||||||
return pt.Operator(self.position_modifier, value, start_pos, prefix)
|
return pt.Operator(self.position_modifier, value, start_pos, prefix)
|
||||||
|
|
||||||
@@ -287,7 +289,8 @@ class ParserWithRecovery(Parser):
|
|||||||
:param module_path: The path of the module in the file system, may be None.
|
:param module_path: The path of the module in the file system, may be None.
|
||||||
:type module_path: str
|
:type module_path: str
|
||||||
"""
|
"""
|
||||||
def __init__(self, grammar, source, module_path=None, tokenizer=None):
|
def __init__(self, grammar, source, module_path=None, tokenizer=None,
|
||||||
|
start_parsing=True):
|
||||||
self.syntax_errors = []
|
self.syntax_errors = []
|
||||||
|
|
||||||
self._omit_dedent_list = []
|
self._omit_dedent_list = []
|
||||||
@@ -302,12 +305,16 @@ class ParserWithRecovery(Parser):
|
|||||||
# if self.options["print_function"]:
|
# if self.options["print_function"]:
|
||||||
# python_grammar = pygram.python_grammar_no_print_statement
|
# python_grammar = pygram.python_grammar_no_print_statement
|
||||||
# else:
|
# else:
|
||||||
super(ParserWithRecovery, self).__init__(grammar, source, tokenizer=tokenizer)
|
super(ParserWithRecovery, self).__init__(
|
||||||
|
grammar, source,
|
||||||
self.module = self._parsed
|
tokenizer=tokenizer,
|
||||||
self.module.used_names = self._used_names
|
start_parsing=start_parsing
|
||||||
self.module.path = module_path
|
)
|
||||||
self.module.global_names = self._global_names
|
if start_parsing:
|
||||||
|
self.module = self._parsed
|
||||||
|
self.module.used_names = self._used_names
|
||||||
|
self.module.path = module_path
|
||||||
|
self.module.global_names = self._global_names
|
||||||
|
|
||||||
def parse(self, tokenizer):
|
def parse(self, tokenizer):
|
||||||
return super(ParserWithRecovery, self).parse(self._tokenize(self._tokenize(tokenizer)))
|
return super(ParserWithRecovery, self).parse(self._tokenize(self._tokenize(tokenizer)))
|
||||||
|
|||||||
@@ -120,6 +120,10 @@ class ParserNode(object):
|
|||||||
self.source = source
|
self.source = source
|
||||||
self.hash = hash(source)
|
self.hash = hash(source)
|
||||||
self.parser = parser
|
self.parser = parser
|
||||||
|
if source:
|
||||||
|
self._end_pos = parser.module.end_pos
|
||||||
|
else:
|
||||||
|
self._end_pos = 1, 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# With fast_parser we have either 1 subscope or only statements.
|
# With fast_parser we have either 1 subscope or only statements.
|
||||||
@@ -162,6 +166,10 @@ class ParserNode(object):
|
|||||||
# There's no module yet.
|
# There's no module yet.
|
||||||
return '<%s: empty>' % type(self).__name__
|
return '<%s: empty>' % type(self).__name__
|
||||||
|
|
||||||
|
@property
|
||||||
|
def end_pos(self):
|
||||||
|
return self._end_pos[0] + self.parser.position_modifier.line, self._end_pos[1]
|
||||||
|
|
||||||
def reset_node(self):
|
def reset_node(self):
|
||||||
"""
|
"""
|
||||||
Removes changes that were applied in this class.
|
Removes changes that were applied in this class.
|
||||||
@@ -188,6 +196,10 @@ class ParserNode(object):
|
|||||||
# Need to insert the own node as well.
|
# Need to insert the own node as well.
|
||||||
dcts.insert(0, self._content_scope.names_dict)
|
dcts.insert(0, self._content_scope.names_dict)
|
||||||
self._content_scope.names_dict = MergedNamesDict(dcts)
|
self._content_scope.names_dict = MergedNamesDict(dcts)
|
||||||
|
endmarker = self.parser.get_parsed_node().children[-1]
|
||||||
|
assert endmarker.type == 'endmarker'
|
||||||
|
last_parser = self._node_children[-1].parser
|
||||||
|
endmarker.start_pos = last_parser.get_parsed_node().end_pos
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _indent(self):
|
def _indent(self):
|
||||||
@@ -414,7 +426,7 @@ class FastParser(use_metaclass(CachedFastParser)):
|
|||||||
# called - just ignore it.
|
# called - just ignore it.
|
||||||
src = ''.join(self._lines[code_part_end_line - 1:])
|
src = ''.join(self._lines[code_part_end_line - 1:])
|
||||||
self._parse_part(code_part, src, code_part_end_line, nodes)
|
self._parse_part(code_part, src, code_part_end_line, nodes)
|
||||||
last_end_line = self.current_node.parser.module.end_pos[0]
|
last_end_line = self.current_node.end_pos[0]
|
||||||
debug.dbg("While parsing %s, starting with line %s wasn't included in split.",
|
debug.dbg("While parsing %s, starting with line %s wasn't included in split.",
|
||||||
self.module_path, code_part_end_line)
|
self.module_path, code_part_end_line)
|
||||||
#assert code_part_end_line > last_end_line
|
#assert code_part_end_line > last_end_line
|
||||||
@@ -426,7 +438,7 @@ class FastParser(use_metaclass(CachedFastParser)):
|
|||||||
code_part_end_line = next_code_part_end_line
|
code_part_end_line = next_code_part_end_line
|
||||||
start += len(code_part)
|
start += len(code_part)
|
||||||
|
|
||||||
last_end_line = self.current_node.parser.module.end_pos[0]
|
last_end_line = self.current_node.end_pos[0]
|
||||||
|
|
||||||
if added_newline:
|
if added_newline:
|
||||||
self.current_node.remove_last_newline()
|
self.current_node.remove_last_newline()
|
||||||
|
|||||||
@@ -147,10 +147,12 @@ class Base(object):
|
|||||||
return scope
|
return scope
|
||||||
|
|
||||||
def get_definition(self):
|
def get_definition(self):
|
||||||
|
if self.type in ('newline', 'dedent', 'indent', 'endmarker'):
|
||||||
|
raise ValueError('Cannot get the indentation of whitespace or indentation.')
|
||||||
scope = self
|
scope = self
|
||||||
while scope.parent is not None:
|
while scope.parent is not None:
|
||||||
parent = scope.parent
|
parent = scope.parent
|
||||||
if scope.isinstance(Node, Name) and parent.type != 'simple_stmt':
|
if scope.isinstance(Node, Leaf) and parent.type != 'simple_stmt':
|
||||||
if scope.type == 'testlist_comp':
|
if scope.type == 'testlist_comp':
|
||||||
try:
|
try:
|
||||||
if isinstance(scope.children[1], CompFor):
|
if isinstance(scope.children[1], CompFor):
|
||||||
@@ -292,7 +294,11 @@ class Leaf(Base):
|
|||||||
|
|
||||||
def get_start_pos_of_prefix(self):
|
def get_start_pos_of_prefix(self):
|
||||||
try:
|
try:
|
||||||
return self.get_previous_leaf().end_pos
|
previous_leaf = self
|
||||||
|
while True:
|
||||||
|
previous_leaf = previous_leaf.get_previous_leaf()
|
||||||
|
if previous_leaf.type not in ('indent', 'dedent'):
|
||||||
|
return previous_leaf.end_pos
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return 1, 0 # It's the first leaf.
|
return 1, 0 # It's the first leaf.
|
||||||
|
|
||||||
@@ -348,10 +354,15 @@ class LeafWithNewLines(Leaf):
|
|||||||
return "<%s: %r>" % (type(self).__name__, self.value)
|
return "<%s: %r>" % (type(self).__name__, self.value)
|
||||||
|
|
||||||
|
|
||||||
class Whitespace(LeafWithNewLines):
|
class EndMarker(Leaf):
|
||||||
|
__slots__ = ()
|
||||||
|
type = 'endmarker'
|
||||||
|
|
||||||
|
|
||||||
|
class Newline(LeafWithNewLines):
|
||||||
"""Contains NEWLINE and ENDMARKER tokens."""
|
"""Contains NEWLINE and ENDMARKER tokens."""
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
type = 'whitespace'
|
type = 'newline'
|
||||||
|
|
||||||
@utf8_repr
|
@utf8_repr
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -421,7 +432,7 @@ class Indent(Leaf):
|
|||||||
|
|
||||||
|
|
||||||
class Dedent(Leaf):
|
class Dedent(Leaf):
|
||||||
type = 'indent'
|
type = 'dedent'
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
|
|
||||||
|
|
||||||
@@ -539,6 +550,10 @@ class BaseNode(Base):
|
|||||||
try:
|
try:
|
||||||
return c.get_leaf_for_position(position, include_prefixes)
|
return c.get_leaf_for_position(position, include_prefixes)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
while c.type in ('indent', 'dedent'):
|
||||||
|
# We'd rather not have indents and dedents as a leaf,
|
||||||
|
# because they don't contain indentation information.
|
||||||
|
c = c.get_next_leaf()
|
||||||
return c
|
return c
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ def setup_readline(namespace_module=__main__):
|
|||||||
|
|
||||||
lines = common.splitlines(text)
|
lines = common.splitlines(text)
|
||||||
position = (len(lines), len(lines[-1]))
|
position = (len(lines), len(lines[-1]))
|
||||||
name = get_on_completion_name(lines, position)
|
name = get_on_completion_name(interpreter._get_module(), lines, position)
|
||||||
before = text[:len(text) - len(name)]
|
before = text[:len(text) - len(name)]
|
||||||
completions = interpreter.completions()
|
completions = interpreter.completions()
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
@@ -311,6 +311,17 @@ for i in 0, 2:
|
|||||||
#? int() str()
|
#? int() str()
|
||||||
GetItemWithList()[i]
|
GetItemWithList()[i]
|
||||||
|
|
||||||
|
|
||||||
|
# With super
|
||||||
|
class SuperYeah(list):
|
||||||
|
def __getitem__(self, index):
|
||||||
|
return super()[index]
|
||||||
|
|
||||||
|
#?
|
||||||
|
SuperYeah([1])[0]
|
||||||
|
#?
|
||||||
|
SuperYeah()[0]
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
# conversions
|
# conversions
|
||||||
# -----------------
|
# -----------------
|
||||||
@@ -368,20 +379,6 @@ for i in set(a for a in [1]):
|
|||||||
i
|
i
|
||||||
|
|
||||||
|
|
||||||
# -----------------
|
|
||||||
# Recursions
|
|
||||||
# -----------------
|
|
||||||
|
|
||||||
def to_list(iterable):
|
|
||||||
return list(set(iterable))
|
|
||||||
|
|
||||||
|
|
||||||
def recursion1(foo):
|
|
||||||
return to_list(to_list(foo)) + recursion1(foo)
|
|
||||||
|
|
||||||
#? int()
|
|
||||||
recursion1([1,2])[0]
|
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
# Merged Arrays
|
# Merged Arrays
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|||||||
@@ -190,6 +190,16 @@ def a():
|
|||||||
#?
|
#?
|
||||||
# str literals in comment """ upper
|
# str literals in comment """ upper
|
||||||
|
|
||||||
|
def completion_in_comment():
|
||||||
|
#? ['Exception']
|
||||||
|
# might fail because the comment is not a leaf: Exception
|
||||||
|
pass
|
||||||
|
|
||||||
|
some_word
|
||||||
|
#? ['Exception']
|
||||||
|
# Very simple comment completion: Exception
|
||||||
|
# Commment after it
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
# magic methods
|
# magic methods
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|||||||
@@ -294,20 +294,6 @@ class A():
|
|||||||
#? list()
|
#? list()
|
||||||
A().b()
|
A().b()
|
||||||
|
|
||||||
# -----------------
|
|
||||||
# recursions
|
|
||||||
# -----------------
|
|
||||||
def Recursion():
|
|
||||||
def recurse(self):
|
|
||||||
self.a = self.a
|
|
||||||
self.b = self.b.recurse()
|
|
||||||
|
|
||||||
#?
|
|
||||||
Recursion().a
|
|
||||||
|
|
||||||
#?
|
|
||||||
Recursion().b
|
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
# ducktyping
|
# ducktyping
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|||||||
26
test/completion/completion.py
Normal file
26
test/completion/completion.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""
|
||||||
|
Special cases of completions (typically special positions that caused issues
|
||||||
|
with context parsing.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def pass_decorator(func):
|
||||||
|
return func
|
||||||
|
|
||||||
|
|
||||||
|
def x():
|
||||||
|
return (
|
||||||
|
1,
|
||||||
|
#? ["tuple"]
|
||||||
|
tuple
|
||||||
|
)
|
||||||
|
|
||||||
|
# Comment just somewhere
|
||||||
|
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
@pass_decorator
|
||||||
|
def x(foo,
|
||||||
|
#? 5 ["tuple"]
|
||||||
|
tuple,
|
||||||
|
):
|
||||||
|
return 1
|
||||||
@@ -173,4 +173,28 @@ def x():
|
|||||||
[a for a in h if hio]
|
[a for a in h if hio]
|
||||||
if hio: pass
|
if hio: pass
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# slices
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
#? list()
|
||||||
|
foo = [x for x in [1, '']][:1]
|
||||||
|
#? int()
|
||||||
|
foo[0]
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# In class
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
class X():
|
||||||
|
def __init__(self, bar):
|
||||||
|
self.bar = bar
|
||||||
|
|
||||||
|
def foo(self):
|
||||||
|
x = [a for a in self.bar][0]
|
||||||
|
#? int()
|
||||||
|
x
|
||||||
|
return x
|
||||||
|
|
||||||
|
#? int()
|
||||||
|
X([1]).foo()
|
||||||
|
|||||||
@@ -177,3 +177,44 @@ d = ''
|
|||||||
""" bsdf """
|
""" bsdf """
|
||||||
#? str()
|
#? str()
|
||||||
d.upper()
|
d.upper()
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# class docstrings
|
||||||
|
# -----------------
|
||||||
|
|
||||||
|
class InInit():
|
||||||
|
def __init__(self, foo):
|
||||||
|
"""
|
||||||
|
:type foo: str
|
||||||
|
"""
|
||||||
|
#? str()
|
||||||
|
foo
|
||||||
|
|
||||||
|
|
||||||
|
class InClass():
|
||||||
|
"""
|
||||||
|
:type foo: str
|
||||||
|
"""
|
||||||
|
def __init__(self, foo):
|
||||||
|
#? str()
|
||||||
|
foo
|
||||||
|
|
||||||
|
|
||||||
|
class InBoth():
|
||||||
|
"""
|
||||||
|
:type foo: str
|
||||||
|
"""
|
||||||
|
def __init__(self, foo):
|
||||||
|
"""
|
||||||
|
:type foo: int
|
||||||
|
"""
|
||||||
|
#? str() int()
|
||||||
|
foo
|
||||||
|
|
||||||
|
|
||||||
|
def __init__(foo):
|
||||||
|
"""
|
||||||
|
:type foo: str
|
||||||
|
"""
|
||||||
|
#? str()
|
||||||
|
foo
|
||||||
|
|||||||
@@ -102,6 +102,13 @@ def f(t=None):
|
|||||||
#! 9 ['t=None']
|
#! 9 ['t=None']
|
||||||
t = t or 1
|
t = t or 1
|
||||||
|
|
||||||
|
|
||||||
|
class X():
|
||||||
|
pass
|
||||||
|
|
||||||
|
#! 3 []
|
||||||
|
X(foo=x)
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
# imports
|
# imports
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|||||||
51
test/completion/recursion.py
Normal file
51
test/completion/recursion.py
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
"""
|
||||||
|
Code that might cause recursion issues (or has caused in the past).
|
||||||
|
"""
|
||||||
|
|
||||||
|
def Recursion():
|
||||||
|
def recurse(self):
|
||||||
|
self.a = self.a
|
||||||
|
self.b = self.b.recurse()
|
||||||
|
|
||||||
|
#?
|
||||||
|
Recursion().a
|
||||||
|
|
||||||
|
#?
|
||||||
|
Recursion().b
|
||||||
|
|
||||||
|
|
||||||
|
class X():
|
||||||
|
def __init__(self):
|
||||||
|
self.recursive = [1, 3]
|
||||||
|
|
||||||
|
def annoying(self):
|
||||||
|
self.recursive = [self.recursive[0]]
|
||||||
|
|
||||||
|
def recurse(self):
|
||||||
|
self.recursive = [self.recursive[1]]
|
||||||
|
|
||||||
|
#? int()
|
||||||
|
X().recursive[0]
|
||||||
|
|
||||||
|
|
||||||
|
def to_list(iterable):
|
||||||
|
return list(set(iterable))
|
||||||
|
|
||||||
|
|
||||||
|
def recursion1(foo):
|
||||||
|
return to_list(to_list(foo)) + recursion1(foo)
|
||||||
|
|
||||||
|
#? int()
|
||||||
|
recursion1([1,2])[0]
|
||||||
|
|
||||||
|
|
||||||
|
class FooListComp():
|
||||||
|
def __init__(self):
|
||||||
|
self.recursive = [1]
|
||||||
|
|
||||||
|
def annoying(self):
|
||||||
|
self.recursive = [x for x in self.recursive]
|
||||||
|
|
||||||
|
|
||||||
|
#? int()
|
||||||
|
FooListComp().recursive[0]
|
||||||
@@ -83,7 +83,7 @@ def test_completion_on_hex_literals():
|
|||||||
_check_number('0x1.', 'int') # hexdecimal
|
_check_number('0x1.', 'int') # hexdecimal
|
||||||
# Completing binary literals doesn't work if they are not actually binary
|
# Completing binary literals doesn't work if they are not actually binary
|
||||||
# (invalid statements).
|
# (invalid statements).
|
||||||
assert api.Script('0b2.').completions() == []
|
assert api.Script('0b2.b').completions() == []
|
||||||
_check_number('0b1.', 'int') # binary
|
_check_number('0b1.', 'int') # binary
|
||||||
|
|
||||||
_check_number('0x2e.', 'int')
|
_check_number('0x2e.', 'int')
|
||||||
@@ -98,8 +98,10 @@ def test_completion_on_complex_literals():
|
|||||||
_check_number('1j.', 'complex')
|
_check_number('1j.', 'complex')
|
||||||
_check_number('44.j.', 'complex')
|
_check_number('44.j.', 'complex')
|
||||||
_check_number('4.0j.', 'complex')
|
_check_number('4.0j.', 'complex')
|
||||||
# No dot no completion
|
# No dot no completion - I thought, but 4j is actually a literall after
|
||||||
assert api.Script('4j').completions() == []
|
# which a keyword like or is allowed. Good times, haha!
|
||||||
|
assert (set([c.name for c in api.Script('4j').completions()]) ==
|
||||||
|
set(['if', 'and', 'in', 'is', 'not', 'or']))
|
||||||
|
|
||||||
|
|
||||||
def test_goto_assignments_on_non_name():
|
def test_goto_assignments_on_non_name():
|
||||||
|
|||||||
9
test/test_api/test_completion_context.py
Normal file
9
test/test_api/test_completion_context.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
from textwrap import dedent
|
||||||
|
from jedi import Script
|
||||||
|
|
||||||
|
|
||||||
|
def test_in_whitespace():
|
||||||
|
code = dedent('''
|
||||||
|
def x():
|
||||||
|
pass''')
|
||||||
|
assert len(Script(code, column=2).completions()) > 20
|
||||||
0
test/test_evaluate/nested_namespaces/__init__.py
Normal file
0
test/test_evaluate/nested_namespaces/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
try:
|
||||||
|
__import__('pkg_resources').declare_namespace(__name__)
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
CONST = 1
|
||||||
@@ -4,7 +4,7 @@ import sys
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
from jedi._compatibility import find_module_py33
|
from jedi._compatibility import find_module_py33, find_module
|
||||||
from ..helpers import cwd_at
|
from ..helpers import cwd_at
|
||||||
|
|
||||||
|
|
||||||
@@ -14,6 +14,44 @@ def test_find_module_py33():
|
|||||||
assert find_module_py33('_io') == (None, '_io', False)
|
assert find_module_py33('_io') == (None, '_io', False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_module_package():
|
||||||
|
file, path, is_package = find_module('json')
|
||||||
|
assert file is None
|
||||||
|
assert path.endswith('json')
|
||||||
|
assert is_package is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_module_not_package():
|
||||||
|
file, path, is_package = find_module('io')
|
||||||
|
assert file is not None
|
||||||
|
assert path.endswith('io.py')
|
||||||
|
assert is_package is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_module_package_zipped():
|
||||||
|
if 'zipped_imports/pkg.zip' not in sys.path:
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__),
|
||||||
|
'zipped_imports/pkg.zip'))
|
||||||
|
file, path, is_package = find_module('pkg')
|
||||||
|
assert file is not None
|
||||||
|
assert path.endswith('pkg.zip')
|
||||||
|
assert is_package is True
|
||||||
|
assert len(jedi.Script('import pkg; pkg.mod', 1, 19).completions()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif('sys.version_info < (2,7)')
|
||||||
|
def test_find_module_not_package_zipped():
|
||||||
|
if 'zipped_imports/not_pkg.zip' not in sys.path:
|
||||||
|
sys.path.append(os.path.join(os.path.dirname(__file__),
|
||||||
|
'zipped_imports/not_pkg.zip'))
|
||||||
|
file, path, is_package = find_module('not_pkg')
|
||||||
|
assert file is not None
|
||||||
|
assert path.endswith('not_pkg.zip')
|
||||||
|
assert is_package is False
|
||||||
|
assert len(
|
||||||
|
jedi.Script('import not_pkg; not_pkg.val', 1, 27).completions()) == 1
|
||||||
|
|
||||||
|
|
||||||
@cwd_at('test/test_evaluate/not_in_sys_path/pkg')
|
@cwd_at('test/test_evaluate/not_in_sys_path/pkg')
|
||||||
def test_import_not_in_sys_path():
|
def test_import_not_in_sys_path():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -51,3 +51,15 @@ def test_namespace_package():
|
|||||||
completion = c
|
completion = c
|
||||||
solution = "statement: foo = '%s'" % solution
|
solution = "statement: foo = '%s'" % solution
|
||||||
assert completion.description == solution
|
assert completion.description == solution
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_namespace_package():
|
||||||
|
CODE = 'from nested_namespaces.namespace.pkg import CONST'
|
||||||
|
|
||||||
|
sys_path = [dirname(__file__)]
|
||||||
|
|
||||||
|
script = jedi.Script(sys_path=sys_path, source=CODE, line=1, column=45)
|
||||||
|
|
||||||
|
result = script.goto_definitions()
|
||||||
|
|
||||||
|
assert len(result) == 1
|
||||||
|
|||||||
BIN
test/test_evaluate/zipped_imports/not_pkg.zip
Normal file
BIN
test/test_evaluate/zipped_imports/not_pkg.zip
Normal file
Binary file not shown.
BIN
test/test_evaluate/zipped_imports/pkg.zip
Normal file
BIN
test/test_evaluate/zipped_imports/pkg.zip
Normal file
Binary file not shown.
@@ -14,6 +14,7 @@ import jedi
|
|||||||
from jedi._compatibility import u
|
from jedi._compatibility import u
|
||||||
from jedi import Script
|
from jedi import Script
|
||||||
from jedi import api
|
from jedi import api
|
||||||
|
from jedi import common
|
||||||
from jedi.evaluate import imports
|
from jedi.evaluate import imports
|
||||||
from jedi.parser import ParserWithRecovery, load_grammar
|
from jedi.parser import ParserWithRecovery, load_grammar
|
||||||
|
|
||||||
@@ -179,6 +180,15 @@ class TestRegression(TestCase):
|
|||||||
else:
|
else:
|
||||||
assert n == limit
|
assert n == limit
|
||||||
|
|
||||||
|
def test_source_to_unicode_unicode_text(self):
|
||||||
|
source = (
|
||||||
|
b"# vim: fileencoding=utf-8\n"
|
||||||
|
b"# \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\n"
|
||||||
|
)
|
||||||
|
actual = common.source_to_unicode(source)
|
||||||
|
expected = source.decode('utf-8')
|
||||||
|
assert actual == expected
|
||||||
|
|
||||||
|
|
||||||
def test_loading_unicode_files_with_bad_global_charset(monkeypatch, tmpdir):
|
def test_loading_unicode_files_with_bad_global_charset(monkeypatch, tmpdir):
|
||||||
dirname = str(tmpdir.mkdir('jedi-test'))
|
dirname = str(tmpdir.mkdir('jedi-test'))
|
||||||
|
|||||||
Reference in New Issue
Block a user