1
0
forked from VimPlug/jedi

Merge dev into linter.

This commit is contained in:
Dave Halter
2015-11-10 21:49:32 +01:00
32 changed files with 451 additions and 123 deletions

View File

@@ -25,7 +25,7 @@ def find_module_py33(string, path=None):
except ValueError as e:
# See #491. Importlib might raise a ValueError, to avoid this, we
# just raise an ImportError to fix the issue.
raise ImportError("Originally ValueError: " + e.message)
raise ImportError("Originally ValueError: " + str(e))
if loader is None:
raise ImportError("Couldn't find a loader for {0}".format(string))

View File

@@ -34,6 +34,7 @@ from jedi.evaluate.param import try_iter_content
from jedi.evaluate.cache import memoize_default
from jedi.evaluate.helpers import FakeName, get_module_names
from jedi.evaluate.finder import global_names_dict_generator, filter_definition_names
from jedi.evaluate.sys_path import get_venv_path
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
# can remove some "maximum recursion depth" errors.
@@ -58,6 +59,18 @@ class Script(object):
You can either use the ``source`` parameter or ``path`` to read a file.
Usually you're going to want to use both of them (in an editor).
The script might be analyzed in a different ``sys.path`` than |jedi|:
- if `sys_path` parameter is not ``None``, it will be used as ``sys.path``
for the script;
- if `sys_path` parameter is ``None`` and ``VIRTUAL_ENV`` environment
variable is defined, ``sys.path`` for the specified environment will be
guessed (see :func:`jedi.evaluate.sys_path.get_venv_path`) and used for
the script;
- otherwise ``sys.path`` will match that of |jedi|.
:param source: The source code of the current file, separated by newlines.
:type source: str
:param line: The line to perform actions on (starting with 1).
@@ -73,9 +86,13 @@ class Script(object):
:param source_encoding: The encoding of ``source``, if it is not a
``unicode`` object (default ``'utf-8'``).
:type encoding: str
:param sys_path: ``sys.path`` to use during analysis of the script
:type sys_path: list
"""
def __init__(self, source=None, line=None, column=None, path=None,
encoding='utf-8', source_path=None, source_encoding=None):
encoding='utf-8', source_path=None, source_encoding=None,
sys_path=None):
if source_path is not None:
warnings.warn("Use path instead of source_path.", DeprecationWarning)
path = source_path
@@ -109,7 +126,11 @@ class Script(object):
self._parser = UserContextParser(self._grammar, self.source, path,
self._pos, self._user_context,
self._parsed_callback)
self._evaluator = Evaluator(self._grammar)
if sys_path is None:
venv = os.getenv('VIRTUAL_ENV')
if venv:
sys_path = list(get_venv_path(venv))
self._evaluator = Evaluator(self._grammar, sys_path=sys_path)
debug.speed('init')
def _parsed_callback(self, parser):

View File

@@ -336,7 +336,7 @@ class BaseDefinition(object):
raise AttributeError()
followed = followed[0] # only check the first one.
if followed.type == 'funcdef':
if followed.type in ('funcdef', 'lambda'):
if isinstance(followed, er.InstanceElement):
params = followed.params[1:]
else:

View File

@@ -61,6 +61,7 @@ that are not used are just being ignored.
"""
import copy
import sys
from itertools import chain
from jedi.parser import tree
@@ -79,7 +80,7 @@ from jedi.evaluate import helpers
class Evaluator(object):
def __init__(self, grammar):
def __init__(self, grammar, sys_path=None):
self.grammar = grammar
self.memoize_cache = {} # for memoize decorators
# To memorize modules -> equals `sys.modules`.
@@ -91,6 +92,14 @@ class Evaluator(object):
self.predefined_if_name_dict_dict = {}
self.is_analysis = False
if sys_path is None:
sys_path = sys.path
self.sys_path = copy.copy(sys_path)
try:
self.sys_path.remove('')
except ValueError:
pass
def wrap(self, element):
if isinstance(element, tree.Class):
return er.Class(self, element)

View File

@@ -10,7 +10,6 @@ from functools import partial
from jedi._compatibility import builtins as _builtins, unicode
from jedi import debug
from jedi.cache import underscore_memoization, memoize_method
from jedi.evaluate.sys_path import get_sys_path
from jedi.parser.tree import Param, Base, Operator, zero_position_modifier
from jedi.evaluate.helpers import FakeName
from . import fake
@@ -309,15 +308,12 @@ class CompiledName(FakeName):
pass # Just ignore this, FakeName tries to overwrite the parent attribute.
def dotted_from_fs_path(fs_path, sys_path=None):
def dotted_from_fs_path(fs_path, sys_path):
"""
Changes `/usr/lib/python3.4/email/utils.py` to `email.utils`. I.e.
compares the path with sys.path and then returns the dotted_path. If the
path is not in the sys.path, just returns None.
"""
if sys_path is None:
sys_path = get_sys_path()
if os.path.basename(fs_path).startswith('__init__.'):
# We are calculating the path. __init__ files are not interesting.
fs_path = os.path.dirname(fs_path)
@@ -341,13 +337,13 @@ def dotted_from_fs_path(fs_path, sys_path=None):
return _path_re.sub('', fs_path[len(path):].lstrip(os.path.sep)).replace(os.path.sep, '.')
def load_module(path=None, name=None):
def load_module(evaluator, path=None, name=None):
sys_path = evaluator.sys_path
if path is not None:
dotted_path = dotted_from_fs_path(path)
dotted_path = dotted_from_fs_path(path, sys_path=sys_path)
else:
dotted_path = name
sys_path = get_sys_path()
if dotted_path is None:
p, _, dotted_path = path.partition(os.path.sep)
sys_path.insert(0, p)

View File

@@ -342,7 +342,7 @@ class Importer(object):
module_file.close()
if module_file is None and not module_path.endswith('.py'):
module = compiled.load_module(module_path)
module = compiled.load_module(self._evaluator, module_path)
else:
module = _load_module(self._evaluator, module_path, source, sys_path)
@@ -440,12 +440,15 @@ def _load_module(evaluator, path=None, source=None, sys_path=None):
with open(path, 'rb') as f:
source = f.read()
else:
return compiled.load_module(path)
return compiled.load_module(evaluator, path)
p = path
p = fast.FastParser(evaluator.grammar, common.source_to_unicode(source), p)
cache.save_parser(path, p)
return p.module
if sys_path is None:
sys_path = evaluator.sys_path
cached = cache.load_parser(path)
module = load(source) if cached is None else cached.module
module = evaluator.wrap(module)

110
jedi/evaluate/site.py Normal file
View File

@@ -0,0 +1,110 @@
"""An adapted copy of relevant site-packages functionality from Python stdlib.
This file contains some functions related to handling site-packages in Python
with jedi-specific modifications:
- the functions operate on sys_path argument rather than global sys.path
- in .pth files "import ..." lines that allow execution of arbitrary code are
skipped to prevent code injection into jedi interpreter
"""
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
# 2011, 2012, 2013, 2014, 2015 Python Software Foundation; All Rights Reserved
from __future__ import print_function
import sys
import os
def makepath(*paths):
dir = os.path.join(*paths)
try:
dir = os.path.abspath(dir)
except OSError:
pass
return dir, os.path.normcase(dir)
def _init_pathinfo(sys_path):
"""Return a set containing all existing directory entries from sys_path"""
d = set()
for dir in sys_path:
try:
if os.path.isdir(dir):
dir, dircase = makepath(dir)
d.add(dircase)
except TypeError:
continue
return d
def addpackage(sys_path, sitedir, name, known_paths):
"""Process a .pth file within the site-packages directory:
For each line in the file, either combine it with sitedir to a path
and add that to known_paths, or execute it if it starts with 'import '.
"""
if known_paths is None:
known_paths = _init_pathinfo(sys_path)
reset = 1
else:
reset = 0
fullname = os.path.join(sitedir, name)
try:
f = open(fullname, "r")
except OSError:
return
with f:
for n, line in enumerate(f):
if line.startswith("#"):
continue
try:
if line.startswith(("import ", "import\t")):
# Change by immerrr: don't evaluate import lines to prevent
# code injection into jedi through pth files.
#
# exec(line)
continue
line = line.rstrip()
dir, dircase = makepath(sitedir, line)
if not dircase in known_paths and os.path.exists(dir):
sys_path.append(dir)
known_paths.add(dircase)
except Exception:
print("Error processing line {:d} of {}:\n".format(n+1, fullname),
file=sys.stderr)
import traceback
for record in traceback.format_exception(*sys.exc_info()):
for line in record.splitlines():
print(' '+line, file=sys.stderr)
print("\nRemainder of file ignored", file=sys.stderr)
break
if reset:
known_paths = None
return known_paths
def addsitedir(sys_path, sitedir, known_paths=None):
"""Add 'sitedir' argument to sys_path if missing and handle .pth files in
'sitedir'"""
if known_paths is None:
known_paths = _init_pathinfo(sys_path)
reset = 1
else:
reset = 0
sitedir, sitedircase = makepath(sitedir)
if not sitedircase in known_paths:
sys_path.append(sitedir) # Add path component
known_paths.add(sitedircase)
try:
names = os.listdir(sitedir)
except OSError:
return
names = [name for name in names if name.endswith(".pth")]
for name in sorted(names):
addpackage(sys_path, sitedir, name, known_paths)
if reset:
known_paths = None
return known_paths

View File

@@ -1,6 +1,7 @@
import glob
import os
import sys
from jedi.evaluate.site import addsitedir
from jedi._compatibility import exec_function, unicode
from jedi.parser import tree
@@ -11,24 +12,51 @@ from jedi import common
from jedi import cache
def get_sys_path():
def check_virtual_env(sys_path):
""" Add virtualenv's site-packages to the `sys.path`."""
venv = os.getenv('VIRTUAL_ENV')
if not venv:
return
venv = os.path.abspath(venv)
p = _get_venv_sitepackages(venv)
if p not in sys_path:
sys_path.insert(0, p)
def get_venv_path(venv):
"""Get sys.path for specified virtual environment."""
sys_path = _get_venv_path_dirs(venv)
with common.ignored(ValueError):
sys_path.remove('')
sys_path = _get_sys_path_with_egglinks(sys_path)
# As of now, get_venv_path_dirs does not scan built-in pythonpath and
# user-local site-packages, let's approximate them using path from Jedi
# interpreter.
return sys_path + sys.path
# Add all egg-links from the virtualenv.
for egg_link in glob.glob(os.path.join(p, '*.egg-link')):
def _get_sys_path_with_egglinks(sys_path):
"""Find all paths including those referenced by egg-links.
Egg-link-referenced directories are inserted into path immediately after
the directory on which their links were found. Such directories are not
taken into consideration by normal import mechanism, but they are traversed
when doing pkg_resources.require.
"""
result = []
for p in sys_path:
result.append(p)
# pkg_resources does not define a specific order for egg-link files
# using os.listdir to enumerate them, we're sorting them to have
# reproducible tests.
for egg_link in sorted(glob.glob(os.path.join(p, '*.egg-link'))):
with open(egg_link) as fd:
sys_path.insert(0, fd.readline().rstrip())
for line in fd:
line = line.strip()
if line:
result.append(os.path.join(p, line))
# pkg_resources package only interprets the first
# non-empty line in egg-link files.
break
return result
check_virtual_env(sys.path)
return [p for p in sys.path if p != ""]
def _get_venv_path_dirs(venv):
"""Get sys.path for venv without starting up the interpreter."""
venv = os.path.abspath(venv)
sitedir = _get_venv_sitepackages(venv)
sys_path = []
addsitedir(sys_path, sitedir)
return sys_path
def _get_venv_sitepackages(venv):
@@ -109,7 +137,6 @@ def _paths_from_list_modifications(module_path, trailer1, trailer2):
name = trailer1.children[1].value
if name not in ['insert', 'append']:
return []
arg = trailer2.children[1]
if name == 'insert' and len(arg.children) in (3, 4): # Possible trailing comma.
arg = arg.children[2]
@@ -117,6 +144,9 @@ def _paths_from_list_modifications(module_path, trailer1, trailer2):
def _check_module(evaluator, module):
"""
Detect sys.path modifications within module.
"""
def get_sys_path_powers(names):
for name in names:
power = name.parent.parent
@@ -128,10 +158,12 @@ def _check_module(evaluator, module):
if isinstance(n, tree.Name) and n.value == 'path':
yield name, power
sys_path = list(get_sys_path()) # copy
sys_path = list(evaluator.sys_path) # copy
try:
possible_names = module.used_names['path']
except KeyError:
# module.used_names is MergedNamesDict whose getitem never throws
# keyerror, this is superfluous.
pass
else:
for name, power in get_sys_path_powers(possible_names):
@@ -148,7 +180,7 @@ def sys_path_with_modifications(evaluator, module):
if module.path is None:
# Support for modules without a path is bad, therefore return the
# normal path.
return list(get_sys_path())
return list(evaluator.sys_path)
curdir = os.path.abspath(os.curdir)
with common.ignored(OSError):

View File

@@ -825,6 +825,15 @@ def _create_params(parent, argslist_list):
class Function(ClassOrFunc):
"""
Used to store the parsed contents of a python function.
Children:
0) <Keyword: def>
1) <Name>
2) parameter list (including open-paren and close-paren <Operator>s)
3) <Operator: :>
4) Node() representing function body
5) ??
6) annotation (if present)
"""
__slots__ = ('listeners',)
type = 'funcdef'
@@ -837,6 +846,7 @@ class Function(ClassOrFunc):
@property
def params(self):
# Contents of parameter lit minus the leading <Operator: (> and the trailing <Operator: )>.
return self.children[2].children[1:-1]
@property
@@ -868,10 +878,13 @@ class Function(ClassOrFunc):
:rtype: str
"""
func_name = func_name or self.children[1]
code = unicode(func_name) + self.children[2].get_code()
func_name = func_name or self.name
code = unicode(func_name) + self._get_paramlist_code()
return '\n'.join(textwrap.wrap(code, width))
def _get_paramlist_code(self):
return self.children[2].get_code()
@property
def doc(self):
""" Return a document string including call signature. """
@@ -897,6 +910,12 @@ class Function(ClassOrFunc):
class Lambda(Function):
"""
Lambdas are basically trimmed functions, so give it the same interface.
Children:
0) <Keyword: lambda>
*) <Param x> for each argument x
-2) <Operator: :>
-1) Node() representing body
"""
type = 'lambda'
__slots__ = ()
@@ -905,9 +924,17 @@ class Lambda(Function):
# We don't want to call the Function constructor, call its parent.
super(Function, self).__init__(children)
self.listeners = set() # not used here, but in evaluation.
lst = self.children[1:-2] # After `def foo`
lst = self.children[1:-2] # Everything between `lambda` and the `:` operator is a parameter.
self.children[1:-2] = _create_params(self, lst)
@property
def name(self):
# Borrow the position of the <Keyword: lambda> AST node.
return Name(self.children[0].position_modifier, '<lambda>', self.children[0].start_pos)
def _get_paramlist_code(self):
return '(' + ''.join(param.get_code() for param in self.params).strip() + ')'
@property
def params(self):
return self.children[1:-2]
@@ -915,6 +942,7 @@ class Lambda(Function):
def is_generator(self):
return False
@property
def yields(self):
return []