mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 22:44:27 +08:00
Remove the module path from the parser tree.
Some static analysis tests are still failing.
This commit is contained in:
@@ -142,7 +142,11 @@ class Script(object):
|
||||
|
||||
@cache.memoize_method
|
||||
def _get_module(self):
|
||||
module = er.ModuleContext(self._evaluator, self._get_module_node())
|
||||
module = er.ModuleContext(
|
||||
self._evaluator,
|
||||
self._get_module_node(),
|
||||
self.path
|
||||
)
|
||||
imports.add_module(self._evaluator, module.name.string_name, module)
|
||||
return module
|
||||
|
||||
@@ -398,7 +402,8 @@ class Interpreter(Script):
|
||||
return interpreter.MixedModuleContext(
|
||||
self._evaluator,
|
||||
parser_module,
|
||||
self.namespaces
|
||||
self.namespaces,
|
||||
path=self.path
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -300,7 +300,7 @@ def cache_call_signatures(evaluator, context, bracket_leaf, code_lines, user_pos
|
||||
whole = '\n'.join(other_lines + [before_cursor])
|
||||
before_bracket = re.match(r'.*\(', whole, re.DOTALL)
|
||||
|
||||
module_path = bracket_leaf.get_root_node().path
|
||||
module_path = context.get_root_context().py__file__()
|
||||
if module_path is None:
|
||||
yield None # Don't cache!
|
||||
else:
|
||||
|
||||
@@ -12,12 +12,12 @@ class MixedModuleContext(Context):
|
||||
resets_positions = True
|
||||
type = 'mixed_module'
|
||||
|
||||
def __init__(self, evaluator, tree_module, namespaces):
|
||||
def __init__(self, evaluator, tree_module, namespaces, path):
|
||||
self.evaluator = evaluator
|
||||
self._namespaces = namespaces
|
||||
|
||||
self._namespace_objects = [type('jedi_namespace', (), n) for n in namespaces]
|
||||
self._module_context = ModuleContext(evaluator, tree_module)
|
||||
self._module_context = ModuleContext(evaluator, tree_module, path=path)
|
||||
self.tree_node = tree_module
|
||||
|
||||
def get_node(self):
|
||||
@@ -33,7 +33,7 @@ class MixedModuleContext(Context):
|
||||
self.evaluator,
|
||||
parent_context=self,
|
||||
compiled_object=compiled_object,
|
||||
tree_name=self.tree_node.name
|
||||
tree_context=self._module_context
|
||||
)
|
||||
for filter in mixed_object.get_filters(*args, **kwargs):
|
||||
yield filter
|
||||
|
||||
@@ -5,8 +5,6 @@ from jedi import debug
|
||||
from jedi.parser.python import tree
|
||||
from jedi.evaluate.compiled import CompiledObject
|
||||
|
||||
from jedi.common import unite
|
||||
|
||||
|
||||
CODES = {
|
||||
'attribute-error': (1, AttributeError, 'Potential AttributeError.'),
|
||||
@@ -82,7 +80,9 @@ def add(node_context, error_name, node, message=None, typ=Error, payload=None):
|
||||
if _check_for_exception_catch(node_context, node, exception, payload):
|
||||
return
|
||||
|
||||
module_path = node.get_root_node().path
|
||||
# TODO this path is probably not right
|
||||
module_context = node_context.get_root_context()
|
||||
module_path = module_context.py__file__()
|
||||
instance = typ(error_name, module_path, node.start_pos, message)
|
||||
debug.warning(str(instance), format=False)
|
||||
node_context.evaluator.analysis.append(instance)
|
||||
|
||||
@@ -29,25 +29,12 @@ class MixedObject(object):
|
||||
fewer special cases, because we in Python you don't have the same freedoms
|
||||
to modify the runtime.
|
||||
"""
|
||||
def __init__(self, evaluator, parent_context, compiled_object, tree_name):
|
||||
def __init__(self, evaluator, parent_context, compiled_object, tree_context):
|
||||
self.evaluator = evaluator
|
||||
self.parent_context = parent_context
|
||||
self.compiled_object = compiled_object
|
||||
self._context = tree_context
|
||||
self.obj = compiled_object.obj
|
||||
self._tree_name = tree_name
|
||||
name_module = tree_name.get_root_node()
|
||||
if parent_context.tree_node.get_root_node() != name_module:
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
module_context = ModuleContext(evaluator, name_module)
|
||||
name = compiled_object.get_root_context().py__name__()
|
||||
imports.add_module(evaluator, name, module_context)
|
||||
else:
|
||||
module_context = parent_context.get_root_context()
|
||||
|
||||
self._context = module_context.create_context(
|
||||
tree_name.parent,
|
||||
node_is_context=True,
|
||||
node_is_object=True
|
||||
)
|
||||
|
||||
# We have to overwrite everything that has to do with trailers, name
|
||||
# lookups and filters to make it possible to route name lookups towards
|
||||
@@ -132,10 +119,10 @@ def find_syntax_node_name(evaluator, python_object):
|
||||
path = inspect.getsourcefile(python_object)
|
||||
except TypeError:
|
||||
# The type might not be known (e.g. class_with_dict.__weakref__)
|
||||
return None
|
||||
return None, None
|
||||
if path is None or not os.path.exists(path):
|
||||
# The path might not exist or be e.g. <stdin>.
|
||||
return None
|
||||
return None, None
|
||||
|
||||
module = _load_module(evaluator, path, python_object)
|
||||
|
||||
@@ -143,17 +130,17 @@ def find_syntax_node_name(evaluator, python_object):
|
||||
# We don't need to check names for modules, because there's not really
|
||||
# a way to write a module in a module in Python (and also __name__ can
|
||||
# be something like ``email.utils``).
|
||||
return module.name
|
||||
return module, path
|
||||
|
||||
name_str = python_object.__name__
|
||||
if name_str == '<lambda>':
|
||||
return None # It's too hard to find lambdas.
|
||||
return None, None # It's too hard to find lambdas.
|
||||
|
||||
# Doesn't always work (e.g. os.stat_result)
|
||||
try:
|
||||
names = module.used_names[name_str]
|
||||
except KeyError:
|
||||
return None
|
||||
return None, None
|
||||
names = [n for n in names if n.is_definition()]
|
||||
|
||||
try:
|
||||
@@ -170,22 +157,44 @@ def find_syntax_node_name(evaluator, python_object):
|
||||
# There's a chance that the object is not available anymore, because
|
||||
# the code has changed in the background.
|
||||
if line_names:
|
||||
return line_names[-1]
|
||||
return line_names[-1].parent, path
|
||||
|
||||
# It's really hard to actually get the right definition, here as a last
|
||||
# resort we just return the last one. This chance might lead to odd
|
||||
# completions at some points but will lead to mostly correct type
|
||||
# inference, because people tend to define a public name in a module only
|
||||
# once.
|
||||
return names[-1]
|
||||
return names[-1].parent, path
|
||||
|
||||
|
||||
@compiled.compiled_objects_cache('mixed_cache')
|
||||
def create(evaluator, obj, parent_context=None, *args):
|
||||
tree_name = find_syntax_node_name(evaluator, obj)
|
||||
tree_node, path = find_syntax_node_name(evaluator, obj)
|
||||
|
||||
compiled_object = compiled.create(
|
||||
evaluator, obj, parent_context=parent_context.compiled_object)
|
||||
if tree_name is None:
|
||||
if tree_node is None:
|
||||
return compiled_object
|
||||
return MixedObject(evaluator, parent_context, compiled_object, tree_name)
|
||||
|
||||
module_node = tree_node.get_root_node()
|
||||
if parent_context.tree_node.get_root_node() == module_node:
|
||||
module_context = parent_context.get_root_context()
|
||||
else:
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
module_context = ModuleContext(evaluator, module_node, path=path)
|
||||
name = compiled_object.get_root_context().py__name__()
|
||||
imports.add_module(evaluator, name, module_context)
|
||||
|
||||
tree_context = module_context.create_context(
|
||||
tree_node,
|
||||
node_is_context=True,
|
||||
node_is_object=True
|
||||
)
|
||||
|
||||
return MixedObject(
|
||||
evaluator,
|
||||
parent_context,
|
||||
compiled_object,
|
||||
tree_context=tree_context
|
||||
)
|
||||
|
||||
|
||||
@@ -459,7 +459,7 @@ def _load_module(evaluator, path=None, code=None, sys_path=None, parent_module=N
|
||||
module_node = parse(code=code, path=path, cache=True, diff_cache=True)
|
||||
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
return ModuleContext(evaluator, module_node)
|
||||
return ModuleContext(evaluator, module_node, path=path)
|
||||
else:
|
||||
return compiled.load_module(evaluator, path)
|
||||
|
||||
@@ -489,7 +489,7 @@ def get_modules_containing_name(evaluator, modules, name):
|
||||
return None
|
||||
else:
|
||||
module_node = parser_cache_item.parser.get_root_node()
|
||||
return er.ModuleContext(evaluator, module_node)
|
||||
return er.ModuleContext(evaluator, module_node, path=path)
|
||||
|
||||
def check_fs(path):
|
||||
with open(path, 'rb') as f:
|
||||
|
||||
@@ -150,7 +150,11 @@ def py__getitem__(context, typ, node):
|
||||
return context.eval_node(nodes[0])
|
||||
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
typing = ModuleContext(context.evaluator, _get_typing_replacement_module())
|
||||
typing = ModuleContext(
|
||||
context.evaluator,
|
||||
module_node=_get_typing_replacement_module(),
|
||||
path=None
|
||||
)
|
||||
factories = typing.py__getattribute__("factory")
|
||||
assert len(factories) == 1
|
||||
factory = list(factories)[0]
|
||||
|
||||
@@ -57,7 +57,8 @@ from jedi.evaluate import imports
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
|
||||
GlobalNameFilter, DictFilter, ContextName, AbstractNameDefinition, \
|
||||
ParamName, AnonymousInstanceParamName, TreeNameDefinition
|
||||
ParamName, AnonymousInstanceParamName, TreeNameDefinition, \
|
||||
ContextNameMixin
|
||||
from jedi.evaluate.dynamic import search_params
|
||||
from jedi.evaluate import context
|
||||
|
||||
@@ -406,13 +407,26 @@ class ModuleAttributeName(AbstractNameDefinition):
|
||||
)
|
||||
|
||||
|
||||
class ModuleName(ContextNameMixin, AbstractNameDefinition):
|
||||
start_pos = 1, 0
|
||||
|
||||
def __init__(self, context, name):
|
||||
self._context = context
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
return self._name
|
||||
|
||||
|
||||
class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
api_type = 'module'
|
||||
parent_context = None
|
||||
|
||||
def __init__(self, evaluator, module_node):
|
||||
def __init__(self, evaluator, module_node, path):
|
||||
super(ModuleContext, self).__init__(evaluator, parent_context=None)
|
||||
self.tree_node = module_node
|
||||
self._path = path
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield ParserTreeFilter(
|
||||
@@ -449,10 +463,21 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
# All the additional module attributes are strings.
|
||||
return dict((n, ModuleAttributeName(self, n)) for n in names)
|
||||
|
||||
@property
|
||||
def _string_name(self):
|
||||
""" This is used for the goto functions. """
|
||||
if self._path is None:
|
||||
return '' # no path -> empty name
|
||||
else:
|
||||
sep = (re.escape(os.path.sep),) * 2
|
||||
r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self._path)
|
||||
# Remove PEP 3149 names
|
||||
return re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
|
||||
|
||||
@property
|
||||
@memoize_default()
|
||||
def name(self):
|
||||
return ContextName(self, self.tree_node.name)
|
||||
return ModuleName(self, self._string_name)
|
||||
|
||||
def _get_init_directory(self):
|
||||
"""
|
||||
@@ -478,10 +503,10 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
"""
|
||||
In contrast to Python's __file__ can be None.
|
||||
"""
|
||||
if self.tree_node.path is None:
|
||||
if self._path is None:
|
||||
return None
|
||||
|
||||
return os.path.abspath(self.tree_node.path)
|
||||
return os.path.abspath(self._path)
|
||||
|
||||
def py__package__(self):
|
||||
if self._get_init_directory() is None:
|
||||
@@ -539,7 +564,7 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
Lists modules in the directory of this module (if this module is a
|
||||
package).
|
||||
"""
|
||||
path = self.tree_node.path
|
||||
path = self._path
|
||||
names = {}
|
||||
if path is not None and path.endswith(os.path.sep + '__init__.py'):
|
||||
mods = pkgutil.iter_modules([os.path.dirname(path)])
|
||||
@@ -559,6 +584,11 @@ class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
def py__class__(self):
|
||||
return compiled.get_special_object(self.evaluator, 'MODULE_CLASS')
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s@%s-%s>" % (
|
||||
self.__class__.__name__, self._string_name,
|
||||
self.tree_node.start_pos[0], self.tree_node.end_pos[0])
|
||||
|
||||
|
||||
class ImplicitNSName(AbstractNameDefinition):
|
||||
"""
|
||||
|
||||
@@ -222,7 +222,7 @@ def _get_paths_from_buildout_script(evaluator, buildout_script_path):
|
||||
return
|
||||
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
for path in _check_module(ModuleContext(evaluator, module_node)):
|
||||
for path in _check_module(ModuleContext(evaluator, module_node, buildout_script_path)):
|
||||
yield path
|
||||
|
||||
|
||||
|
||||
@@ -144,8 +144,6 @@ class ParserWithRecovery(Parser):
|
||||
|
||||
:param grammar: The grammar object of pgen2. Loaded by load_grammar.
|
||||
:param source: The codebase for the parser. Must be unicode.
|
||||
:param module_path: The path of the module in the file system, may be None.
|
||||
:type module_path: str
|
||||
"""
|
||||
def __init__(self, grammar, source, module_path=None):
|
||||
super(ParserWithRecovery, self).__init__(
|
||||
@@ -155,7 +153,6 @@ class ParserWithRecovery(Parser):
|
||||
self.syntax_errors = []
|
||||
self._omit_dedent_list = []
|
||||
self._indent_counter = 0
|
||||
self._module_path = module_path
|
||||
|
||||
# TODO do print absolute import detection here.
|
||||
# try:
|
||||
@@ -169,7 +166,6 @@ class ParserWithRecovery(Parser):
|
||||
|
||||
def parse(self, tokens):
|
||||
root_node = super(ParserWithRecovery, self).parse(self._tokenize(tokens))
|
||||
root_node.path = self._module_path
|
||||
return root_node
|
||||
|
||||
def error_recovery(self, grammar, stack, arcs, typ, value, start_pos, prefix,
|
||||
|
||||
@@ -32,8 +32,7 @@ For static analysis purposes there exists a method called
|
||||
``nodes_to_execute`` on all nodes and leaves. It's documented in the static
|
||||
anaylsis documentation.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
|
||||
from inspect import cleandoc
|
||||
from itertools import chain
|
||||
import textwrap
|
||||
@@ -277,7 +276,7 @@ class Name(_LeafWithoutNewlines):
|
||||
return False
|
||||
|
||||
stmt = self.get_definition()
|
||||
if stmt.type in ('funcdef', 'classdef', 'file_input', 'param'):
|
||||
if stmt.type in ('funcdef', 'classdef', 'param'):
|
||||
return self == stmt.name
|
||||
elif stmt.type == 'for_stmt':
|
||||
return self.start_pos < stmt.children[2].start_pos
|
||||
@@ -413,12 +412,9 @@ class Scope(PythonBaseNode, DocstringMixin):
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
name = self.path
|
||||
name = self.name
|
||||
except AttributeError:
|
||||
try:
|
||||
name = self.name
|
||||
except AttributeError:
|
||||
name = self.command
|
||||
name = ''
|
||||
|
||||
return "<%s: %s@%s-%s>" % (type(self).__name__, name,
|
||||
self.start_pos[0], self.end_pos[0])
|
||||
@@ -442,38 +438,13 @@ class Module(Scope):
|
||||
Depending on the underlying parser this may be a full module or just a part
|
||||
of a module.
|
||||
"""
|
||||
__slots__ = ('path', '_used_names', '_name')
|
||||
__slots__ = ('_used_names', '_name')
|
||||
type = 'file_input'
|
||||
|
||||
def __init__(self, children):
|
||||
"""
|
||||
Initialize :class:`Module`.
|
||||
|
||||
:type path: str
|
||||
:arg path: File path to this module.
|
||||
|
||||
.. todo:: Document `top_module`.
|
||||
"""
|
||||
super(Module, self).__init__(children)
|
||||
self.path = None # Set later.
|
||||
self._used_names = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
""" This is used for the goto functions. """
|
||||
if self.path is None:
|
||||
string = '' # no path -> empty name
|
||||
else:
|
||||
sep = (re.escape(os.path.sep),) * 2
|
||||
r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self.path)
|
||||
# Remove PEP 3149 names
|
||||
string = re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
|
||||
# Positions are not real, but a module starts at (1, 0)
|
||||
p = (1, 0)
|
||||
name = Name(string, p)
|
||||
name.parent = self
|
||||
return name
|
||||
|
||||
@property
|
||||
def has_explicit_absolute_import(self):
|
||||
"""
|
||||
|
||||
@@ -338,7 +338,7 @@ class TestGotoAssignments(TestCase):
|
||||
n = nms[0].goto_assignments()[0]
|
||||
assert n.name == 'json'
|
||||
assert n.type == 'module'
|
||||
assert n._name.tree_name.get_definition().type == 'file_input'
|
||||
assert n._name._context.tree_node.type == 'file_input'
|
||||
|
||||
assert nms[1].name == 'foo'
|
||||
assert nms[1].type == 'module'
|
||||
@@ -347,7 +347,7 @@ class TestGotoAssignments(TestCase):
|
||||
assert len(ass) == 1
|
||||
assert ass[0].name == 'json'
|
||||
assert ass[0].type == 'module'
|
||||
assert ass[0]._name.tree_name.get_definition().type == 'file_input'
|
||||
assert ass[0]._name._context.tree_node.type == 'file_input'
|
||||
|
||||
|
||||
def test_added_equals_to_params():
|
||||
|
||||
@@ -15,7 +15,7 @@ from ..helpers import cwd_at
|
||||
|
||||
def check_module_test(code):
|
||||
grammar = load_grammar()
|
||||
module_context = ModuleContext(Evaluator(grammar), parse(code))
|
||||
module_context = ModuleContext(Evaluator(grammar), parse(code), path=None)
|
||||
return _check_module(module_context)
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ def test_sys_path_with_modifications():
|
||||
path = os.path.abspath(os.path.join(os.curdir, 'module_name.py'))
|
||||
grammar = load_grammar()
|
||||
module_node = parse(code, path=path)
|
||||
module_context = ModuleContext(Evaluator(grammar), module_node)
|
||||
module_context = ModuleContext(Evaluator(grammar), module_node, path=path)
|
||||
paths = sys_path_with_modifications(module_context.evaluator, module_context)
|
||||
assert '/tmp/.buildout/eggs/important_package.egg' in paths
|
||||
|
||||
|
||||
@@ -88,20 +88,6 @@ class TestImports():
|
||||
assert imp.end_pos == (1, len('import math'))
|
||||
|
||||
|
||||
def test_module():
|
||||
module = parse('asdf', path='example.py')
|
||||
name = module.name
|
||||
assert str(name) == 'example'
|
||||
assert name.start_pos == (1, 0)
|
||||
assert name.end_pos == (1, 7)
|
||||
|
||||
module = parse('asdf')
|
||||
name = module.name
|
||||
assert str(name) == ''
|
||||
assert name.start_pos == (1, 0)
|
||||
assert name.end_pos == (1, 0)
|
||||
|
||||
|
||||
def test_end_pos():
|
||||
s = dedent('''
|
||||
x = ['a', 'b', 'c']
|
||||
|
||||
Reference in New Issue
Block a user