1
0
forked from VimPlug/jedi

Merge branch 'project'

This commit is contained in:
Dave Halter
2020-03-21 02:52:51 +01:00
30 changed files with 353 additions and 133 deletions
+13 -4
View File
@@ -30,6 +30,7 @@ from jedi.api.completion import Completion
from jedi.api.keywords import KeywordName
from jedi.api.environment import InterpreterEnvironment
from jedi.api.project import get_default_project, Project
from jedi.api.errors import parso_to_jedi_errors
from jedi.inference import InferenceState
from jedi.inference import imports
from jedi.inference.references import find_references
@@ -87,7 +88,7 @@ class Script(object):
"""
def __init__(self, source=None, line=None, column=None, path=None,
encoding='utf-8', sys_path=None, environment=None,
_project=None):
project=None):
self._orig_path = path
# An empty path (also empty string) should always result in no path.
self.path = os.path.abspath(path) if path else None
@@ -103,15 +104,20 @@ class Script(object):
if sys_path is not None and not is_py3:
sys_path = list(map(force_unicode, sys_path))
project = _project
if project is None:
# Load the Python grammar of the current interpreter.
project = get_default_project(
os.path.dirname(self.path)if path else os.getcwd()
os.path.dirname(self.path) if path else None
)
# TODO deprecate and remove sys_path from the Script API.
if sys_path is not None:
project._sys_path = sys_path
warnings.warn(
"Deprecated since version 0.17.0. Use the project API instead, "
"which means Script(project=Project(dir, sys_path=sys_path)) instead.",
DeprecationWarning,
stacklevel=2
)
self._inference_state = InferenceState(
project, environment=environment, script_path=self.path
)
@@ -500,6 +506,9 @@ class Script(object):
"""
return self._names(**kwargs) # Python 2...
def get_syntax_errors(self):
return parso_to_jedi_errors(self._grammar, self._module_node)
def _names(self, all_scopes=False, definitions=True, references=False):
def def_ref_filter(_def):
is_def = _def._name.tree_name.is_definition()
@@ -560,7 +569,7 @@ class Interpreter(Script):
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
super(Interpreter, self).__init__(source, environment=environment,
_project=Project(os.getcwd()), **kwds)
project=Project(os.getcwd()), **kwds)
self.namespaces = namespaces
self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default
+10
View File
@@ -707,6 +707,16 @@ class Definition(BaseDefinition):
else:
return self._name.tree_name.is_definition()
def is_side_effect(self):
"""
Checks if a name is defined as ``self.foo = 3``. In case of self, this
function would return False, for foo it would return True.
"""
tree_name = self._name.tree_name
if tree_name is None:
return False
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
def __eq__(self, other):
return self._name.start_pos == other._name.start_pos \
and self.module_path == other.module_path \
+36
View File
@@ -0,0 +1,36 @@
"""
This file is about errors in Python files and not about exception handling in
Jedi.
"""
def parso_to_jedi_errors(grammar, module_node):
return [SyntaxError(e) for e in grammar.iter_errors(module_node)]
class SyntaxError(object):
def __init__(self, parso_error):
self._parso_error = parso_error
@property
def line(self):
return self._parso_error.start_pos[0]
@property
def column(self):
return self._parso_error.start_pos[1]
@property
def until_line(self):
return self._parso_error.end_pos[0]
@property
def until_column(self):
return self._parso_error.end_pos[1]
def __repr__(self):
return '<%s from=%s to=%s>' % (
self.__class__.__name__,
self._parso_error.start_pos,
self._parso_error.end_pos,
)
+50 -31
View File
@@ -1,9 +1,9 @@
import os
import errno
import json
from jedi._compatibility import FileNotFoundError, PermissionError, IsADirectoryError
from jedi.api.environment import SameEnvironment, \
get_cached_default_environment
from jedi.api.environment import get_cached_default_environment, create_environment
from jedi.api.exceptions import WrongVersion
from jedi._compatibility import force_unicode
from jedi.inference.sys_path import discover_buildout_paths
@@ -30,13 +30,15 @@ def _force_unicode_list(lst):
class Project(object):
# TODO serialize environment
_serializer_ignore_attributes = ('_environment',)
_environment = None
@staticmethod
def _get_config_folder_path(base_path):
return os.path.join(base_path, _CONFIG_FOLDER)
@staticmethod
def _get_json_path(base_path):
return os.path.join(base_path, _CONFIG_FOLDER, 'project.json')
return os.path.join(Project._get_config_folder_path(base_path), 'project.json')
@classmethod
def load(cls, path):
@@ -47,9 +49,7 @@ class Project(object):
version, data = json.load(f)
if version == 1:
self = cls.__new__()
self.__dict__.update(data)
return self
return cls(**data)
else:
raise WrongVersion(
"The Jedi version of this project seems newer than what we can handle."
@@ -58,35 +58,40 @@ class Project(object):
def __init__(self, path, **kwargs):
"""
:param path: The base path for this project.
:param python_path: The Python executable path, typically the path of a
virtual environment.
:param load_unsafe_extensions: Loads extensions that are not in the
sys path and in the local directories. With this option enabled,
this is potentially unsafe if you clone a git repository and
analyze it's code, because those compiled extensions will be
important and therefore have execution privileges.
:param sys_path: list of str. You can override the sys path if you
want. By default the ``sys.path.`` is generated from the
environment (virtualenvs, etc).
:param added_sys_path: list of str. Adds these paths at the end of the
sys path.
:param smart_sys_path: If this is enabled (default), adds paths from
local directories. Otherwise you will have to rely on your packages
being properly configured on the ``sys.path``.
"""
def py2_comp(path, environment=None, sys_path=None,
smart_sys_path=True, _django=False):
def py2_comp(path, python_path=None, load_unsafe_extensions=False,
sys_path=None, added_sys_path=(), smart_sys_path=True):
self._path = os.path.abspath(path)
if isinstance(environment, SameEnvironment):
self._environment = environment
self._python_path = python_path
self._sys_path = sys_path
self._smart_sys_path = smart_sys_path
self._django = _django
self._load_unsafe_extensions = load_unsafe_extensions
self._django = False
self.added_sys_path = list(added_sys_path)
"""The sys path that is going to be added at the end of the """
py2_comp(path, **kwargs)
@inference_state_as_method_param_cache()
def _get_base_sys_path(self, inference_state, environment=None):
if self._sys_path is not None:
return self._sys_path
def _get_base_sys_path(self, inference_state):
# The sys path has not been set explicitly.
if environment is None:
environment = self.get_environment()
sys_path = list(environment.get_sys_path())
sys_path = list(inference_state.environment.get_sys_path())
try:
sys_path.remove('')
except ValueError:
@@ -94,16 +99,19 @@ class Project(object):
return sys_path
@inference_state_as_method_param_cache()
def _get_sys_path(self, inference_state, environment=None,
add_parent_paths=True, add_init_paths=False):
def _get_sys_path(self, inference_state, add_parent_paths=True, add_init_paths=False):
"""
Keep this method private for all users of jedi. However internally this
one is used like a public method.
"""
suffixed = []
suffixed = list(self.added_sys_path)
prefixed = []
sys_path = list(self._get_base_sys_path(inference_state, environment))
if self._sys_path is None:
sys_path = list(self._get_base_sys_path(inference_state))
else:
sys_path = list(self._sys_path)
if self._smart_sys_path:
prefixed.append(self._path)
@@ -136,16 +144,25 @@ class Project(object):
def save(self):
data = dict(self.__dict__)
for attribute in self._serializer_ignore_attributes:
data.pop(attribute, None)
data.pop('_environment', None)
data.pop('_django', None) # TODO make django setting public?
data = {k.lstrip('_'): v for k, v in data.items()}
with open(self._get_json_path(self._path), 'wb') as f:
# TODO when dropping Python 2 use pathlib.Path.mkdir(parents=True, exist_ok=True)
try:
os.makedirs(self._get_config_folder_path(self._path))
except OSError as e:
if e.errno != errno.EEXIST:
raise
with open(self._get_json_path(self._path), 'w') as f:
return json.dump((_SERIALIZER_VERSION, data), f)
def get_environment(self):
if self._environment is None:
return get_cached_default_environment()
if self._python_path is not None:
self._environment = create_environment(self._python_path, safe=False)
else:
self._environment = get_cached_default_environment()
return self._environment
def __repr__(self):
@@ -192,7 +209,9 @@ def get_default_project(path=None):
first_no_init_file = dir
if _is_django_path(dir):
return Project(dir, _django=True)
project = Project(dir)
project._django = True
return project
if probable_path is None and _is_potential_project(dir):
probable_path = dir