forked from VimPlug/jedi
Merge branch 'dev' of git://github.com/davidhalter/jedi
This commit is contained in:
@@ -5,9 +5,11 @@ env:
|
|||||||
- TOXENV=py32
|
- TOXENV=py32
|
||||||
- TOXENV=py33
|
- TOXENV=py33
|
||||||
- TOXENV=cov
|
- TOXENV=cov
|
||||||
|
- TOXENV=sith
|
||||||
matrix:
|
matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- env: TOXENV=cov
|
- env: TOXENV=cov
|
||||||
|
- env: TOXENV=sith
|
||||||
install:
|
install:
|
||||||
- pip install --quiet --use-mirrors tox
|
- pip install --quiet --use-mirrors tox
|
||||||
script:
|
script:
|
||||||
|
|||||||
22
README.rst
22
README.rst
@@ -10,6 +10,8 @@ Jedi - an awesome autocompletion library for Python
|
|||||||
:target: https://coveralls.io/r/davidhalter/jedi
|
:target: https://coveralls.io/r/davidhalter/jedi
|
||||||
:alt: Coverage Status
|
:alt: Coverage Status
|
||||||
|
|
||||||
|
.. image:: https://pypip.in/d/jedi/badge.png
|
||||||
|
:target: https://crate.io/packages/jedi/
|
||||||
|
|
||||||
Jedi is an autocompletion tool for Python that can be used in IDEs/editors.
|
Jedi is an autocompletion tool for Python that can be used in IDEs/editors.
|
||||||
Jedi works. Jedi is fast. It understands all of the basic Python syntax
|
Jedi works. Jedi is fast. It understands all of the basic Python syntax
|
||||||
@@ -24,12 +26,15 @@ which uses Jedi's autocompletion. I encourage you to use Jedi in your IDEs.
|
|||||||
It's really easy. If there are any problems (also with licensing), just contact
|
It's really easy. If there are any problems (also with licensing), just contact
|
||||||
me.
|
me.
|
||||||
|
|
||||||
Jedi can be used with the following plugins/software:
|
Jedi can be used with the following editors:
|
||||||
|
|
||||||
- `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_
|
- Vim (jedi-vim_, YouCompleteMe_)
|
||||||
- `Emacs-Plugin <https://github.com/tkf/emacs-jedi>`_
|
- Emacs (Jedi.el_)
|
||||||
- `Sublime-Plugin <https://github.com/svaiter/SublimeJEDI>`_
|
- Sublime Text (SublimeJEDI_)
|
||||||
- `wdb (web debugger) <https://github.com/Kozea/wdb>`_
|
|
||||||
|
And it powers the following projects:
|
||||||
|
|
||||||
|
- wdb_
|
||||||
|
|
||||||
|
|
||||||
Here are some pictures:
|
Here are some pictures:
|
||||||
@@ -121,3 +126,10 @@ Tests are also run automatically on `Travis CI
|
|||||||
|
|
||||||
For more detailed information visit the `testing documentation
|
For more detailed information visit the `testing documentation
|
||||||
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_
|
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_
|
||||||
|
|
||||||
|
|
||||||
|
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||||
|
.. _youcompleteme: http://valloric.github.io/YouCompleteMe/
|
||||||
|
.. _Jedi.el: https://github.com/tkf/emacs-jedi
|
||||||
|
.. _sublimejedi: https://github.com/svaiter/SublimeJEDI
|
||||||
|
.. _wdb: https://github.com/Kozea/wdb
|
||||||
|
|||||||
BIN
docs/_static/logo.png
vendored
BIN
docs/_static/logo.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 25 KiB |
12
docs/conf.py
12
docs/conf.py
@@ -28,7 +28,7 @@ sys.path.append(os.path.abspath('_themes'))
|
|||||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo',
|
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo',
|
||||||
'sphinx.ext.inheritance_diagram']
|
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram']
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
@@ -141,7 +141,7 @@ html_static_path = ['_static']
|
|||||||
# Custom sidebar templates, maps document names to template names.
|
# Custom sidebar templates, maps document names to template names.
|
||||||
html_sidebars = {
|
html_sidebars = {
|
||||||
'**': [
|
'**': [
|
||||||
# 'sidebarlogo.html',
|
'sidebarlogo.html',
|
||||||
'localtoc.html',
|
'localtoc.html',
|
||||||
# 'relations.html',
|
# 'relations.html',
|
||||||
'ghbuttons.html',
|
'ghbuttons.html',
|
||||||
@@ -266,6 +266,14 @@ todo_include_todos = False
|
|||||||
|
|
||||||
# -- Options for autodoc module ------------------------------------------------
|
# -- Options for autodoc module ------------------------------------------------
|
||||||
|
|
||||||
|
autoclass_content = 'both'
|
||||||
autodoc_member_order = 'bysource'
|
autodoc_member_order = 'bysource'
|
||||||
autodoc_default_flags = []
|
autodoc_default_flags = []
|
||||||
#autodoc_default_flags = ['members', 'undoc-members']
|
#autodoc_default_flags = ['members', 'undoc-members']
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for intersphinx module --------------------------------------------
|
||||||
|
|
||||||
|
intersphinx_mapping = {
|
||||||
|
'http://docs.python.org/': None,
|
||||||
|
}
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ Parser Representation (parser_representation.py)
|
|||||||
|
|
||||||
.. automodule:: parsing_representation
|
.. automodule:: parsing_representation
|
||||||
|
|
||||||
|
Class inheritance diagram:
|
||||||
|
|
||||||
.. inheritance-diagram::
|
.. inheritance-diagram::
|
||||||
SubModule
|
SubModule
|
||||||
Class
|
Class
|
||||||
|
|||||||
@@ -30,8 +30,15 @@ System-wide installation via a package manager
|
|||||||
Arch Linux
|
Arch Linux
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
You can install jedi directly from AUR: `python-jedi at AUR
|
You can install |jedi| directly from official AUR packages:
|
||||||
<https://aur.archlinux.org/packages/python-jedi/>`__.
|
|
||||||
|
- `python-jedi <https://aur.archlinux.org/packages/python-jedi/>`__ (Python 3)
|
||||||
|
- `python2-jedi <https://aur.archlinux.org/packages/python2-jedi/>`__ (Python 2)
|
||||||
|
|
||||||
|
The specified Python version just refers to the *runtime environment* for
|
||||||
|
|jedi|. Use the Python 2 version if you're running vim (or whatever editor you
|
||||||
|
use) under Python 2. Otherwise, use the Python 3 version. But whatever version
|
||||||
|
you choose, both are able to complete both Python 2 and 3 *code*.
|
||||||
|
|
||||||
(There is also a packaged version of the vim plugin available: `vim-jedi at AUR
|
(There is also a packaged version of the vim plugin available: `vim-jedi at AUR
|
||||||
<https://aur.archlinux.org/packages/vim-jedi/>`__.)
|
<https://aur.archlinux.org/packages/vim-jedi/>`__.)
|
||||||
@@ -45,7 +52,7 @@ Debian packages are available as `experimental packages
|
|||||||
Others
|
Others
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
We are in the discussion of adding Jedi to the Fedora repositories.
|
We are in the discussion of adding |jedi| to the Fedora repositories.
|
||||||
|
|
||||||
|
|
||||||
Manual installation from a downloaded package
|
Manual installation from a downloaded package
|
||||||
@@ -53,7 +60,7 @@ Manual installation from a downloaded package
|
|||||||
|
|
||||||
If you prefer not to use an automated package installer, you can `download
|
If you prefer not to use an automated package installer, you can `download
|
||||||
<https://github.com/davidhalter/jedi/archive/master.zip>`__ a current copy of
|
<https://github.com/davidhalter/jedi/archive/master.zip>`__ a current copy of
|
||||||
*Jedi* and install it manually.
|
|jedi| and install it manually.
|
||||||
|
|
||||||
To install it, navigate to the directory containing `setup.py` on your console
|
To install it, navigate to the directory containing `setup.py` on your console
|
||||||
and type::
|
and type::
|
||||||
|
|||||||
8
docs/docs/repl.rst
Normal file
8
docs/docs/repl.rst
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.. include:: ../global.rst
|
||||||
|
|
||||||
|
How to use Jedi from Python interpreter
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
.. automodule:: jedi.replstartup
|
||||||
|
|
||||||
|
.. autofunction:: jedi.utils.setup_readline
|
||||||
@@ -22,6 +22,7 @@ Docs
|
|||||||
|
|
||||||
docs/installation
|
docs/installation
|
||||||
docs/features
|
docs/features
|
||||||
|
docs/repl
|
||||||
docs/recipes
|
docs/recipes
|
||||||
docs/plugin-api
|
docs/plugin-api
|
||||||
docs/history
|
docs/history
|
||||||
@@ -44,9 +45,18 @@ Resources
|
|||||||
Editor Plugins
|
Editor Plugins
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
- `Vim <http://github.com/davidhalter/jedi-vim>`_
|
Vim:
|
||||||
- `Emacs <https://github.com/tkf/emacs-jedi>`_
|
|
||||||
- `Sublime Text 2 <https://github.com/svaiter/SublimeJEDI>`_
|
- `jedi-vim <http://github.com/davidhalter/jedi-vim>`_
|
||||||
|
- `YouCompleteMe <http://valloric.github.io/YouCompleteMe/>`_
|
||||||
|
|
||||||
|
Emacs:
|
||||||
|
|
||||||
|
- `Jedi.el <https://github.com/tkf/emacs-jedi>`_
|
||||||
|
|
||||||
|
Sublime Text 2:
|
||||||
|
|
||||||
|
- `SublimeJEDI <https://github.com/svaiter/SublimeJEDI>`_
|
||||||
|
|
||||||
|
|
||||||
.. _other-software:
|
.. _other-software:
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ import sys
|
|||||||
# imports and circular imports... Just avoid it:
|
# imports and circular imports... Just avoid it:
|
||||||
sys.path.insert(0, __path__[0])
|
sys.path.insert(0, __path__[0])
|
||||||
|
|
||||||
from .api import Script, NotFoundError, set_debug_function, _quick_complete, \
|
from .api import Script, Interpreter, NotFoundError, set_debug_function, \
|
||||||
preload_module
|
preload_module, defined_names
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
sys.path.pop(0)
|
sys.path.pop(0)
|
||||||
|
|||||||
2
jedi/__main__.py
Normal file
2
jedi/__main__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from os import path
|
||||||
|
print(path.join(path.dirname(path.abspath(__file__)), 'replstartup.py'))
|
||||||
83
jedi/api.py
83
jedi/api.py
@@ -20,6 +20,7 @@ from jedi import helpers
|
|||||||
from jedi import common
|
from jedi import common
|
||||||
from jedi import cache
|
from jedi import cache
|
||||||
from jedi import modules
|
from jedi import modules
|
||||||
|
from jedi import interpret
|
||||||
from jedi._compatibility import next, unicode
|
from jedi._compatibility import next, unicode
|
||||||
import evaluate
|
import evaluate
|
||||||
import keywords
|
import keywords
|
||||||
@@ -53,14 +54,18 @@ class Script(object):
|
|||||||
``unicode`` object (default ``'utf-8'``).
|
``unicode`` object (default ``'utf-8'``).
|
||||||
:type source_encoding: str
|
:type source_encoding: str
|
||||||
"""
|
"""
|
||||||
def __init__(self, source, line, column, source_path,
|
def __init__(self, source, line=None, column=None, source_path=None,
|
||||||
source_encoding='utf-8'):
|
source_encoding='utf-8'):
|
||||||
|
lines = source.splitlines()
|
||||||
|
line = len(lines) if line is None else line
|
||||||
|
column = len(lines[-1]) if column is None else column
|
||||||
|
|
||||||
api_classes._clear_caches()
|
api_classes._clear_caches()
|
||||||
debug.reset_time()
|
debug.reset_time()
|
||||||
self.source = modules.source_to_unicode(source, source_encoding)
|
self.source = modules.source_to_unicode(source, source_encoding)
|
||||||
self.pos = line, column
|
self.pos = line, column
|
||||||
self._module = modules.ModuleWithCursor(source_path,
|
self._module = modules.ModuleWithCursor(
|
||||||
source=self.source, position=self.pos)
|
source_path, source=self.source, position=self.pos)
|
||||||
self._source_path = source_path
|
self._source_path = source_path
|
||||||
self.source_path = None if source_path is None \
|
self.source_path = None if source_path is None \
|
||||||
else os.path.abspath(source_path)
|
else os.path.abspath(source_path)
|
||||||
@@ -337,7 +342,8 @@ class Script(object):
|
|||||||
|
|
||||||
:rtype: list of :class:`api_classes.Definition`
|
:rtype: list of :class:`api_classes.Definition`
|
||||||
"""
|
"""
|
||||||
d = [api_classes.Definition(d) for d in set(self._goto()[0])]
|
d = [api_classes.Definition(d) for d in set(self._goto()[0])
|
||||||
|
if not isinstance(d, imports.ImportPath._GlobalNamespace)]
|
||||||
return self._sorted_defs(d)
|
return self._sorted_defs(d)
|
||||||
|
|
||||||
def _goto(self, add_import_name=False):
|
def _goto(self, add_import_name=False):
|
||||||
@@ -385,7 +391,9 @@ class Script(object):
|
|||||||
defs, search_name = evaluate.goto(stmt)
|
defs, search_name = evaluate.goto(stmt)
|
||||||
definitions = follow_inexistent_imports(defs)
|
definitions = follow_inexistent_imports(defs)
|
||||||
if isinstance(user_stmt, pr.Statement):
|
if isinstance(user_stmt, pr.Statement):
|
||||||
if user_stmt.get_commands()[0].start_pos > self.pos:
|
call = user_stmt.get_commands()[0]
|
||||||
|
if not isinstance(call, (str, unicode)) and \
|
||||||
|
call.start_pos > self.pos:
|
||||||
# The cursor must be after the start, otherwise the
|
# The cursor must be after the start, otherwise the
|
||||||
# statement is just an assignee.
|
# statement is just an assignee.
|
||||||
definitions = [user_stmt]
|
definitions = [user_stmt]
|
||||||
@@ -503,6 +511,45 @@ class Script(object):
|
|||||||
return sorted(d, key=lambda x: (x.module_path or '', x.start_pos))
|
return sorted(d, key=lambda x: (x.module_path or '', x.start_pos))
|
||||||
|
|
||||||
|
|
||||||
|
class Interpreter(Script):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Jedi API for Python REPLs.
|
||||||
|
|
||||||
|
In addition to completion of simple attribute access, Jedi
|
||||||
|
supports code completion based on static code analysis.
|
||||||
|
Jedi can complete attributes of object which is not initialized
|
||||||
|
yet.
|
||||||
|
|
||||||
|
>>> from os.path import join
|
||||||
|
>>> namespace = locals()
|
||||||
|
>>> script = Interpreter('join().up', [namespace])
|
||||||
|
>>> print(script.complete()[0].word)
|
||||||
|
upper
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, source, namespaces=[], **kwds):
|
||||||
|
"""
|
||||||
|
Parse `source` and mixin interpreted Python objects from `namespaces`.
|
||||||
|
|
||||||
|
:type source: str
|
||||||
|
:arg source: Code to parse.
|
||||||
|
:type namespaces: list of dict
|
||||||
|
:arg namespaces: a list of namespace dictionaries such as the one
|
||||||
|
returned by :func:`locals`.
|
||||||
|
|
||||||
|
Other optional arguments are same as the ones for :class:`Script`.
|
||||||
|
If `line` and `column` are None, they are assumed be at the end of
|
||||||
|
`source`.
|
||||||
|
"""
|
||||||
|
super(Interpreter, self).__init__(source, **kwds)
|
||||||
|
|
||||||
|
importer = interpret.ObjectImporter(self._parser.user_scope)
|
||||||
|
for ns in namespaces:
|
||||||
|
importer.import_raw_namespace(ns)
|
||||||
|
|
||||||
|
|
||||||
def defined_names(source, source_path=None, source_encoding='utf-8'):
|
def defined_names(source, source_path=None, source_encoding='utf-8'):
|
||||||
"""
|
"""
|
||||||
Get all definitions in `source` sorted by its position.
|
Get all definitions in `source` sorted by its position.
|
||||||
@@ -519,7 +566,7 @@ def defined_names(source, source_path=None, source_encoding='utf-8'):
|
|||||||
modules.source_to_unicode(source, source_encoding),
|
modules.source_to_unicode(source, source_encoding),
|
||||||
module_path=source_path,
|
module_path=source_path,
|
||||||
)
|
)
|
||||||
return api_classes._defined_names(parser.scope)
|
return api_classes._defined_names(parser.module)
|
||||||
|
|
||||||
|
|
||||||
def preload_module(*modules):
|
def preload_module(*modules):
|
||||||
@@ -545,25 +592,3 @@ def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
|
|||||||
debug.enable_warning = warnings
|
debug.enable_warning = warnings
|
||||||
debug.enable_notice = notices
|
debug.enable_notice = notices
|
||||||
debug.enable_speed = speed
|
debug.enable_speed = speed
|
||||||
|
|
||||||
|
|
||||||
def _quick_complete(source):
|
|
||||||
"""
|
|
||||||
Convenience function to complete a source string at the end.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
>>> _quick_complete('''
|
|
||||||
... import datetime
|
|
||||||
... datetime.da''') #doctest: +ELLIPSIS
|
|
||||||
[<Completion: date>, <Completion: datetime>, ...]
|
|
||||||
|
|
||||||
:param source: The source code to be completed.
|
|
||||||
:type source: string
|
|
||||||
:return: Completion objects as returned by :meth:`complete`.
|
|
||||||
:rtype: list of :class:`api_classes.Completion`
|
|
||||||
"""
|
|
||||||
lines = re.sub(r'[\n\r\s]*$', '', source).splitlines()
|
|
||||||
pos = len(lines), len(lines[-1])
|
|
||||||
script = Script(source, pos[0], pos[1], '')
|
|
||||||
return script.completions()
|
|
||||||
|
|||||||
@@ -142,9 +142,19 @@ class BaseDefinition(object):
|
|||||||
def path(self):
|
def path(self):
|
||||||
"""The module path."""
|
"""The module path."""
|
||||||
path = []
|
path = []
|
||||||
|
|
||||||
|
def insert_nonnone(x):
|
||||||
|
if x:
|
||||||
|
path.insert(0, x)
|
||||||
|
|
||||||
if not isinstance(self._definition, keywords.Keyword):
|
if not isinstance(self._definition, keywords.Keyword):
|
||||||
par = self._definition
|
par = self._definition
|
||||||
while par is not None:
|
while par is not None:
|
||||||
|
if isinstance(par, pr.Import):
|
||||||
|
insert_nonnone(par.namespace)
|
||||||
|
insert_nonnone(par.from_ns)
|
||||||
|
if par.relative_count == 0:
|
||||||
|
break
|
||||||
with common.ignored(AttributeError):
|
with common.ignored(AttributeError):
|
||||||
path.insert(0, par.name)
|
path.insert(0, par.name)
|
||||||
par = par.parent
|
par = par.parent
|
||||||
|
|||||||
@@ -318,7 +318,9 @@ def find_name(scope, name_str, position=None, search_global=False,
|
|||||||
exc = pr.Class, pr.Function
|
exc = pr.Class, pr.Function
|
||||||
until = lambda: par.parent.parent.get_parent_until(exc)
|
until = lambda: par.parent.parent.get_parent_until(exc)
|
||||||
|
|
||||||
if par.isinstance(pr.Flow):
|
if par is None:
|
||||||
|
pass
|
||||||
|
elif par.isinstance(pr.Flow):
|
||||||
if par.command == 'for':
|
if par.command == 'for':
|
||||||
result += handle_for_loops(par)
|
result += handle_for_loops(par)
|
||||||
else:
|
else:
|
||||||
@@ -554,16 +556,33 @@ def assign_tuples(tup, results, seek_name):
|
|||||||
else:
|
else:
|
||||||
r = eval_results(i)
|
r = eval_results(i)
|
||||||
|
|
||||||
# are there still tuples or is it just a Call.
|
# LHS of tuples can be nested, so resolve it recursively
|
||||||
if isinstance(command, pr.Array):
|
result += find_assignments(command, r, seek_name)
|
||||||
# These are "sub"-tuples.
|
|
||||||
result += assign_tuples(command, r, seek_name)
|
|
||||||
else:
|
|
||||||
if command.name.names[-1] == seek_name:
|
|
||||||
result += r
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def find_assignments(lhs, results, seek_name):
|
||||||
|
"""
|
||||||
|
Check if `seek_name` is in the left hand side `lhs` of assignment.
|
||||||
|
|
||||||
|
`lhs` can simply be a variable (`pr.Call`) or a tuple/list (`pr.Array`)
|
||||||
|
representing the following cases::
|
||||||
|
|
||||||
|
a = 1 # lhs is pr.Call
|
||||||
|
(a, b) = 2 # lhs is pr.Array
|
||||||
|
|
||||||
|
:type lhs: pr.Call
|
||||||
|
:type results: list
|
||||||
|
:type seek_name: str
|
||||||
|
"""
|
||||||
|
if isinstance(lhs, pr.Array):
|
||||||
|
return assign_tuples(lhs, results, seek_name)
|
||||||
|
elif lhs.name.names[-1] == seek_name:
|
||||||
|
return results
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
@recursion.RecursionDecorator
|
@recursion.RecursionDecorator
|
||||||
@cache.memoize_default(default=())
|
@cache.memoize_default(default=())
|
||||||
def follow_statement(stmt, seek_name=None):
|
def follow_statement(stmt, seek_name=None):
|
||||||
@@ -587,7 +606,7 @@ def follow_statement(stmt, seek_name=None):
|
|||||||
if len(stmt.get_set_vars()) > 1 and seek_name and stmt.assignment_details:
|
if len(stmt.get_set_vars()) > 1 and seek_name and stmt.assignment_details:
|
||||||
new_result = []
|
new_result = []
|
||||||
for ass_commands, op in stmt.assignment_details:
|
for ass_commands, op in stmt.assignment_details:
|
||||||
new_result += assign_tuples(ass_commands[0], result, seek_name)
|
new_result += find_assignments(ass_commands[0], result, seek_name)
|
||||||
result = new_result
|
result = new_result
|
||||||
return set(result)
|
return set(result)
|
||||||
|
|
||||||
@@ -775,7 +794,10 @@ def goto(stmt, call_path=None):
|
|||||||
commands = stmt.get_commands()
|
commands = stmt.get_commands()
|
||||||
assert len(commands) == 1
|
assert len(commands) == 1
|
||||||
call = commands[0]
|
call = commands[0]
|
||||||
call_path = list(call.generate_call_path())
|
if isinstance(call, (str, unicode)):
|
||||||
|
call_path = [call]
|
||||||
|
else:
|
||||||
|
call_path = list(call.generate_call_path())
|
||||||
|
|
||||||
scope = stmt.get_parent_until(pr.IsScope)
|
scope = stmt.get_parent_until(pr.IsScope)
|
||||||
pos = stmt.start_pos
|
pos = stmt.start_pos
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ class ImportPath(pr.Base):
|
|||||||
# If you edit e.g. gunicorn, there will be imports like this:
|
# If you edit e.g. gunicorn, there will be imports like this:
|
||||||
# `from gunicorn import something`. But gunicorn is not in the
|
# `from gunicorn import something`. But gunicorn is not in the
|
||||||
# sys.path. Therefore look if gunicorn is a parent directory, #56.
|
# sys.path. Therefore look if gunicorn is a parent directory, #56.
|
||||||
parts = self.file_path.split(os.path.sep)
|
|
||||||
in_path = []
|
in_path = []
|
||||||
if self.import_path:
|
if self.import_path:
|
||||||
|
parts = self.file_path.split(os.path.sep)
|
||||||
for i, p in enumerate(parts):
|
for i, p in enumerate(parts):
|
||||||
if p == self.import_path[0]:
|
if p == self.import_path[0]:
|
||||||
new = os.path.sep.join(parts[:i])
|
new = os.path.sep.join(parts[:i])
|
||||||
|
|||||||
171
jedi/interpret.py
Normal file
171
jedi/interpret.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
"""
|
||||||
|
Module to handle interpreted Python objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import itertools
|
||||||
|
import tokenize
|
||||||
|
|
||||||
|
from jedi import parsing_representation as pr
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectImporter(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Import objects in "raw" namespace such as :func:`locals`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, scope):
|
||||||
|
self.scope = scope
|
||||||
|
|
||||||
|
count = itertools.count()
|
||||||
|
self._genname = lambda: '*jedi-%s*' % next(count)
|
||||||
|
"""
|
||||||
|
Generate unique variable names to avoid name collision.
|
||||||
|
To avoid name collision to already defined names, generated
|
||||||
|
names are invalid as Python identifier.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def import_raw_namespace(self, raw_namespace):
|
||||||
|
"""
|
||||||
|
Import interpreted Python objects in a namespace.
|
||||||
|
|
||||||
|
Three kinds of objects are treated here.
|
||||||
|
|
||||||
|
1. Functions and classes. The objects imported like this::
|
||||||
|
|
||||||
|
from os.path import join
|
||||||
|
|
||||||
|
2. Modules. The objects imported like this::
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
3. Instances. The objects created like this::
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
dt = datetime(2013, 1, 1)
|
||||||
|
|
||||||
|
:type raw_namespace: dict
|
||||||
|
:arg raw_namespace: e.g., the dict given by `locals`
|
||||||
|
"""
|
||||||
|
scope = self.scope
|
||||||
|
for (variable, obj) in raw_namespace.items():
|
||||||
|
objname = getattr(obj, '__name__', None)
|
||||||
|
|
||||||
|
# Import functions and classes
|
||||||
|
module = getattr(obj, '__module__', None)
|
||||||
|
if module and objname:
|
||||||
|
fakeimport = self.make_fakeimport(module, objname, variable)
|
||||||
|
scope.add_import(fakeimport)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Import modules
|
||||||
|
if getattr(obj, '__file__', None) and objname:
|
||||||
|
fakeimport = self.make_fakeimport(objname)
|
||||||
|
scope.add_import(fakeimport)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Import instances
|
||||||
|
objclass = getattr(obj, '__class__', None)
|
||||||
|
module = getattr(objclass, '__module__', None)
|
||||||
|
if objclass and module:
|
||||||
|
alias = self._genname()
|
||||||
|
fakeimport = self.make_fakeimport(module, objclass.__name__,
|
||||||
|
alias)
|
||||||
|
fakestmt = self.make_fakestatement(variable, alias, call=True)
|
||||||
|
scope.add_import(fakeimport)
|
||||||
|
scope.add_statement(fakestmt)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def make_fakeimport(self, module, variable=None, alias=None):
|
||||||
|
"""
|
||||||
|
Make a fake import object.
|
||||||
|
|
||||||
|
The following statements are created depending on what parameters
|
||||||
|
are given:
|
||||||
|
|
||||||
|
- only `module`: ``import <module>``
|
||||||
|
- `module` and `variable`: ``from <module> import <variable>``
|
||||||
|
- all: ``from <module> import <variable> as <alias>``
|
||||||
|
|
||||||
|
:type module: str
|
||||||
|
:arg module: ``<module>`` part in ``from <module> import ...``
|
||||||
|
:type variable: str
|
||||||
|
:arg variable: ``<variable>`` part in ``from ... import <variable>``
|
||||||
|
:type alias: str
|
||||||
|
:arg alias: ``<alias>`` part in ``... import ... as <alias>``.
|
||||||
|
|
||||||
|
:rtype: :class:`parsing_representation.Import`
|
||||||
|
"""
|
||||||
|
submodule = self.scope._sub_module
|
||||||
|
if variable:
|
||||||
|
varname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(variable, (-1, 0))],
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
else:
|
||||||
|
varname = None
|
||||||
|
modname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(module, (-1, 0))],
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
if alias:
|
||||||
|
aliasname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(alias, (-1, 0))],
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
else:
|
||||||
|
aliasname = None
|
||||||
|
if varname:
|
||||||
|
fakeimport = pr.Import(
|
||||||
|
module=submodule,
|
||||||
|
namespace=varname,
|
||||||
|
from_ns=modname,
|
||||||
|
alias=aliasname,
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
else:
|
||||||
|
fakeimport = pr.Import(
|
||||||
|
module=submodule,
|
||||||
|
namespace=modname,
|
||||||
|
alias=aliasname,
|
||||||
|
start_pos=(-1, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
return fakeimport
|
||||||
|
|
||||||
|
def make_fakestatement(self, lhs, rhs, call=False):
|
||||||
|
"""
|
||||||
|
Make a fake statement object that represents ``lhs = rhs``.
|
||||||
|
|
||||||
|
:type call: bool
|
||||||
|
:arg call: When `call` is true, make a fake statement that represents
|
||||||
|
``lhs = rhs()``.
|
||||||
|
|
||||||
|
:rtype: :class:`parsing_representation.Statement`
|
||||||
|
"""
|
||||||
|
submodule = self.scope._sub_module
|
||||||
|
lhsname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(lhs, (0, 0))],
|
||||||
|
start_pos=(0, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
rhsname = pr.Name(
|
||||||
|
module=submodule,
|
||||||
|
names=[(rhs, (0, 0))],
|
||||||
|
start_pos=(0, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
|
token_list = [lhsname, (tokenize.OP, '=', (0, 0)), rhsname]
|
||||||
|
if call:
|
||||||
|
token_list.extend([
|
||||||
|
(tokenize.OP, '(', (0, 0)),
|
||||||
|
(tokenize.OP, ')', (0, 0)),
|
||||||
|
])
|
||||||
|
return pr.Statement(
|
||||||
|
module=submodule,
|
||||||
|
set_vars=[lhsname],
|
||||||
|
used_vars=[rhsname],
|
||||||
|
token_list=token_list,
|
||||||
|
start_pos=(0, 0),
|
||||||
|
end_pos=(None, None))
|
||||||
@@ -141,6 +141,7 @@ class ModuleWithCursor(Module):
|
|||||||
last_line = self.get_line(self._line_temp)
|
last_line = self.get_line(self._line_temp)
|
||||||
if last_line and last_line[-1] == '\\':
|
if last_line and last_line[-1] == '\\':
|
||||||
line = last_line[:-1] + ' ' + line
|
line = last_line[:-1] + ' ' + line
|
||||||
|
self._line_length = len(last_line)
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
return line[::-1]
|
return line[::-1]
|
||||||
@@ -187,6 +188,7 @@ class ModuleWithCursor(Module):
|
|||||||
elif token_type == tokenize.NUMBER:
|
elif token_type == tokenize.NUMBER:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
|
self._column_temp = self._line_length - end[1]
|
||||||
break
|
break
|
||||||
|
|
||||||
self._column_temp = self._line_length - end[1]
|
self._column_temp = self._line_length - end[1]
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class Parser(object):
|
|||||||
self.start_pos = self.end_pos = 1 + offset[0], offset[1]
|
self.start_pos = self.end_pos = 1 + offset[0], offset[1]
|
||||||
# initialize global Scope
|
# initialize global Scope
|
||||||
self.module = pr.SubModule(module_path, self.start_pos, top_module)
|
self.module = pr.SubModule(module_path, self.start_pos, top_module)
|
||||||
self.scope = self.module
|
self._scope = self.module
|
||||||
self.current = (None, None)
|
self.current = (None, None)
|
||||||
|
|
||||||
source = source + '\n' # end with \n, because the parser needs it
|
source = source + '\n' # end with \n, because the parser needs it
|
||||||
@@ -390,7 +390,7 @@ class Parser(object):
|
|||||||
#print 'new_stat', set_vars, used_vars
|
#print 'new_stat', set_vars, used_vars
|
||||||
if self.freshscope and not self.no_docstr and len(tok_list) == 1 \
|
if self.freshscope and not self.no_docstr and len(tok_list) == 1 \
|
||||||
and self.last_token[0] == tokenize.STRING:
|
and self.last_token[0] == tokenize.STRING:
|
||||||
self.scope.add_docstr(self.last_token[1])
|
self._scope.add_docstr(self.last_token[1])
|
||||||
return None, tok
|
return None, tok
|
||||||
else:
|
else:
|
||||||
stmt = stmt_class(self.module, set_vars, used_vars, tok_list,
|
stmt = stmt_class(self.module, set_vars, used_vars, tok_list,
|
||||||
@@ -408,7 +408,7 @@ class Parser(object):
|
|||||||
and len(stmt.token_list) == 1
|
and len(stmt.token_list) == 1
|
||||||
and first_tok[0] == tokenize.STRING):
|
and first_tok[0] == tokenize.STRING):
|
||||||
# ... then set it as a docstring
|
# ... then set it as a docstring
|
||||||
self.scope.statements[-1].add_docstr(first_tok[1])
|
self._scope.statements[-1].add_docstr(first_tok[1])
|
||||||
|
|
||||||
if tok in always_break + not_first_break:
|
if tok in always_break + not_first_break:
|
||||||
self._gen.push_last_back()
|
self._gen.push_last_back()
|
||||||
@@ -429,7 +429,7 @@ class Parser(object):
|
|||||||
self.start_pos, self.end_pos = start_pos, end_pos
|
self.start_pos, self.end_pos = start_pos, end_pos
|
||||||
except (StopIteration, common.MultiLevelStopIteration):
|
except (StopIteration, common.MultiLevelStopIteration):
|
||||||
# on finish, set end_pos correctly
|
# on finish, set end_pos correctly
|
||||||
s = self.scope
|
s = self._scope
|
||||||
while s is not None:
|
while s is not None:
|
||||||
if isinstance(s, pr.Module) \
|
if isinstance(s, pr.Module) \
|
||||||
and not isinstance(s, pr.SubModule):
|
and not isinstance(s, pr.SubModule):
|
||||||
@@ -443,8 +443,8 @@ class Parser(object):
|
|||||||
or self.user_scope is None
|
or self.user_scope is None
|
||||||
and self.start_pos[0] >= self.user_position[0]):
|
and self.start_pos[0] >= self.user_position[0]):
|
||||||
debug.dbg('user scope found [%s] = %s' %
|
debug.dbg('user scope found [%s] = %s' %
|
||||||
(self.parserline.replace('\n', ''), repr(self.scope)))
|
(self.parserline.replace('\n', ''), repr(self._scope)))
|
||||||
self.user_scope = self.scope
|
self.user_scope = self._scope
|
||||||
self.last_token = self.current
|
self.last_token = self.current
|
||||||
self.current = (typ, tok)
|
self.current = (typ, tok)
|
||||||
return self.current
|
return self.current
|
||||||
@@ -472,29 +472,29 @@ class Parser(object):
|
|||||||
#debug.dbg('main: tok=[%s] type=[%s] indent=[%s]'\
|
#debug.dbg('main: tok=[%s] type=[%s] indent=[%s]'\
|
||||||
# % (tok, tokenize.tok_name[token_type], start_position[0]))
|
# % (tok, tokenize.tok_name[token_type], start_position[0]))
|
||||||
|
|
||||||
while token_type == tokenize.DEDENT and self.scope != self.module:
|
while token_type == tokenize.DEDENT and self._scope != self.module:
|
||||||
token_type, tok = self.next()
|
token_type, tok = self.next()
|
||||||
if self.start_pos[1] <= self.scope.start_pos[1]:
|
if self.start_pos[1] <= self._scope.start_pos[1]:
|
||||||
self.scope.end_pos = self.start_pos
|
self._scope.end_pos = self.start_pos
|
||||||
self.scope = self.scope.parent
|
self._scope = self._scope.parent
|
||||||
if isinstance(self.scope, pr.Module) \
|
if isinstance(self._scope, pr.Module) \
|
||||||
and not isinstance(self.scope, pr.SubModule):
|
and not isinstance(self._scope, pr.SubModule):
|
||||||
self.scope = self.module
|
self._scope = self.module
|
||||||
|
|
||||||
# check again for unindented stuff. this is true for syntax
|
# check again for unindented stuff. this is true for syntax
|
||||||
# errors. only check for names, because thats relevant here. If
|
# errors. only check for names, because thats relevant here. If
|
||||||
# some docstrings are not indented, I don't care.
|
# some docstrings are not indented, I don't care.
|
||||||
while self.start_pos[1] <= self.scope.start_pos[1] \
|
while self.start_pos[1] <= self._scope.start_pos[1] \
|
||||||
and (token_type == tokenize.NAME or tok in ['(', '['])\
|
and (token_type == tokenize.NAME or tok in ['(', '['])\
|
||||||
and self.scope != self.module:
|
and self._scope != self.module:
|
||||||
self.scope.end_pos = self.start_pos
|
self._scope.end_pos = self.start_pos
|
||||||
self.scope = self.scope.parent
|
self._scope = self._scope.parent
|
||||||
if isinstance(self.scope, pr.Module) \
|
if isinstance(self._scope, pr.Module) \
|
||||||
and not isinstance(self.scope, pr.SubModule):
|
and not isinstance(self._scope, pr.SubModule):
|
||||||
self.scope = self.module
|
self._scope = self.module
|
||||||
|
|
||||||
use_as_parent_scope = self.top_module if isinstance(self.scope,
|
use_as_parent_scope = self.top_module if isinstance(self._scope,
|
||||||
pr.SubModule) else self.scope
|
pr.SubModule) else self._scope
|
||||||
first_pos = self.start_pos
|
first_pos = self.start_pos
|
||||||
if tok == 'def':
|
if tok == 'def':
|
||||||
func = self._parse_function()
|
func = self._parse_function()
|
||||||
@@ -503,7 +503,7 @@ class Parser(object):
|
|||||||
self.start_pos[0])
|
self.start_pos[0])
|
||||||
continue
|
continue
|
||||||
self.freshscope = True
|
self.freshscope = True
|
||||||
self.scope = self.scope.add_scope(func, self._decorators)
|
self._scope = self._scope.add_scope(func, self._decorators)
|
||||||
self._decorators = []
|
self._decorators = []
|
||||||
elif tok == 'class':
|
elif tok == 'class':
|
||||||
cls = self._parse_class()
|
cls = self._parse_class()
|
||||||
@@ -511,7 +511,7 @@ class Parser(object):
|
|||||||
debug.warning("class: syntax error@%s" % self.start_pos[0])
|
debug.warning("class: syntax error@%s" % self.start_pos[0])
|
||||||
continue
|
continue
|
||||||
self.freshscope = True
|
self.freshscope = True
|
||||||
self.scope = self.scope.add_scope(cls, self._decorators)
|
self._scope = self._scope.add_scope(cls, self._decorators)
|
||||||
self._decorators = []
|
self._decorators = []
|
||||||
# import stuff
|
# import stuff
|
||||||
elif tok == 'import':
|
elif tok == 'import':
|
||||||
@@ -522,7 +522,7 @@ class Parser(object):
|
|||||||
i = pr.Import(self.module, first_pos, end_pos, m,
|
i = pr.Import(self.module, first_pos, end_pos, m,
|
||||||
alias, defunct=defunct)
|
alias, defunct=defunct)
|
||||||
self._check_user_stmt(i)
|
self._check_user_stmt(i)
|
||||||
self.scope.add_import(i)
|
self._scope.add_import(i)
|
||||||
if not imports:
|
if not imports:
|
||||||
i = pr.Import(self.module, first_pos, self.end_pos, None,
|
i = pr.Import(self.module, first_pos, self.end_pos, None,
|
||||||
defunct=True)
|
defunct=True)
|
||||||
@@ -559,7 +559,7 @@ class Parser(object):
|
|||||||
alias, mod, star, relative_count,
|
alias, mod, star, relative_count,
|
||||||
defunct=defunct or defunct2)
|
defunct=defunct or defunct2)
|
||||||
self._check_user_stmt(i)
|
self._check_user_stmt(i)
|
||||||
self.scope.add_import(i)
|
self._scope.add_import(i)
|
||||||
self.freshscope = False
|
self.freshscope = False
|
||||||
#loops
|
#loops
|
||||||
elif tok == 'for':
|
elif tok == 'for':
|
||||||
@@ -569,7 +569,7 @@ class Parser(object):
|
|||||||
if tok == ':':
|
if tok == ':':
|
||||||
s = [] if statement is None else [statement]
|
s = [] if statement is None else [statement]
|
||||||
f = pr.ForFlow(self.module, s, first_pos, set_stmt)
|
f = pr.ForFlow(self.module, s, first_pos, set_stmt)
|
||||||
self.scope = self.scope.add_statement(f)
|
self._scope = self._scope.add_statement(f)
|
||||||
else:
|
else:
|
||||||
debug.warning('syntax err, for flow started @%s',
|
debug.warning('syntax err, for flow started @%s',
|
||||||
self.start_pos[0])
|
self.start_pos[0])
|
||||||
@@ -612,13 +612,13 @@ class Parser(object):
|
|||||||
# the flow statement, because a dedent releases the
|
# the flow statement, because a dedent releases the
|
||||||
# main scope, so just take the last statement.
|
# main scope, so just take the last statement.
|
||||||
try:
|
try:
|
||||||
s = self.scope.statements[-1].set_next(f)
|
s = self._scope.statements[-1].set_next(f)
|
||||||
except (AttributeError, IndexError):
|
except (AttributeError, IndexError):
|
||||||
# If set_next doesn't exist, just add it.
|
# If set_next doesn't exist, just add it.
|
||||||
s = self.scope.add_statement(f)
|
s = self._scope.add_statement(f)
|
||||||
else:
|
else:
|
||||||
s = self.scope.add_statement(f)
|
s = self._scope.add_statement(f)
|
||||||
self.scope = s
|
self._scope = s
|
||||||
else:
|
else:
|
||||||
for i in inputs:
|
for i in inputs:
|
||||||
i.parent = use_as_parent_scope
|
i.parent = use_as_parent_scope
|
||||||
@@ -629,7 +629,7 @@ class Parser(object):
|
|||||||
s = self.start_pos
|
s = self.start_pos
|
||||||
self.freshscope = False
|
self.freshscope = False
|
||||||
# add returns to the scope
|
# add returns to the scope
|
||||||
func = self.scope.get_parent_until(pr.Function)
|
func = self._scope.get_parent_until(pr.Function)
|
||||||
if tok == 'yield':
|
if tok == 'yield':
|
||||||
func.is_generator = True
|
func.is_generator = True
|
||||||
|
|
||||||
@@ -646,7 +646,7 @@ class Parser(object):
|
|||||||
elif tok == 'global':
|
elif tok == 'global':
|
||||||
stmt, tok = self._parse_statement(self.current)
|
stmt, tok = self._parse_statement(self.current)
|
||||||
if stmt:
|
if stmt:
|
||||||
self.scope.add_statement(stmt)
|
self._scope.add_statement(stmt)
|
||||||
for name in stmt.used_vars:
|
for name in stmt.used_vars:
|
||||||
# add the global to the top, because there it is
|
# add the global to the top, because there it is
|
||||||
# important.
|
# important.
|
||||||
@@ -659,8 +659,9 @@ class Parser(object):
|
|||||||
continue
|
continue
|
||||||
elif tok == 'assert':
|
elif tok == 'assert':
|
||||||
stmt, tok = self._parse_statement()
|
stmt, tok = self._parse_statement()
|
||||||
stmt.parent = use_as_parent_scope
|
if stmt is not None:
|
||||||
self.scope.asserts.append(stmt)
|
stmt.parent = use_as_parent_scope
|
||||||
|
self._scope.asserts.append(stmt)
|
||||||
# default
|
# default
|
||||||
elif token_type in [tokenize.NAME, tokenize.STRING,
|
elif token_type in [tokenize.NAME, tokenize.STRING,
|
||||||
tokenize.NUMBER] \
|
tokenize.NUMBER] \
|
||||||
@@ -670,7 +671,7 @@ class Parser(object):
|
|||||||
# by the statement parser.
|
# by the statement parser.
|
||||||
stmt, tok = self._parse_statement(self.current)
|
stmt, tok = self._parse_statement(self.current)
|
||||||
if stmt:
|
if stmt:
|
||||||
self.scope.add_statement(stmt)
|
self._scope.add_statement(stmt)
|
||||||
self.freshscope = False
|
self.freshscope = False
|
||||||
else:
|
else:
|
||||||
if token_type not in [tokenize.COMMENT, tokenize.INDENT,
|
if token_type not in [tokenize.COMMENT, tokenize.INDENT,
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ is the easiest way to write a parser. The same behaviour applies to ``Param``,
|
|||||||
which is being used in a function definition.
|
which is being used in a function definition.
|
||||||
|
|
||||||
The easiest way to play with this module is to use :class:`parsing.Parser`.
|
The easiest way to play with this module is to use :class:`parsing.Parser`.
|
||||||
:attr:`parsing.Parser.scope` holds an instance of :class:`SubModule`:
|
:attr:`parsing.Parser.module` holds an instance of :class:`SubModule`:
|
||||||
|
|
||||||
>>> from jedi.parsing import Parser
|
>>> from jedi.parsing import Parser
|
||||||
>>> parser = Parser('import os', 'example.py')
|
>>> parser = Parser('import os', 'example.py')
|
||||||
>>> submodule = parser.scope
|
>>> submodule = parser.module
|
||||||
>>> submodule
|
>>> submodule
|
||||||
<SubModule: example.py@1-1>
|
<SubModule: example.py@1-1>
|
||||||
|
|
||||||
@@ -248,14 +248,14 @@ class Scope(Simple, IsScope):
|
|||||||
... b = y
|
... b = y
|
||||||
... b.c = z
|
... b.c = z
|
||||||
... ''')
|
... ''')
|
||||||
>>> parser.scope.get_defined_names()
|
>>> parser.module.get_defined_names()
|
||||||
[<Name: a@2,0>, <Name: b@3,0>]
|
[<Name: a@2,0>, <Name: b@3,0>]
|
||||||
|
|
||||||
Note that unlike :meth:`get_set_vars`, assignment to object
|
Note that unlike :meth:`get_set_vars`, assignment to object
|
||||||
attribute does not change the result because it does not change
|
attribute does not change the result because it does not change
|
||||||
the defined names in this scope.
|
the defined names in this scope.
|
||||||
|
|
||||||
>>> parser.scope.get_set_vars()
|
>>> parser.module.get_set_vars()
|
||||||
[<Name: a@2,0>, <Name: b@3,0>, <Name: b.c@4,0>]
|
[<Name: a@2,0>, <Name: b@3,0>, <Name: b.c@4,0>]
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
25
jedi/replstartup.py
Normal file
25
jedi/replstartup.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
"""
|
||||||
|
``PYTHONSTARTUP`` to use Jedi in your Python interpreter.
|
||||||
|
|
||||||
|
To use Jedi completion in Python interpreter, add the following in your shell
|
||||||
|
setup (e.g., ``.bashrc``)::
|
||||||
|
|
||||||
|
export PYTHONSTARTUP="$(python -m jedi)"
|
||||||
|
|
||||||
|
Then you will be able to use Jedi completer in your Python interpreter::
|
||||||
|
|
||||||
|
$ python
|
||||||
|
Python 2.7.2+ (default, Jul 20 2012, 22:15:08)
|
||||||
|
[GCC 4.6.1] on linux2
|
||||||
|
Type "help", "copyright", "credits" or "license" for more information.
|
||||||
|
>>> import os
|
||||||
|
>>> os.path.join().split().in<TAB> # doctest: +SKIP
|
||||||
|
os.path.join().split().index os.path.join().split().insert
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import jedi.utils
|
||||||
|
jedi.utils.setup_readline()
|
||||||
|
del jedi
|
||||||
|
# Note: try not to do many things here, as it will contaminate global
|
||||||
|
# namespace of the interpreter.
|
||||||
@@ -127,8 +127,10 @@ cache_directory = os.path.expanduser(_cache_directory)
|
|||||||
"""
|
"""
|
||||||
The path where all the caches can be found.
|
The path where all the caches can be found.
|
||||||
|
|
||||||
On Linux, this defaults to ``~/.cache/jedi/``, on OS X to ``~/.jedi/`` and on
|
On Linux, this defaults to ``~/.cache/jedi/``, on OS X to
|
||||||
Windows to ``%APPDATA%\\Jedi\\Jedi\\``.
|
``~/Library/Caches/Jedi/`` and on Windows to ``%APPDATA%\\Jedi\\Jedi\\``.
|
||||||
|
On Linux, if environment variable ``$XDG_CACHE_HOME`` is set,
|
||||||
|
``$XDG_CACHE_HOME/jedi`` is used instead of the default one.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# ----------------
|
# ----------------
|
||||||
|
|||||||
110
jedi/utils.py
Normal file
110
jedi/utils.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
"""
|
||||||
|
Utilities for end-users.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from rlcompleter import Completer
|
||||||
|
|
||||||
|
from jedi import Interpreter
|
||||||
|
|
||||||
|
|
||||||
|
_NON_DELIMS = ' \t\n()'
|
||||||
|
"""
|
||||||
|
:class:`rcompleter.Completer` assumes these characters to be delimiter
|
||||||
|
(i.e., :meth:`rcompleter.Completer.complete` does not expect these
|
||||||
|
characters) but :class:`JediRLCompleter` can handle them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
import readline
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
_READLINE_DEFAULT_DELIMS = readline.get_completer_delims()
|
||||||
|
_READLINE_JEDI_DELIMS = ''.join(
|
||||||
|
set(_READLINE_DEFAULT_DELIMS) - set(_NON_DELIMS))
|
||||||
|
|
||||||
|
|
||||||
|
class JediRLCompleter(Completer):
|
||||||
|
|
||||||
|
"""
|
||||||
|
:class:`rlcompleter.Completer` enhanced by Jedi.
|
||||||
|
|
||||||
|
This class tries matchers defined in :class:`.Completer` first.
|
||||||
|
If they fail, :class:`jedi.Interpreter` is used.
|
||||||
|
|
||||||
|
>>> import os
|
||||||
|
>>> completer = JediRLCompleter(locals())
|
||||||
|
>>> completer.complete('os.path.joi', 0) # completion w/o Jedi
|
||||||
|
'os.path.join('
|
||||||
|
>>> completer.complete('os.path.join().s', 0) # completion with Jedi
|
||||||
|
'os.path.join().split'
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _jedi_matches(self, text):
|
||||||
|
completions = Interpreter(text, [self.namespace]).completions()
|
||||||
|
return [text + c.complete for c in completions]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _split_for_default_matcher(text, delims=_NON_DELIMS):
|
||||||
|
"""
|
||||||
|
Split `text` before passing it to :meth:`Completer.attr_matches` etc.
|
||||||
|
|
||||||
|
>>> JediRLCompleter._split_for_default_matcher('f(')
|
||||||
|
('f(', '')
|
||||||
|
>>> JediRLCompleter._split_for_default_matcher('f().g')
|
||||||
|
('f()', '.g')
|
||||||
|
|
||||||
|
"""
|
||||||
|
import re
|
||||||
|
m = re.match(r"(.*[{0}])([^{0}]*)".format(re.escape(delims)), text)
|
||||||
|
if not m:
|
||||||
|
return ('', text)
|
||||||
|
return m.groups()
|
||||||
|
|
||||||
|
def _find_matches(self, default_matcher, text):
|
||||||
|
"""
|
||||||
|
Common part for :meth:`attr_matches` and :meth:`global_matches`.
|
||||||
|
|
||||||
|
Try `default_matcher` first and return what it returns if
|
||||||
|
it is not empty. Otherwise, try :meth:`_jedi_matches`.
|
||||||
|
|
||||||
|
:arg default_matcher: :meth:`.Completer.attr_matches` or
|
||||||
|
:meth:`.Completer.global_matches`.
|
||||||
|
:arg str text: code to complete
|
||||||
|
"""
|
||||||
|
(pre, body) = self._split_for_default_matcher(text)
|
||||||
|
matches = default_matcher(self, body)
|
||||||
|
if matches:
|
||||||
|
return [pre + m for m in matches]
|
||||||
|
return self._jedi_matches(text)
|
||||||
|
|
||||||
|
def attr_matches(self, text):
|
||||||
|
# NOTE: Completer is old type class so `super` cannot be used here
|
||||||
|
return self._find_matches(Completer.attr_matches, text)
|
||||||
|
|
||||||
|
def global_matches(self, text):
|
||||||
|
# NOTE: Completer is old type class so `super` cannot be used here
|
||||||
|
return self._find_matches(Completer.global_matches, text)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_readline():
|
||||||
|
"""
|
||||||
|
Install Jedi completer to :mod:`readline`.
|
||||||
|
|
||||||
|
This function setups :mod:`readline` to use Jedi in Python interactive
|
||||||
|
shell. If you want to use custom ``PYTHONSTARTUP`` file, you can call
|
||||||
|
this function like this:
|
||||||
|
|
||||||
|
>>> from jedi.utils import setup_readline
|
||||||
|
>>> setup_readline()
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import readline
|
||||||
|
except ImportError:
|
||||||
|
print("Module readline not available.")
|
||||||
|
else:
|
||||||
|
readline.set_completer(JediRLCompleter().complete)
|
||||||
|
readline.parse_and_bind("tab: complete")
|
||||||
|
readline.set_completer_delims(_READLINE_JEDI_DELIMS)
|
||||||
311
sith.py
Executable file
311
sith.py
Executable file
@@ -0,0 +1,311 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Sith attacks (and helps debugging) Jedi.
|
||||||
|
|
||||||
|
Randomly search Python files and run Jedi on it. Exception and used
|
||||||
|
arguments are recorded to ``./record.json`` (specified by --record)::
|
||||||
|
|
||||||
|
%(prog)s random /path/to/sourcecode
|
||||||
|
|
||||||
|
Redo recorded exception::
|
||||||
|
|
||||||
|
%(prog)s redo
|
||||||
|
|
||||||
|
Fallback to pdb when error is raised::
|
||||||
|
|
||||||
|
%(prog)s --pdb random
|
||||||
|
%(prog)s --pdb redo
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import print_function, division, unicode_literals
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import random
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import jedi
|
||||||
|
|
||||||
|
_unspecified = object()
|
||||||
|
|
||||||
|
|
||||||
|
class SourceCode(object):
|
||||||
|
|
||||||
|
def __init__(self, path):
|
||||||
|
self.path = path
|
||||||
|
with open(path) as f:
|
||||||
|
self.source = f.read()
|
||||||
|
self.lines = self.source.splitlines()
|
||||||
|
self.maxline = len(self.lines)
|
||||||
|
|
||||||
|
def choose_script_args(self):
|
||||||
|
line = random.randint(1, self.maxline)
|
||||||
|
column = random.randint(0, len(self.lines[line - 1]))
|
||||||
|
return (self.source, line, column, self.path)
|
||||||
|
|
||||||
|
|
||||||
|
class SourceFinder(object):
|
||||||
|
|
||||||
|
def __init__(self, rootpath):
|
||||||
|
self.rootpath = rootpath
|
||||||
|
self.files = list(self.search_files())
|
||||||
|
|
||||||
|
def search_files(self):
|
||||||
|
for root, dirnames, filenames in os.walk(self.rootpath):
|
||||||
|
for name in filenames:
|
||||||
|
if name.endswith('.py'):
|
||||||
|
yield os.path.join(root, name)
|
||||||
|
|
||||||
|
def choose_source(self):
|
||||||
|
# FIXME: try same file for several times
|
||||||
|
return SourceCode(random.choice(self.files))
|
||||||
|
|
||||||
|
|
||||||
|
class BaseAttacker(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.record = {'data': []}
|
||||||
|
|
||||||
|
def attack(self, operation, *args):
|
||||||
|
script = jedi.Script(*args)
|
||||||
|
op = getattr(script, operation)
|
||||||
|
op()
|
||||||
|
|
||||||
|
def add_record(self, exc_info, operation, args):
|
||||||
|
(_type, value, tb) = exc_info
|
||||||
|
self.record['data'].append({
|
||||||
|
'traceback': traceback.format_tb(tb),
|
||||||
|
'error': repr(value),
|
||||||
|
'operation': operation,
|
||||||
|
'args': args,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_record(self, recid):
|
||||||
|
return self.record['data'][recid]
|
||||||
|
|
||||||
|
def save_record(self, path):
|
||||||
|
directory = os.path.dirname(os.path.abspath(path))
|
||||||
|
if not os.path.isdir(directory):
|
||||||
|
os.makedirs(directory)
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
json.dump(self.record, f)
|
||||||
|
|
||||||
|
def load_record(self, path):
|
||||||
|
with open(path) as f:
|
||||||
|
self.record = json.load(f)
|
||||||
|
return self.record
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.set_defaults(func=self.do_run)
|
||||||
|
|
||||||
|
def get_help(self):
|
||||||
|
for line in self.__doc__.splitlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line:
|
||||||
|
return line
|
||||||
|
|
||||||
|
|
||||||
|
class MixinPrinter(object):
|
||||||
|
|
||||||
|
def print_record(self, recid=-1):
|
||||||
|
data = self.get_record(recid)
|
||||||
|
print(*data['traceback'], end='')
|
||||||
|
print("""
|
||||||
|
{error} is raised by running Script(...).{operation}() with
|
||||||
|
line : {args[1]}
|
||||||
|
column: {args[2]}
|
||||||
|
path : {args[3]}
|
||||||
|
""".format(**data))
|
||||||
|
|
||||||
|
|
||||||
|
class MixinLoader(object):
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
super(MixinLoader, self).add_arguments(parser)
|
||||||
|
parser = parser.add_argument(
|
||||||
|
'recid', default=0, nargs='?', type=int, help="""
|
||||||
|
This option currently has no effect as random attack record
|
||||||
|
only one error.
|
||||||
|
""")
|
||||||
|
|
||||||
|
def do_run(self, record, recid):
|
||||||
|
self.load_record(record)
|
||||||
|
|
||||||
|
|
||||||
|
class AttackReporter(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.tries = 0
|
||||||
|
self.errors = 0
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __next__(self):
|
||||||
|
self.tries += 1
|
||||||
|
sys.stderr.write('.')
|
||||||
|
sys.stderr.flush()
|
||||||
|
return self.tries
|
||||||
|
|
||||||
|
next = __next__
|
||||||
|
|
||||||
|
def error(self):
|
||||||
|
self.errors += 1
|
||||||
|
sys.stderr.write('\n')
|
||||||
|
sys.stderr.flush()
|
||||||
|
print('{0}th error is encountered after {1} tries.'
|
||||||
|
.format(self.errors, self.tries))
|
||||||
|
|
||||||
|
|
||||||
|
class RandomAttacker(MixinPrinter, BaseAttacker):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Randomly run Script().<method>() against files under <rootpath>.
|
||||||
|
"""
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
'completions', 'goto_assignments', 'goto_definitions', 'usages',
|
||||||
|
'call_signatures']
|
||||||
|
|
||||||
|
def choose_operation(self):
|
||||||
|
return random.choice(self.operations)
|
||||||
|
|
||||||
|
def generate_attacks(self, maxtries, finder):
|
||||||
|
for _ in range(maxtries):
|
||||||
|
src = finder.choose_source()
|
||||||
|
operation = self.choose_operation()
|
||||||
|
yield (operation, src.choose_script_args())
|
||||||
|
|
||||||
|
def do_run(self, record, rootpath, maxtries):
|
||||||
|
finder = SourceFinder(rootpath)
|
||||||
|
reporter = AttackReporter()
|
||||||
|
for (operation, args) in self.generate_attacks(maxtries, finder):
|
||||||
|
reporter.next()
|
||||||
|
try:
|
||||||
|
self.attack(operation, *args)
|
||||||
|
except jedi.NotFoundError:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
self.add_record(sys.exc_info(), operation, args)
|
||||||
|
reporter.error()
|
||||||
|
self.print_record()
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
self.save_record(record)
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
super(RandomAttacker, self).add_arguments(parser)
|
||||||
|
parser.add_argument(
|
||||||
|
'--maxtries', '-l', default=10000, type=int)
|
||||||
|
parser.add_argument(
|
||||||
|
'rootpath', default='.', nargs='?',
|
||||||
|
help='root directory to look for Python files.')
|
||||||
|
|
||||||
|
|
||||||
|
class RedoAttacker(MixinLoader, BaseAttacker):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Redo recorded attack.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def do_run(self, record, recid):
|
||||||
|
super(RedoAttacker, self).do_run(record, recid)
|
||||||
|
data = self.get_record(recid)
|
||||||
|
try:
|
||||||
|
self.attack(data['operation'], *data['args'])
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
class ShowRecord(MixinLoader, MixinPrinter, BaseAttacker):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Show recorded errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def do_run(self, record, recid):
|
||||||
|
super(ShowRecord, self).do_run(record, recid)
|
||||||
|
self.print_record()
|
||||||
|
|
||||||
|
|
||||||
|
class AttackApp(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.parsers = []
|
||||||
|
self.attackers = []
|
||||||
|
|
||||||
|
def run(self, args=None):
|
||||||
|
parser = self.get_parser()
|
||||||
|
self.do_run(**vars(parser.parse_args(args)))
|
||||||
|
|
||||||
|
def do_run(self, func, debugger, fs_cache, **kwds):
|
||||||
|
if fs_cache is _unspecified:
|
||||||
|
jedi.settings.use_filesystem_cache = False
|
||||||
|
else:
|
||||||
|
jedi.settings.cache_directory = fs_cache
|
||||||
|
|
||||||
|
try:
|
||||||
|
func(**kwds)
|
||||||
|
except:
|
||||||
|
if debugger:
|
||||||
|
einfo = sys.exc_info()
|
||||||
|
pdb = __import__(debugger)
|
||||||
|
pdb.post_mortem(einfo if debugger == 'pudb' else einfo[2])
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def add_parser(self, attacker_class, *args, **kwds):
|
||||||
|
attacker = attacker_class()
|
||||||
|
parser = self.subparsers.add_parser(
|
||||||
|
*args,
|
||||||
|
help=attacker.get_help(),
|
||||||
|
description=attacker.__doc__,
|
||||||
|
**kwds)
|
||||||
|
attacker.add_arguments(parser)
|
||||||
|
|
||||||
|
# Not required, just fore debugging:
|
||||||
|
self.parsers.append(parser)
|
||||||
|
self.attackers.append(attacker)
|
||||||
|
|
||||||
|
def get_parser(self):
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
description=__doc__)
|
||||||
|
parser.add_argument(
|
||||||
|
'--record', '-R', default='record.json',
|
||||||
|
help='Exceptions are recorded in here (default: %(default)s).')
|
||||||
|
parser.add_argument(
|
||||||
|
'--pdb', dest='debugger', const='pdb', action='store_const',
|
||||||
|
help='Launch pdb when error is raised.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--ipdb', dest='debugger', const='ipdb', action='store_const',
|
||||||
|
help='Launch ipdb when error is raised.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--pudb', dest='debugger', const='pudb', action='store_const',
|
||||||
|
help='Launch pudb when error is raised.')
|
||||||
|
parser.add_argument(
|
||||||
|
'--fs-cache', '-C', default=_unspecified,
|
||||||
|
help="""
|
||||||
|
By default, file system cache is off for reproducibility.
|
||||||
|
Pass a temporary directory to use file system cache.
|
||||||
|
It is set to ``jedi.settings.cache_directory``.
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.subparsers = parser.add_subparsers()
|
||||||
|
self.add_parser(RandomAttacker, 'random')
|
||||||
|
self.add_parser(RedoAttacker, 'redo')
|
||||||
|
self.add_parser(ShowRecord, 'show')
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
try:
|
||||||
|
import argparse
|
||||||
|
except ImportError:
|
||||||
|
print('The argparse module (Python>=2.7) is needed to run sith.')
|
||||||
|
sys.exit(1)
|
||||||
|
app = AttackApp()
|
||||||
|
app.run()
|
||||||
@@ -94,6 +94,28 @@ a4
|
|||||||
b4
|
b4
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------
|
||||||
|
# multiple assignments
|
||||||
|
# -----------------
|
||||||
|
a = b = 1
|
||||||
|
#? int()
|
||||||
|
a
|
||||||
|
#? int()
|
||||||
|
b
|
||||||
|
|
||||||
|
(a, b) = (c, (e, f)) = ('2', (3, 4))
|
||||||
|
#? str()
|
||||||
|
a
|
||||||
|
#? tuple()
|
||||||
|
b
|
||||||
|
#? str()
|
||||||
|
c
|
||||||
|
#? int()
|
||||||
|
e
|
||||||
|
#? int()
|
||||||
|
f
|
||||||
|
|
||||||
|
|
||||||
# -----------------
|
# -----------------
|
||||||
# unnessecary braces
|
# unnessecary braces
|
||||||
# -----------------
|
# -----------------
|
||||||
|
|||||||
@@ -3,7 +3,13 @@ import textwrap
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from jedi import api
|
from jedi import api
|
||||||
|
import jedi
|
||||||
|
|
||||||
|
def test_is_keyword():
|
||||||
|
results = jedi.Script('import ', 1, 1, None).goto_definitions()
|
||||||
|
assert len(results) == 1 and results[0].is_keyword == True
|
||||||
|
results = jedi.Script('str', 1, 1, None).goto_definitions()
|
||||||
|
assert len(results) == 1 and results[0].is_keyword == False
|
||||||
|
|
||||||
def make_definitions():
|
def make_definitions():
|
||||||
"""
|
"""
|
||||||
|
|||||||
75
test/test_defined_names.py
Normal file
75
test/test_defined_names.py
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
"""
|
||||||
|
Tests for `api.defined_names`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
from jedi import api
|
||||||
|
from .base import TestBase
|
||||||
|
|
||||||
|
|
||||||
|
class TestDefinedNames(TestBase):
|
||||||
|
|
||||||
|
def assert_definition_names(self, definitions, names):
|
||||||
|
self.assertEqual([d.name for d in definitions], names)
|
||||||
|
|
||||||
|
def check_defined_names(self, source, names):
|
||||||
|
definitions = api.defined_names(textwrap.dedent(source))
|
||||||
|
self.assert_definition_names(definitions, names)
|
||||||
|
return definitions
|
||||||
|
|
||||||
|
def test_get_definitions_flat(self):
|
||||||
|
self.check_defined_names("""
|
||||||
|
import module
|
||||||
|
class Class:
|
||||||
|
pass
|
||||||
|
def func():
|
||||||
|
pass
|
||||||
|
data = None
|
||||||
|
""", ['module', 'Class', 'func', 'data'])
|
||||||
|
|
||||||
|
def test_dotted_assignment(self):
|
||||||
|
self.check_defined_names("""
|
||||||
|
x = Class()
|
||||||
|
x.y.z = None
|
||||||
|
""", ['x'])
|
||||||
|
|
||||||
|
def test_multiple_assignment(self):
|
||||||
|
self.check_defined_names("""
|
||||||
|
x = y = None
|
||||||
|
""", ['x', 'y'])
|
||||||
|
|
||||||
|
def test_multiple_imports(self):
|
||||||
|
self.check_defined_names("""
|
||||||
|
from module import a, b
|
||||||
|
from another_module import *
|
||||||
|
""", ['a', 'b'])
|
||||||
|
|
||||||
|
def test_nested_definitions(self):
|
||||||
|
definitions = self.check_defined_names("""
|
||||||
|
class Class:
|
||||||
|
def f():
|
||||||
|
pass
|
||||||
|
def g():
|
||||||
|
pass
|
||||||
|
""", ['Class'])
|
||||||
|
subdefinitions = definitions[0].defined_names()
|
||||||
|
self.assert_definition_names(subdefinitions, ['f', 'g'])
|
||||||
|
self.assertEqual([d.full_name for d in subdefinitions],
|
||||||
|
['Class.f', 'Class.g'])
|
||||||
|
|
||||||
|
def test_nested_class(self):
|
||||||
|
definitions = self.check_defined_names("""
|
||||||
|
class L1:
|
||||||
|
class L2:
|
||||||
|
class L3:
|
||||||
|
def f(): pass
|
||||||
|
def f(): pass
|
||||||
|
def f(): pass
|
||||||
|
def f(): pass
|
||||||
|
""", ['L1', 'f'])
|
||||||
|
subdefs = definitions[0].defined_names()
|
||||||
|
subsubdefs = subdefs[0].defined_names()
|
||||||
|
self.assert_definition_names(subdefs, ['L2', 'f'])
|
||||||
|
self.assert_definition_names(subsubdefs, ['L3', 'f'])
|
||||||
|
self.assert_definition_names(subsubdefs[0].defined_names(), ['f'])
|
||||||
93
test/test_full_name.py
Normal file
93
test/test_full_name.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
"""
|
||||||
|
Tests for :attr:`.BaseDefinition.full_name`.
|
||||||
|
|
||||||
|
There are three kinds of test:
|
||||||
|
|
||||||
|
#. Test classes derived from :class:`MixinTestFullName`.
|
||||||
|
Child class defines :meth:`.get_definitions` to alter how
|
||||||
|
the api definition instance is created.
|
||||||
|
|
||||||
|
#. :class:`TestFullDefinedName` is to test combination of
|
||||||
|
:attr:`.full_name` and :func:`.defined_names`.
|
||||||
|
|
||||||
|
#. Misc single-function tests.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
import jedi
|
||||||
|
from jedi import api_classes
|
||||||
|
from .base import TestBase
|
||||||
|
|
||||||
|
|
||||||
|
class MixinTestFullName(object):
|
||||||
|
|
||||||
|
def get_definitions(self, source):
|
||||||
|
"""
|
||||||
|
Get definition objects of the variable at the end of `source`.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def check(self, source, desired):
|
||||||
|
definitions = self.get_definitions(textwrap.dedent(source))
|
||||||
|
self.assertEqual(definitions[0].full_name, desired)
|
||||||
|
|
||||||
|
def test_os_path_join(self):
|
||||||
|
self.check('import os; os.path.join', 'os.path.join')
|
||||||
|
|
||||||
|
def test_builtin(self):
|
||||||
|
self.check('type', 'type')
|
||||||
|
|
||||||
|
def test_from_import(self):
|
||||||
|
self.check('from os import path', 'os.path')
|
||||||
|
|
||||||
|
|
||||||
|
class TestFullNameWithGotoDefinitions(MixinTestFullName, TestBase):
|
||||||
|
|
||||||
|
get_definitions = TestBase.goto_definitions
|
||||||
|
|
||||||
|
def test_tuple_mapping(self):
|
||||||
|
self.check("""
|
||||||
|
import re
|
||||||
|
any_re = re.compile('.*')
|
||||||
|
any_re""", 're.RegexObject')
|
||||||
|
|
||||||
|
|
||||||
|
class TestFullNameWithCompletions(MixinTestFullName, TestBase):
|
||||||
|
get_definitions = TestBase.completions
|
||||||
|
|
||||||
|
|
||||||
|
class TestFullDefinedName(TestBase):
|
||||||
|
|
||||||
|
"""
|
||||||
|
Test combination of :attr:`.full_name` and :func:`.defined_names`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def check(self, source, desired):
|
||||||
|
definitions = jedi.defined_names(textwrap.dedent(source))
|
||||||
|
full_names = [d.full_name for d in definitions]
|
||||||
|
self.assertEqual(full_names, desired)
|
||||||
|
|
||||||
|
def test_local_names(self):
|
||||||
|
self.check("""
|
||||||
|
def f(): pass
|
||||||
|
class C: pass
|
||||||
|
""", ['f', 'C'])
|
||||||
|
|
||||||
|
def test_imports(self):
|
||||||
|
self.check("""
|
||||||
|
import os
|
||||||
|
from os import path
|
||||||
|
from os.path import join
|
||||||
|
from os import path as opath
|
||||||
|
""", ['os', 'os.path', 'os.path.join', 'os.path'])
|
||||||
|
|
||||||
|
|
||||||
|
def test_keyword_full_name_should_be_none():
|
||||||
|
"""issue #94"""
|
||||||
|
# Using `from jedi.keywords import Keyword` here does NOT work
|
||||||
|
# in Python 3. This is due to the import hack jedi using.
|
||||||
|
Keyword = api_classes.keywords.Keyword
|
||||||
|
d = api_classes.Definition(Keyword('(', (0, 0)))
|
||||||
|
assert d.full_name is None
|
||||||
@@ -14,9 +14,8 @@ import textwrap
|
|||||||
from .base import TestBase, unittest, cwd_at
|
from .base import TestBase, unittest, cwd_at
|
||||||
|
|
||||||
import jedi
|
import jedi
|
||||||
from jedi._compatibility import utf8, unicode
|
from jedi._compatibility import utf8, unicode, is_py33
|
||||||
from jedi import api, parsing, common
|
from jedi import api, parsing, common
|
||||||
api_classes = api.api_classes
|
|
||||||
|
|
||||||
#jedi.set_debug_function(jedi.debug.print_to_stdout)
|
#jedi.set_debug_function(jedi.debug.print_to_stdout)
|
||||||
|
|
||||||
@@ -314,14 +313,29 @@ class TestRegression(TestBase):
|
|||||||
types = [o.type for o in objs]
|
types = [o.type for o in objs]
|
||||||
assert 'import' not in types and 'class' in types
|
assert 'import' not in types and 'class' in types
|
||||||
|
|
||||||
def test_keyword_definition_doc(self):
|
def test_keyword(self):
|
||||||
""" github jedi-vim issue #44 """
|
""" github jedi-vim issue #44 """
|
||||||
defs = self.goto_definitions("print")
|
defs = self.goto_definitions("print")
|
||||||
assert [d.doc for d in defs]
|
assert [d.doc for d in defs]
|
||||||
|
|
||||||
defs = self.goto_definitions("import")
|
defs = self.goto_definitions("import")
|
||||||
assert len(defs) == 1
|
assert len(defs) == 1 and [1 for d in defs if d.doc]
|
||||||
assert [d.doc for d in defs]
|
# unrelated to #44
|
||||||
|
defs = self.goto_assignments("import")
|
||||||
|
assert len(defs) == 0
|
||||||
|
completions = self.completions("import", (1,1))
|
||||||
|
assert len(completions) == 0
|
||||||
|
with common.ignored(jedi.NotFoundError): # TODO shouldn't throw that.
|
||||||
|
defs = self.goto_definitions("assert")
|
||||||
|
assert len(defs) == 1
|
||||||
|
|
||||||
|
def test_goto_assignments_keyword(self):
|
||||||
|
"""
|
||||||
|
Bug: goto assignments on ``in`` used to raise AttributeError::
|
||||||
|
|
||||||
|
'unicode' object has no attribute 'generate_call_path'
|
||||||
|
"""
|
||||||
|
self.goto_assignments('in')
|
||||||
|
|
||||||
def test_goto_following_on_imports(self):
|
def test_goto_following_on_imports(self):
|
||||||
s = "import multiprocessing.dummy; multiprocessing.dummy"
|
s = "import multiprocessing.dummy; multiprocessing.dummy"
|
||||||
@@ -377,10 +391,47 @@ class TestRegression(TestBase):
|
|||||||
# jedi issue #150
|
# jedi issue #150
|
||||||
s = "x()\nx( )\nx( )\nx ( )"
|
s = "x()\nx( )\nx( )\nx ( )"
|
||||||
parser = parsing.Parser(s)
|
parser = parsing.Parser(s)
|
||||||
for i, s in enumerate(parser.scope.statements, 3):
|
for i, s in enumerate(parser.module.statements, 3):
|
||||||
for c in s.get_commands():
|
for c in s.get_commands():
|
||||||
self.assertEqual(c.execution.end_pos[1], i)
|
self.assertEqual(c.execution.end_pos[1], i)
|
||||||
|
|
||||||
|
def check_definition_by_marker(self, source, after_cursor, names):
|
||||||
|
r"""
|
||||||
|
Find definitions specified by `after_cursor` and check what found
|
||||||
|
|
||||||
|
For example, for the following configuration, you can pass
|
||||||
|
``after_cursor = 'y)'``.::
|
||||||
|
|
||||||
|
function(
|
||||||
|
x, y)
|
||||||
|
\
|
||||||
|
`- You want cursor to be here
|
||||||
|
"""
|
||||||
|
source = textwrap.dedent(source)
|
||||||
|
for (i, line) in enumerate(source.splitlines()):
|
||||||
|
if after_cursor in line:
|
||||||
|
break
|
||||||
|
column = len(line) - len(after_cursor)
|
||||||
|
defs = self.goto_definitions(source, (i + 1, column))
|
||||||
|
self.assertEqual([d.name for d in defs], names)
|
||||||
|
|
||||||
|
def test_backslash_continuation(self):
|
||||||
|
"""
|
||||||
|
Test that ModuleWithCursor.get_path_until_cursor handles continuation
|
||||||
|
"""
|
||||||
|
self.check_definition_by_marker(r"""
|
||||||
|
x = 0
|
||||||
|
a = \
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 9, x] # <-- here
|
||||||
|
""", '] # <-- here', ['int'])
|
||||||
|
|
||||||
|
def test_backslash_continuation_and_bracket(self):
|
||||||
|
self.check_definition_by_marker(r"""
|
||||||
|
x = 0
|
||||||
|
a = \
|
||||||
|
[1, 2, 3, 4, 5, 6, 7, 8, 9, (x)] # <-- here
|
||||||
|
""", '(x)] # <-- here', [None])
|
||||||
|
|
||||||
|
|
||||||
class TestDocstring(TestBase):
|
class TestDocstring(TestBase):
|
||||||
|
|
||||||
@@ -410,28 +461,6 @@ class TestDocstring(TestBase):
|
|||||||
|
|
||||||
|
|
||||||
class TestFeature(TestBase):
|
class TestFeature(TestBase):
|
||||||
def test_full_name(self):
|
|
||||||
""" feature request #61"""
|
|
||||||
assert self.completions('import os; os.path.join')[0].full_name \
|
|
||||||
== 'os.path.join'
|
|
||||||
|
|
||||||
def test_keyword_full_name_should_be_none(self):
|
|
||||||
"""issue #94"""
|
|
||||||
# Using `from jedi.keywords import Keyword` here does NOT work
|
|
||||||
# in Python 3. This is due to the import hack jedi using.
|
|
||||||
Keyword = api_classes.keywords.Keyword
|
|
||||||
d = api_classes.Definition(Keyword('(', (0, 0)))
|
|
||||||
assert d.full_name is None
|
|
||||||
|
|
||||||
def test_full_name_builtin(self):
|
|
||||||
self.assertEqual(self.completions('type')[0].full_name, 'type')
|
|
||||||
|
|
||||||
def test_full_name_tuple_mapping(self):
|
|
||||||
s = """
|
|
||||||
import re
|
|
||||||
any_re = re.compile('.*')
|
|
||||||
any_re"""
|
|
||||||
self.assertEqual(self.goto_definitions(s)[0].full_name, 're.RegexObject')
|
|
||||||
|
|
||||||
def test_preload_modules(self):
|
def test_preload_modules(self):
|
||||||
def check_loaded(*modules):
|
def check_loaded(*modules):
|
||||||
@@ -453,81 +482,6 @@ class TestFeature(TestBase):
|
|||||||
|
|
||||||
cache.parser_cache = temp_cache
|
cache.parser_cache = temp_cache
|
||||||
|
|
||||||
def test_quick_completion(self):
|
|
||||||
sources = [
|
|
||||||
('import json; json.l', (1, 19)),
|
|
||||||
('import json; json.l ', (1, 19)),
|
|
||||||
('import json\njson.l', (2, 6)),
|
|
||||||
('import json\njson.l ', (2, 6)),
|
|
||||||
('import json\njson.l\n\n', (2, 6)),
|
|
||||||
('import json\njson.l \n\n', (2, 6)),
|
|
||||||
('import json\njson.l \n \n\n', (2, 6)),
|
|
||||||
]
|
|
||||||
for source, pos in sources:
|
|
||||||
# Run quick_complete
|
|
||||||
quick_completions = api._quick_complete(source)
|
|
||||||
# Run real completion
|
|
||||||
script = jedi.Script(source, pos[0], pos[1], '')
|
|
||||||
real_completions = script.completions()
|
|
||||||
# Compare results
|
|
||||||
quick_values = [(c.full_name, c.line, c.column) for c in quick_completions]
|
|
||||||
real_values = [(c.full_name, c.line, c.column) for c in real_completions]
|
|
||||||
self.assertEqual(quick_values, real_values)
|
|
||||||
|
|
||||||
|
|
||||||
class TestGetDefinitions(TestBase):
|
|
||||||
|
|
||||||
def test_get_definitions_flat(self):
|
|
||||||
definitions = api.defined_names("""
|
|
||||||
import module
|
|
||||||
class Class:
|
|
||||||
pass
|
|
||||||
def func():
|
|
||||||
pass
|
|
||||||
data = None
|
|
||||||
""")
|
|
||||||
self.assertEqual([d.name for d in definitions],
|
|
||||||
['module', 'Class', 'func', 'data'])
|
|
||||||
|
|
||||||
def test_dotted_assignment(self):
|
|
||||||
definitions = api.defined_names("""
|
|
||||||
x = Class()
|
|
||||||
x.y.z = None
|
|
||||||
""")
|
|
||||||
self.assertEqual([d.name for d in definitions],
|
|
||||||
['x'])
|
|
||||||
|
|
||||||
def test_multiple_assignment(self):
|
|
||||||
definitions = api.defined_names("""
|
|
||||||
x = y = None
|
|
||||||
""")
|
|
||||||
self.assertEqual([d.name for d in definitions],
|
|
||||||
['x', 'y'])
|
|
||||||
|
|
||||||
def test_multiple_imports(self):
|
|
||||||
definitions = api.defined_names("""
|
|
||||||
from module import a, b
|
|
||||||
from another_module import *
|
|
||||||
""")
|
|
||||||
self.assertEqual([d.name for d in definitions],
|
|
||||||
['a', 'b'])
|
|
||||||
|
|
||||||
def test_nested_definitions(self):
|
|
||||||
definitions = api.defined_names("""
|
|
||||||
class Class:
|
|
||||||
def f():
|
|
||||||
pass
|
|
||||||
def g():
|
|
||||||
pass
|
|
||||||
""")
|
|
||||||
self.assertEqual([d.name for d in definitions],
|
|
||||||
['Class'])
|
|
||||||
subdefinitions = definitions[0].defined_names()
|
|
||||||
self.assertEqual([d.name for d in subdefinitions],
|
|
||||||
['f', 'g'])
|
|
||||||
self.assertEqual([d.full_name for d in subdefinitions],
|
|
||||||
['Class.f', 'Class.g'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestSpeed(TestBase):
|
class TestSpeed(TestBase):
|
||||||
def _check_speed(time_per_run, number=4, run_warm=True):
|
def _check_speed(time_per_run, number=4, run_warm=True):
|
||||||
@@ -562,6 +516,44 @@ class TestSpeed(TestBase):
|
|||||||
#print(jedi.imports.imports_processed)
|
#print(jedi.imports.imports_processed)
|
||||||
|
|
||||||
|
|
||||||
|
class TestInterpreterAPI(unittest.TestCase):
|
||||||
|
|
||||||
|
def check_interpreter_complete(self, source, namespace, completions,
|
||||||
|
**kwds):
|
||||||
|
script = api.Interpreter(source, [namespace], **kwds)
|
||||||
|
cs = script.complete()
|
||||||
|
actual = [c.word for c in cs]
|
||||||
|
self.assertEqual(sorted(actual), sorted(completions))
|
||||||
|
|
||||||
|
def test_complete_raw_function(self):
|
||||||
|
from os.path import join
|
||||||
|
self.check_interpreter_complete('join().up',
|
||||||
|
locals(),
|
||||||
|
['upper'])
|
||||||
|
|
||||||
|
def test_complete_raw_function_different_name(self):
|
||||||
|
from os.path import join as pjoin
|
||||||
|
self.check_interpreter_complete('pjoin().up',
|
||||||
|
locals(),
|
||||||
|
['upper'])
|
||||||
|
|
||||||
|
def test_complete_raw_module(self):
|
||||||
|
import os
|
||||||
|
self.check_interpreter_complete('os.path.join().up',
|
||||||
|
locals(),
|
||||||
|
['upper'])
|
||||||
|
|
||||||
|
def test_complete_raw_instance(self):
|
||||||
|
import datetime
|
||||||
|
dt = datetime.datetime(2013, 1, 1)
|
||||||
|
completions = ['time', 'timetz', 'timetuple']
|
||||||
|
if is_py33:
|
||||||
|
completions += ['timestamp']
|
||||||
|
self.check_interpreter_complete('(dt - dt).ti',
|
||||||
|
locals(),
|
||||||
|
completions)
|
||||||
|
|
||||||
|
|
||||||
def test_settings_module():
|
def test_settings_module():
|
||||||
"""
|
"""
|
||||||
jedi.settings and jedi.cache.settings must be the same module.
|
jedi.settings and jedi.cache.settings must be the same module.
|
||||||
|
|||||||
Reference in New Issue
Block a user