Fixed vim and zsh
This commit is contained in:
@ -0,0 +1,19 @@
|
||||
[run]
|
||||
omit =
|
||||
jedi/_compatibility.py
|
||||
jedi/evaluate/site.py
|
||||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
# Don't complain about missing debug-only code:
|
||||
def __repr__
|
||||
if self\.debug
|
||||
|
||||
# Don't complain if tests don't hit defensive assertion code:
|
||||
raise AssertionError
|
||||
raise NotImplementedError
|
||||
|
||||
# Don't complain if non-runnable code isn't run:
|
||||
if 0:
|
||||
if __name__ == .__main__.:
|
13
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/.gitignore
vendored
Normal file
13
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
*~
|
||||
*.sw?
|
||||
*.pyc
|
||||
.ropeproject
|
||||
.tox
|
||||
.coveralls.yml
|
||||
.coverage
|
||||
/build/
|
||||
/docs/_build/
|
||||
/dist/
|
||||
jedi.egg-info/
|
||||
record.json
|
||||
/.cache/
|
@ -0,0 +1,30 @@
|
||||
language: python
|
||||
sudo: false
|
||||
python:
|
||||
- 2.6
|
||||
- 2.7
|
||||
- 3.3
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- pypy
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: pypy
|
||||
- env: TOXENV=cov
|
||||
- env: TOXENV=sith
|
||||
include:
|
||||
- python: 3.5
|
||||
env: TOXENV=cov
|
||||
- python: 3.5
|
||||
env: TOXENV=sith
|
||||
install:
|
||||
- pip install --quiet tox-travis
|
||||
script:
|
||||
- tox
|
||||
after_script:
|
||||
- if [ $TOXENV == "cov" ]; then
|
||||
pip install --quiet coveralls;
|
||||
coveralls;
|
||||
fi
|
||||
|
@ -0,0 +1,49 @@
|
||||
Main Authors
|
||||
============
|
||||
|
||||
David Halter (@davidhalter) <davidhalter88@gmail.com>
|
||||
Takafumi Arakaki (@tkf) <aka.tkf@gmail.com>
|
||||
|
||||
Code Contributors
|
||||
=================
|
||||
|
||||
Danilo Bargen (@dbrgn) <mail@dbrgn.ch>
|
||||
Laurens Van Houtven (@lvh) <_@lvh.cc>
|
||||
Aldo Stracquadanio (@Astrac) <aldo.strac@gmail.com>
|
||||
Jean-Louis Fuchs (@ganwell) <ganwell@fangorn.ch>
|
||||
tek (@tek)
|
||||
Yasha Borevich (@jjay) <j.borevich@gmail.com>
|
||||
Aaron Griffin <aaronmgriffin@gmail.com>
|
||||
andviro (@andviro)
|
||||
Mike Gilbert (@floppym) <floppym@gentoo.org>
|
||||
Aaron Meurer (@asmeurer) <asmeurer@gmail.com>
|
||||
Lubos Trilety <ltrilety@redhat.com>
|
||||
Akinori Hattori (@hattya) <hattya@gmail.com>
|
||||
srusskih (@srusskih)
|
||||
Steven Silvester (@blink1073)
|
||||
Colin Duquesnoy (@ColinDuquesnoy) <colin.duquesnoy@gmail.com>
|
||||
Jorgen Schaefer (@jorgenschaefer) <contact@jorgenschaefer.de>
|
||||
Fredrik Bergroth (@fbergroth)
|
||||
Mathias Fußenegger (@mfussenegger)
|
||||
Syohei Yoshida (@syohex) <syohex@gmail.com>
|
||||
ppalucky (@ppalucky)
|
||||
immerrr (@immerrr) immerrr@gmail.com
|
||||
Albertas Agejevas (@alga)
|
||||
Savor d'Isavano (@KenetJervet) <newelevenken@163.com>
|
||||
Phillip Berndt (@phillipberndt) <phillip.berndt@gmail.com>
|
||||
Ian Lee (@IanLee1521) <IanLee1521@gmail.com>
|
||||
Farkhad Khatamov (@hatamov) <comsgn@gmail.com>
|
||||
Kevin Kelley (@kelleyk) <kelleyk@kelleyk.net>
|
||||
Sid Shanker (@squidarth) <sid.p.shanker@gmail.com>
|
||||
Reinoud Elhorst (@reinhrst)
|
||||
Guido van Rossum (@gvanrossum) <guido@python.org>
|
||||
Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro>
|
||||
Cristi Burcă (@scribu)
|
||||
bstaint (@bstaint)
|
||||
Mathias Rav (@Mortal) <rav@cs.au.dk>
|
||||
Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
|
||||
Simon Ruggier (@sruggier)
|
||||
Élie Gouzien (@ElieGouzien)
|
||||
|
||||
|
||||
Note: (@user) means a github user name.
|
@ -0,0 +1,87 @@
|
||||
.. :changelog:
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
0.11.0 (2017-09-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Split Jedi's parser into a separate project called ``parso``.
|
||||
- Avoiding side effects in REPL completion.
|
||||
- Numpy docstring support should be much better.
|
||||
- Moved the `settings.*recursion*` away, they are no longer usable.
|
||||
|
||||
0.10.2 (2017-04-05)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Python Packaging sucks. Some files were not included in 0.10.1.
|
||||
|
||||
0.10.1 (2017-04-05)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Fixed a few very annoying bugs.
|
||||
- Prepared the parser to be factored out of Jedi.
|
||||
|
||||
0.10.0 (2017-02-03)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Actual semantic completions for the complete Python syntax.
|
||||
- Basic type inference for ``yield from`` PEP 380.
|
||||
- PEP 484 support (most of the important features of it). Thanks Claude! (@reinhrst)
|
||||
- Added ``get_line_code`` to ``Definition`` and ``Completion`` objects.
|
||||
- Completely rewritten the type inference engine.
|
||||
- A new and better parser for (fast) parsing diffs of Python code.
|
||||
|
||||
0.9.0 (2015-04-10)
|
||||
++++++++++++++++++
|
||||
|
||||
- The import logic has been rewritten to look more like Python's. There is now
|
||||
an ``Evaluator.modules`` import cache, which resembles ``sys.modules``.
|
||||
- Integrated the parser of 2to3. This will make refactoring possible. It will
|
||||
also be possible to check for error messages (like compiling an AST would give)
|
||||
in the future.
|
||||
- With the new parser, the evaluation also completely changed. It's now simpler
|
||||
and more readable.
|
||||
- Completely rewritten REPL completion.
|
||||
- Added ``jedi.names``, a command to do static analysis. Thanks to that
|
||||
sourcegraph guys for sponsoring this!
|
||||
- Alpha version of the linter.
|
||||
|
||||
|
||||
0.8.1 (2014-07-23)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Bugfix release, the last release forgot to include files that improve
|
||||
autocompletion for builtin libraries. Fixed.
|
||||
|
||||
0.8.0 (2014-05-05)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Memory Consumption for compiled modules (e.g. builtins, sys) has been reduced
|
||||
drastically. Loading times are down as well (it takes basically as long as an
|
||||
import).
|
||||
- REPL completion is starting to become usable.
|
||||
- Various small API changes. Generally this release focuses on stability and
|
||||
refactoring of internal APIs.
|
||||
- Introducing operator precedence, which makes calculating correct Array
|
||||
indices and ``__getattr__`` strings possible.
|
||||
|
||||
0.7.0 (2013-08-09)
|
||||
++++++++++++++++++
|
||||
|
||||
- Switched from LGPL to MIT license.
|
||||
- Added an Interpreter class to the API to make autocompletion in REPL
|
||||
possible.
|
||||
- Added autocompletion support for namespace packages.
|
||||
- Add sith.py, a new random testing method.
|
||||
|
||||
0.6.0 (2013-05-14)
|
||||
++++++++++++++++++
|
||||
|
||||
- Much faster parser with builtin part caching.
|
||||
- A test suite, thanks @tkf.
|
||||
|
||||
0.5 versions (2012)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Initial development.
|
@ -0,0 +1,8 @@
|
||||
Pull Requests are great.
|
||||
|
||||
1. Fork the Repo on github.
|
||||
2. If you are adding functionality or fixing a bug, please add a test!
|
||||
3. Add your name to AUTHORS.txt
|
||||
4. Push to your fork and submit a pull request.
|
||||
|
||||
**Try to use the PEP8 style guide.**
|
@ -0,0 +1,24 @@
|
||||
All contributions towards Jedi are MIT licensed.
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) <2013> <David Halter and others, see AUTHORS.txt>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@ -0,0 +1,15 @@
|
||||
include README.rst
|
||||
include CHANGELOG.rst
|
||||
include LICENSE.txt
|
||||
include AUTHORS.txt
|
||||
include .coveragerc
|
||||
include sith.py
|
||||
include conftest.py
|
||||
include pytest.ini
|
||||
include tox.ini
|
||||
include requirements.txt
|
||||
include jedi/evaluate/compiled/fake/*.pym
|
||||
include jedi/parser/python/grammar*.txt
|
||||
recursive-include test *
|
||||
recursive-include docs *
|
||||
recursive-exclude * *.pyc
|
@ -0,0 +1,215 @@
|
||||
###################################################################
|
||||
Jedi - an awesome autocompletion/static analysis library for Python
|
||||
###################################################################
|
||||
|
||||
.. image:: https://secure.travis-ci.org/davidhalter/jedi.png?branch=master
|
||||
:target: http://travis-ci.org/davidhalter/jedi
|
||||
:alt: Travis-CI build status
|
||||
|
||||
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.png?branch=master
|
||||
:target: https://coveralls.io/r/davidhalter/jedi
|
||||
:alt: Coverage Status
|
||||
|
||||
|
||||
*If you have specific questions, please add an issue or ask on* `stackoverflow
|
||||
<https://stackoverflow.com/questions/tagged/python-jedi>`_ *with the label* ``python-jedi``.
|
||||
|
||||
|
||||
Jedi is a static analysis tool for Python that can be used in IDEs/editors. Its
|
||||
historic focus is autocompletion, but does static analysis for now as well.
|
||||
Jedi is fast and is very well tested. It understands Python on a deeper level
|
||||
than all other static analysis frameworks for Python.
|
||||
|
||||
Jedi has support for two different goto functions. It's possible to search for
|
||||
related names and to list all names in a Python file and infer them. Jedi
|
||||
understands docstrings and you can use Jedi autocompletion in your REPL as
|
||||
well.
|
||||
|
||||
Jedi uses a very simple API to connect with IDE's. There's a reference
|
||||
implementation as a `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_,
|
||||
which uses Jedi's autocompletion. We encourage you to use Jedi in your IDEs.
|
||||
It's really easy.
|
||||
|
||||
Jedi can currently be used with the following editors/projects:
|
||||
|
||||
- Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_)
|
||||
- Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_)
|
||||
- Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3])
|
||||
- TextMate_ (Not sure if it's actually working)
|
||||
- Kate_ version 4.13+ supports it natively, you have to enable it, though. [`proof
|
||||
<https://projects.kde.org/projects/kde/applications/kate/repository/show?rev=KDE%2F4.13>`_]
|
||||
- Atom_ (autocomplete-python-jedi_)
|
||||
- SourceLair_
|
||||
- `GNOME Builder`_ (with support for GObject Introspection)
|
||||
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=donjayamanne.python>`_)
|
||||
- Gedit (gedi_)
|
||||
- wdb_ - Web Debugger
|
||||
- `Eric IDE`_ (Available as a plugin)
|
||||
- `Ipython 6.0.0+ <http://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
|
||||
|
||||
and many more!
|
||||
|
||||
|
||||
Here are some pictures taken from jedi-vim_:
|
||||
|
||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png
|
||||
|
||||
Completion for almost anything (Ctrl+Space).
|
||||
|
||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_function.png
|
||||
|
||||
Display of function/class bodies, docstrings.
|
||||
|
||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_pydoc.png
|
||||
|
||||
Pydoc support (Shift+k).
|
||||
|
||||
There is also support for goto and renaming.
|
||||
|
||||
Get the latest version from `github <https://github.com/davidhalter/jedi>`_
|
||||
(master branch should always be kind of stable/working).
|
||||
|
||||
Docs are available at `https://jedi.readthedocs.org/en/latest/
|
||||
<https://jedi.readthedocs.org/en/latest/>`_. Pull requests with documentation
|
||||
enhancements and/or fixes are awesome and most welcome. Jedi uses `semantic
|
||||
versioning <http://semver.org/>`_.
|
||||
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
pip install jedi
|
||||
|
||||
Note: This just installs the Jedi library, not the editor plugins. For
|
||||
information about how to make it work with your editor, refer to the
|
||||
corresponding documentation.
|
||||
|
||||
You don't want to use ``pip``? Please refer to the `manual
|
||||
<https://jedi.readthedocs.org/en/latest/docs/installation.html>`_.
|
||||
|
||||
|
||||
Feature Support and Caveats
|
||||
===========================
|
||||
|
||||
Jedi really understands your Python code. For a comprehensive list what Jedi
|
||||
understands, see: `Features
|
||||
<https://jedi.readthedocs.org/en/latest/docs/features.html>`_. A list of
|
||||
caveats can be found on the same page.
|
||||
|
||||
You can run Jedi on cPython 2.6, 2.7, 3.3, 3.4 or 3.5 but it should also
|
||||
understand/parse code older than those versions.
|
||||
|
||||
Tips on how to use Jedi efficiently can be found `here
|
||||
<https://jedi.readthedocs.org/en/latest/docs/features.html#recipes>`_.
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
You can find the documentation for the `API here <https://jedi.readthedocs.org/en/latest/docs/plugin-api.html>`_.
|
||||
|
||||
|
||||
Autocompletion / Goto / Pydoc
|
||||
-----------------------------
|
||||
|
||||
Please check the API for a good explanation. There are the following commands:
|
||||
|
||||
- ``jedi.Script.goto_assignments``
|
||||
- ``jedi.Script.completions``
|
||||
- ``jedi.Script.usages``
|
||||
|
||||
The returned objects are very powerful and really all you might need.
|
||||
|
||||
|
||||
Autocompletion in your REPL (IPython, etc.)
|
||||
-------------------------------------------
|
||||
|
||||
Starting with Ipython `6.0.0` Jedi is a dependency of IPython. Autocompletion
|
||||
in IPython is therefore possible without additional configuration.
|
||||
|
||||
It's possible to have Jedi autocompletion in REPL modes - `example video <https://vimeo.com/122332037>`_.
|
||||
This means that in Python you can enable tab completion in a `REPL
|
||||
<https://jedi.readthedocs.org/en/latest/docs/usage.html#tab-completion-in-the-python-shell>`_.
|
||||
|
||||
|
||||
Static Analysis / Linter
|
||||
------------------------
|
||||
|
||||
To do all forms of static analysis, please try to use ``jedi.names``. It will
|
||||
return a list of names that you can use to infer types and so on.
|
||||
|
||||
Linting is another thing that is going to be part of Jedi. For now you can try
|
||||
an alpha version ``python -m jedi linter``. The API might change though and
|
||||
it's still buggy. It's Jedi's goal to be smarter than classic linter and
|
||||
understand ``AttributeError`` and other code issues.
|
||||
|
||||
|
||||
Refactoring
|
||||
-----------
|
||||
|
||||
Jedi's parser would support refactoring, but there's no API to use it right
|
||||
now. If you're interested in helping out here, let me know. With the latest
|
||||
parser changes, it should be very easy to actually make it work.
|
||||
|
||||
|
||||
Development
|
||||
===========
|
||||
|
||||
There's a pretty good and extensive `development documentation
|
||||
<https://jedi.readthedocs.org/en/latest/docs/development.html>`_.
|
||||
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
The test suite depends on ``tox`` and ``pytest``::
|
||||
|
||||
pip install tox pytest
|
||||
|
||||
To run the tests for all supported Python versions::
|
||||
|
||||
tox
|
||||
|
||||
If you want to test only a specific Python version (e.g. Python 2.7), it's as
|
||||
easy as ::
|
||||
|
||||
tox -e py27
|
||||
|
||||
Tests are also run automatically on `Travis CI
|
||||
<https://travis-ci.org/davidhalter/jedi/>`_.
|
||||
|
||||
For more detailed information visit the `testing documentation
|
||||
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_
|
||||
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
- Takafumi Arakaki (@tkf) for creating a solid test environment and a lot of
|
||||
other things.
|
||||
- Danilo Bargen (@dbrgn) for general housekeeping and being a good friend :).
|
||||
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
|
||||
(originally used in lib2to3).
|
||||
|
||||
|
||||
|
||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||
.. _youcompleteme: http://valloric.github.io/YouCompleteMe/
|
||||
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
|
||||
.. _completor.vim: https://github.com/maralla/completor.vim
|
||||
.. _Jedi.el: https://github.com/tkf/emacs-jedi
|
||||
.. _company-mode: https://github.com/syohex/emacs-company-jedi
|
||||
.. _elpy: https://github.com/jorgenschaefer/elpy
|
||||
.. _anaconda-mode: https://github.com/proofit404/anaconda-mode
|
||||
.. _ycmd: https://github.com/abingham/emacs-ycmd
|
||||
.. _sublimejedi: https://github.com/srusskih/SublimeJEDI
|
||||
.. _anaconda: https://github.com/DamnWidget/anaconda
|
||||
.. _wdb: https://github.com/Kozea/wdb
|
||||
.. _TextMate: https://github.com/lawrenceakka/python-jedi.tmbundle
|
||||
.. _Kate: http://kate-editor.org
|
||||
.. _Atom: https://atom.io/
|
||||
.. _autocomplete-python-jedi: https://atom.io/packages/autocomplete-python-jedi
|
||||
.. _SourceLair: https://www.sourcelair.com
|
||||
.. _GNOME Builder: https://wiki.gnome.org/Apps/Builder
|
||||
.. _Visual Studio Code: https://code.visualstudio.com/
|
||||
.. _gedi: https://github.com/isamert/gedi
|
||||
.. _Eric IDE: http://eric-ide.python-projects.org
|
@ -0,0 +1,72 @@
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
import pytest
|
||||
|
||||
import jedi
|
||||
|
||||
collect_ignore = ["setup.py"]
|
||||
|
||||
|
||||
# The following hooks (pytest_configure, pytest_unconfigure) are used
|
||||
# to modify `jedi.settings.cache_directory` because `clean_jedi_cache`
|
||||
# has no effect during doctests. Without these hooks, doctests uses
|
||||
# user's cache (e.g., ~/.cache/jedi/). We should remove this
|
||||
# workaround once the problem is fixed in py.test.
|
||||
#
|
||||
# See:
|
||||
# - https://github.com/davidhalter/jedi/pull/168
|
||||
# - https://bitbucket.org/hpk42/pytest/issue/275/
|
||||
|
||||
jedi_cache_directory_orig = None
|
||||
jedi_cache_directory_temp = None
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--jedi-debug", "-D", action='store_true',
|
||||
help="Enables Jedi's debug output.")
|
||||
|
||||
parser.addoption("--warning-is-error", action='store_true',
|
||||
help="Warnings are treated as errors.")
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
global jedi_cache_directory_orig, jedi_cache_directory_temp
|
||||
jedi_cache_directory_orig = jedi.settings.cache_directory
|
||||
jedi_cache_directory_temp = tempfile.mkdtemp(prefix='jedi-test-')
|
||||
jedi.settings.cache_directory = jedi_cache_directory_temp
|
||||
|
||||
if config.option.jedi_debug:
|
||||
jedi.set_debug_function()
|
||||
|
||||
if config.option.warning_is_error:
|
||||
import warnings
|
||||
warnings.simplefilter("error")
|
||||
|
||||
|
||||
def pytest_unconfigure(config):
|
||||
global jedi_cache_directory_orig, jedi_cache_directory_temp
|
||||
jedi.settings.cache_directory = jedi_cache_directory_orig
|
||||
shutil.rmtree(jedi_cache_directory_temp)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def clean_jedi_cache(request):
|
||||
"""
|
||||
Set `jedi.settings.cache_directory` to a temporary directory during test.
|
||||
|
||||
Note that you can't use built-in `tmpdir` and `monkeypatch`
|
||||
fixture here because their scope is 'function', which is not used
|
||||
in 'session' scope fixture.
|
||||
|
||||
This fixture is activated in ../pytest.ini.
|
||||
"""
|
||||
from jedi import settings
|
||||
old = settings.cache_directory
|
||||
tmp = tempfile.mkdtemp(prefix='jedi-test-')
|
||||
settings.cache_directory = tmp
|
||||
|
||||
@request.addfinalizer
|
||||
def restore():
|
||||
settings.cache_directory = old
|
||||
shutil.rmtree(tmp)
|
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env bash
|
||||
# The script creates a separate folder in build/ and creates tags there, pushes
|
||||
# them and then uploads the package to PyPI.
|
||||
|
||||
set -eu -o pipefail
|
||||
|
||||
BASE_DIR=$(dirname $(readlink -f "$0"))
|
||||
cd $BASE_DIR
|
||||
|
||||
git fetch --tags
|
||||
|
||||
PROJECT_NAME=jedi
|
||||
BRANCH=master
|
||||
BUILD_FOLDER=build
|
||||
|
||||
[ -d $BUILD_FOLDER ] || mkdir $BUILD_FOLDER
|
||||
# Remove the previous deployment first.
|
||||
# Checkout the right branch
|
||||
cd $BUILD_FOLDER
|
||||
rm -rf $PROJECT_NAME
|
||||
git clone .. $PROJECT_NAME
|
||||
cd $PROJECT_NAME
|
||||
git checkout $BRANCH
|
||||
|
||||
# Test first.
|
||||
tox
|
||||
|
||||
# Create tag
|
||||
tag=v$(python -c "import $PROJECT_NAME; print($PROJECT_NAME.__version__)")
|
||||
|
||||
master_ref=$(git show-ref -s heads/$BRANCH)
|
||||
tag_ref=$(git show-ref -s $tag || true)
|
||||
if [[ $tag_ref ]]; then
|
||||
if [[ $tag_ref != $master_ref ]]; then
|
||||
echo 'Cannot tag something that has already been tagged with another commit.'
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
git tag $tag
|
||||
git push --tags
|
||||
fi
|
||||
|
||||
# Package and upload to PyPI
|
||||
#rm -rf dist/ - Not needed anymore, because the folder is never reused.
|
||||
echo `pwd`
|
||||
python setup.py sdist bdist_wheel
|
||||
# Maybe do a pip install twine before.
|
||||
twine upload dist/*
|
||||
|
||||
cd $BASE_DIR
|
||||
# The tags have been pushed to this repo. Push the tags to github, now.
|
||||
git push --tags
|
@ -0,0 +1,153 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Jedi.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Jedi.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Jedi"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Jedi"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
@ -0,0 +1,14 @@
|
||||
Installation
|
||||
------------
|
||||
|
||||
Install the graphviz library::
|
||||
|
||||
sudo apt-get install graphviz
|
||||
|
||||
Install sphinx::
|
||||
|
||||
sudo pip install sphinx
|
||||
|
||||
You might also need to install the Python graphviz interface::
|
||||
|
||||
sudo pip install graphviz
|
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
3
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_static/logo-src.txt
vendored
Normal file
3
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_static/logo-src.txt
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
The source of the logo is a photoshop file hosted here:
|
||||
|
||||
https://dl.dropboxusercontent.com/u/170011615/Jedi12_Logo.psd.xz
|
BIN
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_static/logo.png
vendored
Normal file
BIN
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_static/logo.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 28 KiB |
@ -0,0 +1,4 @@
|
||||
<h3>Github</h3>
|
||||
<iframe src="http://ghbtns.com/github-btn.html?user=davidhalter&repo=jedi&type=watch&count=true&size=large"
|
||||
frameborder="0" scrolling="0" width="170" height="30" allowtransparency="true"></iframe>
|
||||
<br><br>
|
@ -0,0 +1,3 @@
|
||||
<p class="logo"><a href="{{ pathto(master_doc) }}">
|
||||
<img class="logo" src="{{ pathto('_static/logo.png', 1) }}" alt="Logo"/>
|
||||
</a></p>
|
37
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_themes/flask/LICENSE
vendored
Normal file
37
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_themes/flask/LICENSE
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
Copyright (c) 2010 by Armin Ronacher.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Flask and Flask-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
28
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_themes/flask/layout.html
vendored
Normal file
28
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_themes/flask/layout.html
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
{%- extends "basic/layout.html" %}
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
{% endif %}
|
||||
<link media="only screen and (max-device-width: 480px)" href="{{
|
||||
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
||||
<a href="https://github.com/davidhalter/jedi">
|
||||
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub">
|
||||
</a>
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
© Copyright {{ copyright }}.
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
||||
</div>
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endblock %}
|
@ -0,0 +1,19 @@
|
||||
<h3>Related Topics</h3>
|
||||
<ul>
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||
{%- endfor %}
|
||||
{%- if prev %}
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||
}}">{{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||
}}">{{ next.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- for parent in parents %}
|
||||
</ul></li>
|
||||
{%- endfor %}
|
||||
</ul></li>
|
||||
</ul>
|
@ -0,0 +1,394 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
{% set page_width = '940px' %}
|
||||
{% set sidebar_width = '220px' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 17px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: {{ page_width }};
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: {{ sidebar_width }};
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: {{ page_width }};
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
{% if theme_index_logo %}
|
||||
div.indexwrapper h1 {
|
||||
text-indent: -999999px;
|
||||
background: url({{ theme_index_logo }}) no-repeat center center;
|
||||
height: {{ theme_index_logo_height }};
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #eee;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/*
|
||||
* small_flask.css_t
|
||||
* ~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 102.5%;
|
||||
margin: 50px -30px -20px -30px;
|
||||
padding: 10px 20px;
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||
div.sphinxsidebar h3 a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
div.related ul,
|
||||
div.related ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
index_logo =
|
||||
index_logo_height = 120px
|
||||
touch_icon =
|
125
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_themes/flask_theme_support.py
vendored
Normal file
125
vim/plugins/deoplete-jedi/rplugin/python3/deoplete/vendored/jedi/docs/_themes/flask_theme_support.py
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
"""
|
||||
Copyright (c) 2010 by Armin Ronacher.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Flask and Flask-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
background_color = "#f8f8f8"
|
||||
default_style = ""
|
||||
|
||||
styles = {
|
||||
# No corresponding class for the following:
|
||||
#Text: "", # class: ''
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
|
||||
Punctuation: "bold #000000", # class: 'p'
|
||||
|
||||
# because special names such as Name.Class, Name.Function, etc.
|
||||
# are not recognized as such later in the parsing, we choose them
|
||||
# to look the same as ordinary variables.
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
|
||||
Number: "#990000", # class: 'm'
|
||||
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
}
|
@ -0,0 +1,291 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Jedi documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Dec 26 00:11:34 2012.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
#needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo',
|
||||
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
source_encoding = 'utf-8'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Jedi'
|
||||
copyright = u'2012 - {today.year}, Jedi contributors'.format(today=datetime.date.today())
|
||||
|
||||
import jedi
|
||||
from jedi.utils import version_info
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '.'.join(str(x) for x in version_info()[:2])
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = jedi.__version__
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
#today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
#today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = []
|
||||
|
||||
# The reST default role (used for this markup: `text`) to use for all documents.
|
||||
#default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
#add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
#add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'flask'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_themes']
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
#html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
#html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
#html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
html_sidebars = {
|
||||
'**': [
|
||||
'sidebarlogo.html',
|
||||
'localtoc.html',
|
||||
#'relations.html',
|
||||
'ghbuttons.html',
|
||||
#'sourcelink.html',
|
||||
#'searchbox.html'
|
||||
]
|
||||
}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
#html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Jedidoc'
|
||||
|
||||
#html_style = 'default.css' # Force usage of default template on RTD
|
||||
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
#'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
#'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
#'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Jedi.tex', u'Jedi Documentation',
|
||||
u'Jedi contributors', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output --------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'jedi', u'Jedi Documentation',
|
||||
[u'Jedi contributors'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output ------------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Jedi', u'Jedi Documentation',
|
||||
u'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#texinfo_show_urls = 'footnote'
|
||||
|
||||
# -- Options for todo module ---------------------------------------------------
|
||||
|
||||
todo_include_todos = False
|
||||
|
||||
# -- Options for autodoc module ------------------------------------------------
|
||||
|
||||
autoclass_content = 'both'
|
||||
autodoc_member_order = 'bysource'
|
||||
autodoc_default_flags = []
|
||||
#autodoc_default_flags = ['members', 'undoc-members']
|
||||
|
||||
|
||||
# -- Options for intersphinx module --------------------------------------------
|
||||
|
||||
intersphinx_mapping = {
|
||||
'http://docs.python.org/': None,
|
||||
}
|
||||
|
||||
|
||||
def skip_deprecated(app, what, name, obj, skip, options):
|
||||
"""
|
||||
All attributes containing a deprecated note shouldn't be documented
|
||||
anymore. This makes it even clearer that they are not supported anymore.
|
||||
"""
|
||||
doc = obj.__doc__
|
||||
return skip or doc and '.. deprecated::' in doc
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.connect('autodoc-skip-member', skip_deprecated)
|
@ -0,0 +1,244 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
Jedi Development
|
||||
================
|
||||
|
||||
.. currentmodule:: jedi
|
||||
|
||||
.. note:: This documentation is for Jedi developers who want to improve Jedi
|
||||
itself, but have no idea how Jedi works. If you want to use Jedi for
|
||||
your IDE, look at the `plugin api <plugin-api.html>`_.
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
This page tries to address the fundamental demand for documentation of the
|
||||
|jedi| internals. Understanding a dynamic language is a complex task. Especially
|
||||
because type inference in Python can be a very recursive task. Therefore |jedi|
|
||||
couldn't get rid of complexity. I know that **simple is better than complex**,
|
||||
but unfortunately it sometimes requires complex solutions to understand complex
|
||||
systems.
|
||||
|
||||
Since most of the Jedi internals have been written by me (David Halter), this
|
||||
introduction will be written mostly by me, because no one else understands to
|
||||
the same level how Jedi works. Actually this is also the reason for exactly this
|
||||
part of the documentation. To make multiple people able to edit the Jedi core.
|
||||
|
||||
In five chapters I'm trying to describe the internals of |jedi|:
|
||||
|
||||
- :ref:`The Jedi Core <core>`
|
||||
- :ref:`Core Extensions <core-extensions>`
|
||||
- :ref:`Imports & Modules <imports-modules>`
|
||||
- :ref:`Caching & Recursions <caching-recursions>`
|
||||
- :ref:`Helper modules <dev-helpers>`
|
||||
|
||||
.. note:: Testing is not documented here, you'll find that
|
||||
`right here <testing.html>`_.
|
||||
|
||||
|
||||
.. _core:
|
||||
|
||||
The Jedi Core
|
||||
-------------
|
||||
|
||||
The core of Jedi consists of three parts:
|
||||
|
||||
- :ref:`Parser <parser>`
|
||||
- :ref:`Python code evaluation <evaluate>`
|
||||
- :ref:`API <dev-api>`
|
||||
|
||||
Most people are probably interested in :ref:`code evaluation <evaluate>`,
|
||||
because that's where all the magic happens. I need to introduce the :ref:`parser
|
||||
<parser>` first, because :mod:`jedi.evaluate` uses it extensively.
|
||||
|
||||
.. _parser:
|
||||
|
||||
Parser (parser/__init__.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.parser
|
||||
|
||||
Parser Tree (parser/tree.py)
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. automodule:: jedi.parser.tree
|
||||
|
||||
Class inheritance diagram:
|
||||
|
||||
.. inheritance-diagram::
|
||||
Module
|
||||
Class
|
||||
Function
|
||||
Lambda
|
||||
Flow
|
||||
ForStmt
|
||||
Import
|
||||
ExprStmt
|
||||
Param
|
||||
Name
|
||||
CompFor
|
||||
:parts: 1
|
||||
|
||||
.. _evaluate:
|
||||
|
||||
Evaluation of python code (evaluate/__init__.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate
|
||||
|
||||
Evaluation Representation (evaluate/representation.py)
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. automodule:: jedi.evaluate.representation
|
||||
|
||||
.. inheritance-diagram::
|
||||
jedi.evaluate.instance.TreeInstance
|
||||
jedi.evaluate.representation.ClassContext
|
||||
jedi.evaluate.representation.FunctionContext
|
||||
jedi.evaluate.representation.FunctionExecutionContext
|
||||
:parts: 1
|
||||
|
||||
|
||||
.. _name_resolution:
|
||||
|
||||
Name resolution (evaluate/finder.py)
|
||||
++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. automodule:: jedi.evaluate.finder
|
||||
|
||||
|
||||
.. _dev-api:
|
||||
|
||||
API (api.py and api_classes.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The API has been designed to be as easy to use as possible. The API
|
||||
documentation can be found `here <plugin-api.html>`_. The API itself contains
|
||||
little code that needs to be mentioned here. Generally I'm trying to be
|
||||
conservative with the API. I'd rather not add new API features if they are not
|
||||
necessary, because it's much harder to deprecate stuff than to add it later.
|
||||
|
||||
|
||||
.. _core-extensions:
|
||||
|
||||
Core Extensions
|
||||
---------------
|
||||
|
||||
Core Extensions is a summary of the following topics:
|
||||
|
||||
- :ref:`Iterables & Dynamic Arrays <iterables>`
|
||||
- :ref:`Dynamic Parameters <dynamic>`
|
||||
- :ref:`Diff Parser <diff-parser>`
|
||||
- :ref:`Docstrings <docstrings>`
|
||||
- :ref:`Refactoring <refactoring>`
|
||||
|
||||
These topics are very important to understand what Jedi additionally does, but
|
||||
they could be removed from Jedi and Jedi would still work. But slower and
|
||||
without some features.
|
||||
|
||||
.. _iterables:
|
||||
|
||||
Iterables & Dynamic Arrays (evaluate/iterable.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To understand Python on a deeper level, |jedi| needs to understand some of the
|
||||
dynamic features of Python like lists that are filled after creation:
|
||||
|
||||
.. automodule:: jedi.evaluate.iterable
|
||||
|
||||
|
||||
.. _dynamic:
|
||||
|
||||
Parameter completion (evaluate/dynamic.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.dynamic
|
||||
|
||||
|
||||
.. _diff-parser:
|
||||
|
||||
Diff Parser (parser/diff.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.parser.python.diff
|
||||
|
||||
.. _docstrings:
|
||||
|
||||
Docstrings (evaluate/docstrings.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.docstrings
|
||||
|
||||
.. _refactoring:
|
||||
|
||||
Refactoring (evaluate/refactoring.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.refactoring
|
||||
|
||||
|
||||
.. _imports-modules:
|
||||
|
||||
Imports & Modules
|
||||
-------------------
|
||||
|
||||
|
||||
- :ref:`Modules <modules>`
|
||||
- :ref:`Builtin Modules <builtin>`
|
||||
- :ref:`Imports <imports>`
|
||||
|
||||
|
||||
.. _builtin:
|
||||
|
||||
Compiled Modules (evaluate/compiled.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.compiled
|
||||
|
||||
|
||||
.. _imports:
|
||||
|
||||
Imports (evaluate/imports.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.imports
|
||||
|
||||
|
||||
.. _caching-recursions:
|
||||
|
||||
Caching & Recursions
|
||||
--------------------
|
||||
|
||||
|
||||
- :ref:`Caching <cache>`
|
||||
- :ref:`Recursions <recursion>`
|
||||
|
||||
.. _cache:
|
||||
|
||||
Caching (cache.py)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.cache
|
||||
|
||||
.. _recursion:
|
||||
|
||||
Recursions (recursion.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.recursion
|
||||
|
||||
|
||||
.. _dev-helpers:
|
||||
|
||||
Helper Modules
|
||||
---------------
|
||||
|
||||
Most other modules are not really central to how Jedi works. They all contain
|
||||
relevant code, but you if you understand the modules above, you pretty much
|
||||
understand Jedi.
|
||||
|
||||
Python 2/3 compatibility (_compatibility.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi._compatibility
|
@ -0,0 +1,263 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
Features and Caveats
|
||||
====================
|
||||
|
||||
Jedi obviously supports autocompletion. It's also possible to get it working in
|
||||
(:ref:`your REPL (IPython, etc.) <repl-completion>`).
|
||||
|
||||
Static analysis is also possible by using the command ``jedi.names``.
|
||||
|
||||
The Jedi Linter is currently in an alpha version and can be tested by calling
|
||||
``python -m jedi linter``.
|
||||
|
||||
Jedi would in theory support refactoring, but we have never publicized it,
|
||||
because it's not production ready. If you're interested in helping out here,
|
||||
let me know. With the latest parser changes, it should be very easy to actually
|
||||
make it work.
|
||||
|
||||
|
||||
General Features
|
||||
----------------
|
||||
|
||||
- python 2.6+ and 3.3+ support
|
||||
- ignores syntax errors and wrong indentation
|
||||
- can deal with complex module / function / class structures
|
||||
- virtualenv support
|
||||
- can infer function arguments from sphinx, epydoc and basic numpydoc docstrings,
|
||||
and PEP0484-style type hints (:ref:`type hinting <type-hinting>`)
|
||||
|
||||
|
||||
Supported Python Features
|
||||
-------------------------
|
||||
|
||||
|jedi| supports many of the widely used Python features:
|
||||
|
||||
- builtins
|
||||
- returns, yields, yield from
|
||||
- tuple assignments / array indexing / dictionary indexing / star unpacking
|
||||
- with-statement / exception handling
|
||||
- ``*args`` / ``**kwargs``
|
||||
- decorators / lambdas / closures
|
||||
- generators / iterators
|
||||
- some descriptors: property / staticmethod / classmethod
|
||||
- some magic methods: ``__call__``, ``__iter__``, ``__next__``, ``__get__``,
|
||||
``__getitem__``, ``__init__``
|
||||
- ``list.append()``, ``set.add()``, ``list.extend()``, etc.
|
||||
- (nested) list comprehensions / ternary expressions
|
||||
- relative imports
|
||||
- ``getattr()`` / ``__getattr__`` / ``__getattribute__``
|
||||
- function annotations (py3k feature, are ignored right now, but being parsed.
|
||||
I don't know what to do with them.)
|
||||
- class decorators (py3k feature, are being ignored too, until I find a use
|
||||
case, that doesn't work with |jedi|)
|
||||
- simple/usual ``sys.path`` modifications
|
||||
- ``isinstance`` checks for if/while/assert
|
||||
- namespace packages (includes ``pkgutil`` and ``pkg_resources`` namespaces)
|
||||
- Django / Flask / Buildout support
|
||||
|
||||
|
||||
Unsupported Features
|
||||
--------------------
|
||||
|
||||
Not yet implemented:
|
||||
|
||||
- manipulations of instances outside the instance variables without using
|
||||
methods
|
||||
- implicit namespace packages (Python 3.3+, `PEP 420 <https://www.python.org/dev/peps/pep-0420/>`_)
|
||||
|
||||
Will probably never be implemented:
|
||||
|
||||
- metaclasses (how could an auto-completion ever support this)
|
||||
- ``setattr()``, ``__import__()``
|
||||
- writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
|
||||
- evaluating ``if`` / ``while`` / ``del``
|
||||
|
||||
|
||||
Caveats
|
||||
-------
|
||||
|
||||
**Malformed Syntax**
|
||||
|
||||
Syntax errors and other strange stuff may lead to undefined behaviour of the
|
||||
completion. |jedi| is **NOT** a Python compiler, that tries to correct you. It
|
||||
is a tool that wants to help you. But **YOU** have to know Python, not |jedi|.
|
||||
|
||||
**Legacy Python 2 Features**
|
||||
|
||||
This framework should work for both Python 2/3. However, some things were just
|
||||
not as *pythonic* in Python 2 as things should be. To keep things simple, some
|
||||
older Python 2 features have been left out:
|
||||
|
||||
- Classes: Always Python 3 like, therefore all classes inherit from ``object``.
|
||||
- Generators: No ``next()`` method. The ``__next__()`` method is used instead.
|
||||
|
||||
**Slow Performance**
|
||||
|
||||
Importing ``numpy`` can be quite slow sometimes, as well as loading the
|
||||
builtins the first time. If you want to speed things up, you could write import
|
||||
hooks in |jedi|, which preload stuff. However, once loaded, this is not a
|
||||
problem anymore. The same is true for huge modules like ``PySide``, ``wx``,
|
||||
etc.
|
||||
|
||||
**Security**
|
||||
|
||||
Security is an important issue for |jedi|. Therefore no Python code is
|
||||
executed. As long as you write pure python, everything is evaluated
|
||||
statically. But: If you use builtin modules (``c_builtin``) there is no other
|
||||
option than to execute those modules. However: Execute isn't that critical (as
|
||||
e.g. in pythoncomplete, which used to execute *every* import!), because it
|
||||
means one import and no more. So basically the only dangerous thing is using
|
||||
the import itself. If your ``c_builtin`` uses some strange initializations, it
|
||||
might be dangerous. But if it does you're screwed anyways, because eventually
|
||||
you're going to execute your code, which executes the import.
|
||||
|
||||
|
||||
Recipes
|
||||
-------
|
||||
|
||||
Here are some tips on how to use |jedi| efficiently.
|
||||
|
||||
|
||||
.. _type-hinting:
|
||||
|
||||
Type Hinting
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If |jedi| cannot detect the type of a function argument correctly (due to the
|
||||
dynamic nature of Python), you can help it by hinting the type using
|
||||
one of the following docstring/annotation syntax styles:
|
||||
|
||||
**PEP-0484 style**
|
||||
|
||||
https://www.python.org/dev/peps/pep-0484/
|
||||
|
||||
function annotations (python 3 only; python 2 function annotations with
|
||||
comments in planned but not yet implemented)
|
||||
|
||||
::
|
||||
|
||||
def myfunction(node: ProgramNode, foo: str) -> None:
|
||||
"""Do something with a ``node``.
|
||||
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
|
||||
assignment, for-loop and with-statement type hints (all python versions).
|
||||
Note that the type hints must be on the same line as the statement
|
||||
|
||||
::
|
||||
|
||||
x = foo() # type: int
|
||||
x, y = 2, 3 # type: typing.Optional[int], typing.Union[int, str] # typing module is mostly supported
|
||||
for key, value in foo.items(): # type: str, Employee # note that Employee must be in scope
|
||||
pass
|
||||
with foo() as f: # type: int
|
||||
print(f + 3)
|
||||
|
||||
Most of the features in PEP-0484 are supported including the typing module
|
||||
(for python < 3.5 you have to do ``pip install typing`` to use these),
|
||||
and forward references.
|
||||
|
||||
Things that are missing (and this is not an exhaustive list; some of these
|
||||
are planned, others might be hard to implement and provide little worth):
|
||||
|
||||
- annotating functions with comments: https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
|
||||
- understanding ``typing.cast()``
|
||||
- stub files: https://www.python.org/dev/peps/pep-0484/#stub-files
|
||||
- ``typing.Callable``
|
||||
- ``typing.TypeVar``
|
||||
- User defined generic types: https://www.python.org/dev/peps/pep-0484/#user-defined-generic-types
|
||||
|
||||
**Sphinx style**
|
||||
|
||||
http://sphinx-doc.org/domains.html#info-field-lists
|
||||
|
||||
::
|
||||
|
||||
def myfunction(node, foo):
|
||||
"""Do something with a ``node``.
|
||||
|
||||
:type node: ProgramNode
|
||||
:param str foo: foo parameter description
|
||||
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
**Epydoc**
|
||||
|
||||
http://epydoc.sourceforge.net/manual-fields.html
|
||||
|
||||
::
|
||||
|
||||
def myfunction(node):
|
||||
"""Do something with a ``node``.
|
||||
|
||||
@type node: ProgramNode
|
||||
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
**Numpydoc**
|
||||
|
||||
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
|
||||
|
||||
In order to support the numpydoc format, you need to install the `numpydoc
|
||||
<https://pypi.python.org/pypi/numpydoc>`__ package.
|
||||
|
||||
::
|
||||
|
||||
def foo(var1, var2, long_var_name='hi'):
|
||||
r"""A one-line summary that does not use variable names or the
|
||||
function name.
|
||||
|
||||
...
|
||||
|
||||
Parameters
|
||||
----------
|
||||
var1 : array_like
|
||||
Array_like means all those objects -- lists, nested lists,
|
||||
etc. -- that can be converted to an array. We can also
|
||||
refer to variables like `var1`.
|
||||
var2 : int
|
||||
The type above can either refer to an actual Python type
|
||||
(e.g. ``int``), or describe the type of the variable in more
|
||||
detail, e.g. ``(N,) ndarray`` or ``array_like``.
|
||||
long_variable_name : {'hi', 'ho'}, optional
|
||||
Choices in brackets, default first when optional.
|
||||
|
||||
...
|
||||
|
||||
"""
|
||||
var2.| # complete here
|
||||
|
||||
A little history
|
||||
----------------
|
||||
|
||||
The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit
|
||||
of the precognition the Jedi have. There's even an awesome `scene
|
||||
<http://www.youtube.com/watch?v=5BDO3pyavOY>`_ of Monty Python Jedis :-).
|
||||
|
||||
But actually the name hasn't so much to do with Star Wars. It's part of my
|
||||
second name.
|
||||
|
||||
After I explained Guido van Rossum, how some parts of my auto-completion work,
|
||||
he said (we drank a beer or two):
|
||||
|
||||
*"Oh, that worries me..."*
|
||||
|
||||
When it's finished, I hope he'll like it :-)
|
||||
|
||||
I actually started Jedi, because there were no good solutions available for VIM.
|
||||
Most auto-completions just didn't work well. The only good solution was PyCharm.
|
||||
But I like my good old VIM. Rope was never really intended to be an
|
||||
auto-completion (and also I really hate project folders for my Python scripts).
|
||||
It's more of a refactoring suite. So I decided to do my own version of a
|
||||
completion, which would execute non-dangerous code. But I soon realized, that
|
||||
this wouldn't work. So I built an extremely recursive thing which understands
|
||||
many of Python's key features.
|
||||
|
||||
By the way, I really tried to program it as understandable as possible. But I
|
||||
think understanding it might need quite some time, because of its recursive
|
||||
nature.
|
@ -0,0 +1,83 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
Installation and Configuration
|
||||
==============================
|
||||
|
||||
You can either include |jedi| as a submodule in your text editor plugin (like
|
||||
jedi-vim_ does by default), or you can install it systemwide.
|
||||
|
||||
.. note:: This just installs the |jedi| library, not the :ref:`editor plugins
|
||||
<editor-plugins>`. For information about how to make it work with your
|
||||
editor, refer to the corresponding documentation.
|
||||
|
||||
|
||||
The preferred way
|
||||
-----------------
|
||||
|
||||
On any system you can install |jedi| directly from the Python package index
|
||||
using pip::
|
||||
|
||||
sudo pip install jedi
|
||||
|
||||
If you want to install the current development version (master branch)::
|
||||
|
||||
sudo pip install -e git://github.com/davidhalter/jedi.git#egg=jedi
|
||||
|
||||
|
||||
System-wide installation via a package manager
|
||||
----------------------------------------------
|
||||
|
||||
Arch Linux
|
||||
~~~~~~~~~~
|
||||
|
||||
You can install |jedi| directly from official Arch Linux packages:
|
||||
|
||||
- `python-jedi <https://www.archlinux.org/packages/community/any/python-jedi/>`__
|
||||
(Python 3)
|
||||
- `python2-jedi <https://www.archlinux.org/packages/community/any/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 Arch Linux <https://www.archlinux.org/packages/community/any/vim-jedi/>`__.)
|
||||
|
||||
Debian
|
||||
~~~~~~
|
||||
|
||||
Debian packages are available in the `unstable repository
|
||||
<http://packages.debian.org/search?keywords=python%20jedi>`__.
|
||||
|
||||
Others
|
||||
~~~~~~
|
||||
|
||||
We are in the discussion of adding |jedi| to the Fedora repositories.
|
||||
|
||||
|
||||
Manual installation from a downloaded package
|
||||
---------------------------------------------
|
||||
|
||||
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
|
||||
|jedi| and install it manually.
|
||||
|
||||
To install it, navigate to the directory containing `setup.py` on your console
|
||||
and type::
|
||||
|
||||
sudo python setup.py install
|
||||
|
||||
|
||||
Inclusion as a submodule
|
||||
------------------------
|
||||
|
||||
If you use an editor plugin like jedi-vim_, you can simply include |jedi| as a
|
||||
git submodule of the plugin directory. Vim plugin managers like Vundle_ or
|
||||
Pathogen_ make it very easy to keep submodules up to date.
|
||||
|
||||
|
||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||
.. _vundle: https://github.com/gmarik/vundle
|
||||
.. _pathogen: https://github.com/tpope/vim-pathogen
|
@ -0,0 +1,36 @@
|
||||
.. _xxx:
|
||||
|
||||
Parser Tree
|
||||
===========
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
.. automodule:: jedi.parser.python
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Parser Tree Base Class
|
||||
----------------------
|
||||
|
||||
All nodes and leaves have these methods/properties:
|
||||
|
||||
.. autoclass:: jedi.parser.tree.NodeOrLeaf
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Python Parser Tree
|
||||
------------------
|
||||
|
||||
.. automodule:: jedi.parser.python.tree
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Utility
|
||||
-------
|
||||
|
||||
.. autofunction:: jedi.parser.tree.search_ancestor
|
@ -0,0 +1,10 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
.. _plugin-api-classes:
|
||||
|
||||
API Return Classes
|
||||
------------------
|
||||
|
||||
.. automodule:: jedi.api.classes
|
||||
:members:
|
||||
:undoc-members:
|
@ -0,0 +1,100 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
The Plugin API
|
||||
==============
|
||||
|
||||
.. currentmodule:: jedi
|
||||
|
||||
Note: This documentation is for Plugin developers, who want to improve their
|
||||
editors/IDE autocompletion
|
||||
|
||||
If you want to use |jedi|, you first need to ``import jedi``. You then have
|
||||
direct access to the :class:`.Script`. You can then call the functions
|
||||
documented here. These functions return :ref:`API classes
|
||||
<plugin-api-classes>`.
|
||||
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
The deprecation process is as follows:
|
||||
|
||||
1. A deprecation is announced in the next major/minor release.
|
||||
2. We wait either at least a year & at least two minor releases until we remove
|
||||
the deprecated functionality.
|
||||
|
||||
|
||||
API documentation
|
||||
-----------------
|
||||
|
||||
API Interface
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.api
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Completions:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''import json; json.l'''
|
||||
>>> script = jedi.Script(source, 1, 19, '')
|
||||
>>> script
|
||||
<jedi.api.Script object at 0x2121b10>
|
||||
>>> completions = script.completions()
|
||||
>>> completions
|
||||
[<Completion: load>, <Completion: loads>]
|
||||
>>> completions[1]
|
||||
<Completion: loads>
|
||||
>>> completions[1].complete
|
||||
'oads'
|
||||
>>> completions[1].name
|
||||
'loads'
|
||||
|
||||
Definitions / Goto:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''def my_func():
|
||||
... print 'called'
|
||||
...
|
||||
... alias = my_func
|
||||
... my_list = [1, None, alias]
|
||||
... inception = my_list[2]
|
||||
...
|
||||
... inception()'''
|
||||
>>> script = jedi.Script(source, 8, 1, '')
|
||||
>>>
|
||||
>>> script.goto_assignments()
|
||||
[<Definition inception=my_list[2]>]
|
||||
>>>
|
||||
>>> script.goto_definitions()
|
||||
[<Definition def my_func>]
|
||||
|
||||
Related names:
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''x = 3
|
||||
... if 1 == 2:
|
||||
... x = 4
|
||||
... else:
|
||||
... del x'''
|
||||
>>> script = jedi.Script(source, 5, 8, '')
|
||||
>>> rns = script.related_names()
|
||||
>>> rns
|
||||
[<RelatedName x@3,4>, <RelatedName x@1,0>]
|
||||
>>> rns[0].start_pos
|
||||
(3, 4)
|
||||
>>> rns[0].is_keyword
|
||||
False
|
||||
>>> rns[0].text
|
||||
'x'
|
@ -0,0 +1,6 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
Settings
|
||||
========
|
||||
|
||||
.. automodule:: jedi.settings
|
@ -0,0 +1,106 @@
|
||||
|
||||
This file is the start of the documentation of how static analysis works.
|
||||
|
||||
Below is a list of parser names that are used within nodes_to_execute.
|
||||
|
||||
------------ cared for:
|
||||
global_stmt
|
||||
exec_stmt # no priority
|
||||
assert_stmt
|
||||
if_stmt
|
||||
while_stmt
|
||||
for_stmt
|
||||
try_stmt
|
||||
(except_clause)
|
||||
with_stmt
|
||||
(with_item)
|
||||
(with_var)
|
||||
print_stmt
|
||||
del_stmt
|
||||
return_stmt
|
||||
raise_stmt
|
||||
yield_expr
|
||||
file_input
|
||||
funcdef
|
||||
param
|
||||
old_lambdef
|
||||
lambdef
|
||||
import_name
|
||||
import_from
|
||||
(import_as_name)
|
||||
(dotted_as_name)
|
||||
(import_as_names)
|
||||
(dotted_as_names)
|
||||
(dotted_name)
|
||||
classdef
|
||||
comp_for
|
||||
(comp_if) ?
|
||||
decorator
|
||||
|
||||
----------- add basic
|
||||
test
|
||||
or_test
|
||||
and_test
|
||||
not_test
|
||||
expr
|
||||
xor_expr
|
||||
and_expr
|
||||
shift_expr
|
||||
arith_expr
|
||||
term
|
||||
factor
|
||||
power
|
||||
atom
|
||||
comparison
|
||||
expr_stmt
|
||||
testlist
|
||||
testlist1
|
||||
testlist_safe
|
||||
|
||||
----------- special care:
|
||||
# mostly depends on how we handle the other ones.
|
||||
testlist_star_expr # should probably just work with expr_stmt
|
||||
star_expr
|
||||
exprlist # just ignore? then names are just resolved. Strange anyway, bc expr is not really allowed in the list, typically.
|
||||
|
||||
----------- ignore:
|
||||
suite
|
||||
subscriptlist
|
||||
subscript
|
||||
simple_stmt
|
||||
?? sliceop # can probably just be added.
|
||||
testlist_comp # prob ignore and care about it with atom.
|
||||
dictorsetmaker
|
||||
trailer
|
||||
decorators
|
||||
decorated
|
||||
# always execute function arguments? -> no problem with stars.
|
||||
# Also arglist and argument are different in different grammars.
|
||||
arglist
|
||||
argument
|
||||
|
||||
|
||||
----------- remove:
|
||||
tname # only exists in current Jedi parser. REMOVE!
|
||||
tfpdef # python 2: tuple assignment; python 3: annotation
|
||||
vfpdef # reduced in python 3 and therefore not existing.
|
||||
tfplist # not in 3
|
||||
vfplist # not in 3
|
||||
|
||||
--------- not existing with parser reductions.
|
||||
small_stmt
|
||||
import_stmt
|
||||
flow_stmt
|
||||
compound_stmt
|
||||
stmt
|
||||
pass_stmt
|
||||
break_stmt
|
||||
continue_stmt
|
||||
comp_op
|
||||
augassign
|
||||
old_test
|
||||
typedargslist # afaik becomes [param]
|
||||
varargslist # dito
|
||||
vname
|
||||
comp_iter
|
||||
test_nocond
|
@ -0,0 +1,40 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
Jedi Testing
|
||||
============
|
||||
|
||||
The test suite depends on ``tox`` and ``pytest``::
|
||||
|
||||
pip install tox pytest
|
||||
|
||||
To run the tests for all supported Python versions::
|
||||
|
||||
tox
|
||||
|
||||
If you want to test only a specific Python version (e.g. Python 2.7), it's as
|
||||
easy as::
|
||||
|
||||
tox -e py27
|
||||
|
||||
Tests are also run automatically on `Travis CI
|
||||
<https://travis-ci.org/davidhalter/jedi/>`_.
|
||||
|
||||
You want to add a test for |jedi|? Great! We love that. Normally you should
|
||||
write your tests as :ref:`Blackbox Tests <blackbox>`. Most tests would
|
||||
fit right in there.
|
||||
|
||||
For specific API testing we're using simple unit tests, with a focus on a
|
||||
simple and readable testing structure.
|
||||
|
||||
.. _blackbox:
|
||||
|
||||
Blackbox Tests (run.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: test.run
|
||||
|
||||
Refactoring Tests (refactor.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: test.refactor
|
||||
|
@ -0,0 +1,122 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
End User Usage
|
||||
==============
|
||||
|
||||
If you are a not an IDE Developer, the odds are that you just want to use
|
||||
|jedi| as a browser plugin or in the shell. Yes that's :ref:`also possible
|
||||
<repl-completion>`!
|
||||
|
||||
|jedi| is relatively young and can be used in a variety of Plugins and
|
||||
Software. If your Editor/IDE is not among them, recommend |jedi| to your IDE
|
||||
developers.
|
||||
|
||||
|
||||
.. _editor-plugins:
|
||||
|
||||
Editor Plugins
|
||||
--------------
|
||||
|
||||
Vim:
|
||||
|
||||
- jedi-vim_
|
||||
- YouCompleteMe_
|
||||
- deoplete-jedi_
|
||||
|
||||
Emacs:
|
||||
|
||||
- Jedi.el_
|
||||
- elpy_
|
||||
- anaconda-mode_
|
||||
|
||||
Sublime Text 2/3:
|
||||
|
||||
- SublimeJEDI_ (ST2 & ST3)
|
||||
- anaconda_ (only ST3)
|
||||
|
||||
SynWrite:
|
||||
|
||||
- SynJedi_
|
||||
|
||||
TextMate:
|
||||
|
||||
- Textmate_ (Not sure if it's actually working)
|
||||
|
||||
Kate:
|
||||
|
||||
- Kate_ version 4.13+ `supports it natively
|
||||
<https://projects.kde.org/projects/kde/applications/kate/repository/entry/addons/kate/pate/src/plugins/python_autocomplete_jedi.py?rev=KDE%2F4.13>`__,
|
||||
you have to enable it, though.
|
||||
|
||||
Visual Studio Code:
|
||||
|
||||
- `Python Extension`_
|
||||
|
||||
Atom:
|
||||
|
||||
- autocomplete-python-jedi_
|
||||
|
||||
SourceLair:
|
||||
|
||||
- SourceLair_
|
||||
|
||||
GNOME Builder:
|
||||
|
||||
- `GNOME Builder`_ `supports it natively
|
||||
<https://git.gnome.org/browse/gnome-builder/tree/plugins/jedi>`__,
|
||||
and is enabled by default.
|
||||
|
||||
Gedit:
|
||||
|
||||
- gedi_
|
||||
|
||||
Eric IDE:
|
||||
|
||||
- `Eric IDE`_ (Available as a plugin)
|
||||
|
||||
Web Debugger:
|
||||
|
||||
- wdb_
|
||||
|
||||
and many more!
|
||||
|
||||
.. _repl-completion:
|
||||
|
||||
Tab Completion in the Python Shell
|
||||
----------------------------------
|
||||
|
||||
Starting with Ipython `6.0.0` Jedi is a dependency of IPython. Autocompletion
|
||||
in IPython is therefore possible without additional configuration.
|
||||
|
||||
There are two different options how you can use Jedi autocompletion in
|
||||
your Python interpreter. One with your custom ``$HOME/.pythonrc.py`` file
|
||||
and one that uses ``PYTHONSTARTUP``.
|
||||
|
||||
Using ``PYTHONSTARTUP``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.api.replstartup
|
||||
|
||||
Using a custom ``$HOME/.pythonrc.py``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autofunction:: jedi.utils.setup_readline
|
||||
|
||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||
.. _youcompleteme: http://valloric.github.io/YouCompleteMe/
|
||||
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
|
||||
.. _Jedi.el: https://github.com/tkf/emacs-jedi
|
||||
.. _elpy: https://github.com/jorgenschaefer/elpy
|
||||
.. _anaconda-mode: https://github.com/proofit404/anaconda-mode
|
||||
.. _sublimejedi: https://github.com/srusskih/SublimeJEDI
|
||||
.. _anaconda: https://github.com/DamnWidget/anaconda
|
||||
.. _SynJedi: http://uvviewsoft.com/synjedi/
|
||||
.. _wdb: https://github.com/Kozea/wdb
|
||||
.. _TextMate: https://github.com/lawrenceakka/python-jedi.tmbundle
|
||||
.. _kate: http://kate-editor.org/
|
||||
.. _autocomplete-python-jedi: https://atom.io/packages/autocomplete-python-jedi
|
||||
.. _SourceLair: https://www.sourcelair.com
|
||||
.. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/
|
||||
.. _gedi: https://github.com/isamert/gedi
|
||||
.. _Eric IDE: http://eric-ide.python-projects.org
|
||||
.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python
|
@ -0,0 +1,3 @@
|
||||
:orphan:
|
||||
|
||||
.. |jedi| replace:: *Jedi*
|
@ -0,0 +1,40 @@
|
||||
.. include global.rst
|
||||
|
||||
Jedi - an awesome autocompletion/static analysis library for Python
|
||||
===================================================================
|
||||
|
||||
Release v\ |release|. (:doc:`Installation <docs/installation>`)
|
||||
|
||||
.. automodule:: jedi
|
||||
|
||||
Autocompletion can look like this (e.g. VIM plugin):
|
||||
|
||||
.. figure:: _screenshots/screenshot_complete.png
|
||||
|
||||
|
||||
.. _toc:
|
||||
|
||||
Docs
|
||||
----
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
docs/usage
|
||||
docs/installation
|
||||
docs/features
|
||||
docs/plugin-api
|
||||
docs/plugin-api-classes
|
||||
docs/settings
|
||||
docs/development
|
||||
docs/testing
|
||||
|
||||
|
||||
.. _resources:
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
- `Source Code on Github <https://github.com/davidhalter/jedi>`_
|
||||
- `Travis Testing <https://travis-ci.org/davidhalter/jedi>`_
|
||||
- `Python Package Index <http://pypi.python.org/pypi/jedi/>`_
|
@ -0,0 +1,43 @@
|
||||
"""
|
||||
Jedi is a static analysis tool for Python that can be used in IDEs/editors. Its
|
||||
historic focus is autocompletion, but does static analysis for now as well.
|
||||
Jedi is fast and is very well tested. It understands Python on a deeper level
|
||||
than all other static analysis frameworks for Python.
|
||||
|
||||
Jedi has support for two different goto functions. It's possible to search for
|
||||
related names and to list all names in a Python file and infer them. Jedi
|
||||
understands docstrings and you can use Jedi autocompletion in your REPL as
|
||||
well.
|
||||
|
||||
Jedi uses a very simple API to connect with IDE's. There's a reference
|
||||
implementation as a `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_,
|
||||
which uses Jedi's autocompletion. We encourage you to use Jedi in your IDEs.
|
||||
It's really easy.
|
||||
|
||||
To give you a simple example how you can use the Jedi library, here is an
|
||||
example for the autocompletion feature:
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''
|
||||
... import datetime
|
||||
... datetime.da'''
|
||||
>>> script = jedi.Script(source, 3, len('datetime.da'), 'example.py')
|
||||
>>> script
|
||||
<Script: 'example.py'>
|
||||
>>> completions = script.completions()
|
||||
>>> completions #doctest: +ELLIPSIS
|
||||
[<Completion: date>, <Completion: datetime>, ...]
|
||||
>>> print(completions[0].complete)
|
||||
te
|
||||
>>> print(completions[0].name)
|
||||
date
|
||||
|
||||
As you see Jedi is pretty simple and allows you to concentrate on writing a
|
||||
good text editor, while still having very good IDE features for Python.
|
||||
"""
|
||||
|
||||
__version__ = '0.11.0'
|
||||
|
||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
||||
preload_module, names
|
||||
from jedi import settings
|
@ -0,0 +1,48 @@
|
||||
import sys
|
||||
from os.path import join, dirname, abspath, isdir
|
||||
|
||||
|
||||
def _start_linter():
|
||||
"""
|
||||
This is a pre-alpha API. You're not supposed to use it at all, except for
|
||||
testing. It will very likely change.
|
||||
"""
|
||||
import jedi
|
||||
|
||||
if '--debug' in sys.argv:
|
||||
jedi.set_debug_function()
|
||||
|
||||
for path in sys.argv[2:]:
|
||||
if path.startswith('--'):
|
||||
continue
|
||||
if isdir(path):
|
||||
import fnmatch
|
||||
import os
|
||||
|
||||
paths = []
|
||||
for root, dirnames, filenames in os.walk(path):
|
||||
for filename in fnmatch.filter(filenames, '*.py'):
|
||||
paths.append(os.path.join(root, filename))
|
||||
else:
|
||||
paths = [path]
|
||||
|
||||
try:
|
||||
for path in paths:
|
||||
for error in jedi.Script(path=path)._analysis():
|
||||
print(error)
|
||||
except Exception:
|
||||
if '--pdb' in sys.argv:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
import pdb
|
||||
pdb.post_mortem()
|
||||
else:
|
||||
raise
|
||||
|
||||
|
||||
if len(sys.argv) == 2 and sys.argv[1] == 'repl':
|
||||
# don't want to use __main__ only for repl yet, maybe we want to use it for
|
||||
# something else. So just use the keyword ``repl`` for now.
|
||||
print(join(dirname(abspath(__file__)), 'api', 'replstartup.py'))
|
||||
elif len(sys.argv) > 1 and sys.argv[1] == 'linter':
|
||||
_start_linter()
|
@ -0,0 +1,299 @@
|
||||
"""
|
||||
To ensure compatibility from Python ``2.6`` - ``3.3``, a module has been
|
||||
created. Clearly there is huge need to use conforming syntax.
|
||||
"""
|
||||
import sys
|
||||
import imp
|
||||
import os
|
||||
import re
|
||||
import pkgutil
|
||||
import warnings
|
||||
try:
|
||||
import importlib
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Cannot use sys.version.major and minor names, because in Python 2.6 it's not
|
||||
# a namedtuple.
|
||||
is_py3 = sys.version_info[0] >= 3
|
||||
is_py33 = is_py3 and sys.version_info[1] >= 3
|
||||
is_py34 = is_py3 and sys.version_info[1] >= 4
|
||||
is_py35 = is_py3 and sys.version_info[1] >= 5
|
||||
is_py26 = not is_py3 and sys.version_info[1] < 7
|
||||
py_version = int(str(sys.version_info[0]) + str(sys.version_info[1]))
|
||||
|
||||
|
||||
class DummyFile(object):
|
||||
def __init__(self, loader, string):
|
||||
self.loader = loader
|
||||
self.string = string
|
||||
|
||||
def read(self):
|
||||
return self.loader.get_source(self.string)
|
||||
|
||||
def close(self):
|
||||
del self.loader
|
||||
|
||||
|
||||
def find_module_py34(string, path=None, fullname=None):
|
||||
implicit_namespace_pkg = False
|
||||
spec = None
|
||||
loader = None
|
||||
|
||||
spec = importlib.machinery.PathFinder.find_spec(string, path)
|
||||
if hasattr(spec, 'origin'):
|
||||
origin = spec.origin
|
||||
implicit_namespace_pkg = origin == 'namespace'
|
||||
|
||||
# We try to disambiguate implicit namespace pkgs with non implicit namespace pkgs
|
||||
if implicit_namespace_pkg:
|
||||
fullname = string if not path else fullname
|
||||
implicit_ns_info = ImplicitNSInfo(fullname, spec.submodule_search_locations._path)
|
||||
return None, implicit_ns_info, False
|
||||
|
||||
# we have found the tail end of the dotted path
|
||||
if hasattr(spec, 'loader'):
|
||||
loader = spec.loader
|
||||
return find_module_py33(string, path, loader)
|
||||
|
||||
def find_module_py33(string, path=None, loader=None, fullname=None):
|
||||
loader = loader or importlib.machinery.PathFinder.find_module(string, path)
|
||||
|
||||
if loader is None and path is None: # Fallback to find builtins
|
||||
try:
|
||||
with warnings.catch_warnings(record=True):
|
||||
# Mute "DeprecationWarning: Use importlib.util.find_spec()
|
||||
# instead." While we should replace that in the future, it's
|
||||
# probably good to wait until we deprecate Python 3.3, since
|
||||
# it was added in Python 3.4 and find_loader hasn't been
|
||||
# removed in 3.6.
|
||||
loader = importlib.find_loader(string)
|
||||
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 " + repr(e))
|
||||
|
||||
if loader is None:
|
||||
raise ImportError("Couldn't find a loader for {0}".format(string))
|
||||
|
||||
try:
|
||||
is_package = loader.is_package(string)
|
||||
if is_package:
|
||||
if hasattr(loader, 'path'):
|
||||
module_path = os.path.dirname(loader.path)
|
||||
else:
|
||||
# At least zipimporter does not have path attribute
|
||||
module_path = os.path.dirname(loader.get_filename(string))
|
||||
if hasattr(loader, 'archive'):
|
||||
module_file = DummyFile(loader, string)
|
||||
else:
|
||||
module_file = None
|
||||
else:
|
||||
module_path = loader.get_filename(string)
|
||||
module_file = DummyFile(loader, string)
|
||||
except AttributeError:
|
||||
# ExtensionLoader has not attribute get_filename, instead it has a
|
||||
# path attribute that we can use to retrieve the module path
|
||||
try:
|
||||
module_path = loader.path
|
||||
module_file = DummyFile(loader, string)
|
||||
except AttributeError:
|
||||
module_path = string
|
||||
module_file = None
|
||||
finally:
|
||||
is_package = False
|
||||
|
||||
if hasattr(loader, 'archive'):
|
||||
module_path = loader.archive
|
||||
|
||||
return module_file, module_path, is_package
|
||||
|
||||
|
||||
def find_module_pre_py33(string, path=None, fullname=None):
|
||||
try:
|
||||
module_file, module_path, description = imp.find_module(string, path)
|
||||
module_type = description[2]
|
||||
return module_file, module_path, module_type is imp.PKG_DIRECTORY
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if path is None:
|
||||
path = sys.path
|
||||
for item in path:
|
||||
loader = pkgutil.get_importer(item)
|
||||
if loader:
|
||||
try:
|
||||
loader = loader.find_module(string)
|
||||
if loader:
|
||||
is_package = loader.is_package(string)
|
||||
is_archive = hasattr(loader, 'archive')
|
||||
try:
|
||||
module_path = loader.get_filename(string)
|
||||
except AttributeError:
|
||||
# fallback for py26
|
||||
try:
|
||||
module_path = loader._get_filename(string)
|
||||
except AttributeError:
|
||||
continue
|
||||
if is_package:
|
||||
module_path = os.path.dirname(module_path)
|
||||
if is_archive:
|
||||
module_path = loader.archive
|
||||
file = None
|
||||
if not is_package or is_archive:
|
||||
file = DummyFile(loader, string)
|
||||
return (file, module_path, is_package)
|
||||
except ImportError:
|
||||
pass
|
||||
raise ImportError("No module named {0}".format(string))
|
||||
|
||||
|
||||
find_module = find_module_py33 if is_py33 else find_module_pre_py33
|
||||
find_module = find_module_py34 if is_py34 else find_module
|
||||
find_module.__doc__ = """
|
||||
Provides information about a module.
|
||||
|
||||
This function isolates the differences in importing libraries introduced with
|
||||
python 3.3 on; it gets a module name and optionally a path. It will return a
|
||||
tuple containin an open file for the module (if not builtin), the filename
|
||||
or the name of the module if it is a builtin one and a boolean indicating
|
||||
if the module is contained in a package.
|
||||
"""
|
||||
|
||||
|
||||
class ImplicitNSInfo(object):
|
||||
"""Stores information returned from an implicit namespace spec"""
|
||||
def __init__(self, name, paths):
|
||||
self.name = name
|
||||
self.paths = paths
|
||||
|
||||
# unicode function
|
||||
try:
|
||||
unicode = unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
# exec function
|
||||
if is_py3:
|
||||
def exec_function(source, global_map):
|
||||
exec(source, global_map)
|
||||
else:
|
||||
eval(compile("""def exec_function(source, global_map):
|
||||
exec source in global_map """, 'blub', 'exec'))
|
||||
|
||||
# re-raise function
|
||||
if is_py3:
|
||||
def reraise(exception, traceback):
|
||||
raise exception.with_traceback(traceback)
|
||||
else:
|
||||
eval(compile("""
|
||||
def reraise(exception, traceback):
|
||||
raise exception, None, traceback
|
||||
""", 'blub', 'exec'))
|
||||
|
||||
reraise.__doc__ = """
|
||||
Re-raise `exception` with a `traceback` object.
|
||||
|
||||
Usage::
|
||||
|
||||
reraise(Exception, sys.exc_info()[2])
|
||||
|
||||
"""
|
||||
|
||||
class Python3Method(object):
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, objtype):
|
||||
if obj is None:
|
||||
return lambda *args, **kwargs: self.func(*args, **kwargs)
|
||||
else:
|
||||
return lambda *args, **kwargs: self.func(obj, *args, **kwargs)
|
||||
|
||||
|
||||
def use_metaclass(meta, *bases):
|
||||
""" Create a class with a metaclass. """
|
||||
if not bases:
|
||||
bases = (object,)
|
||||
return meta("HackClass", bases, {})
|
||||
|
||||
|
||||
try:
|
||||
encoding = sys.stdout.encoding
|
||||
if encoding is None:
|
||||
encoding = 'utf-8'
|
||||
except AttributeError:
|
||||
encoding = 'ascii'
|
||||
|
||||
|
||||
def u(string):
|
||||
"""Cast to unicode DAMMIT!
|
||||
Written because Python2 repr always implicitly casts to a string, so we
|
||||
have to cast back to a unicode (and we now that we always deal with valid
|
||||
unicode, because we check that in the beginning).
|
||||
"""
|
||||
if is_py3:
|
||||
return str(string)
|
||||
|
||||
if not isinstance(string, unicode):
|
||||
return unicode(str(string), 'UTF-8')
|
||||
return string
|
||||
|
||||
try:
|
||||
import builtins # module name in python 3
|
||||
except ImportError:
|
||||
import __builtin__ as builtins
|
||||
|
||||
|
||||
import ast
|
||||
|
||||
|
||||
def literal_eval(string):
|
||||
# py3.0, py3.1 and py32 don't support unicode literals. Support those, I
|
||||
# don't want to write two versions of the tokenizer.
|
||||
if is_py3 and sys.version_info.minor < 3:
|
||||
if re.match('[uU][\'"]', string):
|
||||
string = string[1:]
|
||||
return ast.literal_eval(string)
|
||||
|
||||
|
||||
try:
|
||||
from itertools import zip_longest
|
||||
except ImportError:
|
||||
from itertools import izip_longest as zip_longest # Python 2
|
||||
|
||||
try:
|
||||
FileNotFoundError = FileNotFoundError
|
||||
except NameError:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
|
||||
def no_unicode_pprint(dct):
|
||||
"""
|
||||
Python 2/3 dict __repr__ may be different, because of unicode differens
|
||||
(with or without a `u` prefix). Normally in doctests we could use `pprint`
|
||||
to sort dicts and check for equality, but here we have to write a separate
|
||||
function to do that.
|
||||
"""
|
||||
import pprint
|
||||
s = pprint.pformat(dct)
|
||||
print(re.sub("u'", "'", s))
|
||||
|
||||
|
||||
def utf8_repr(func):
|
||||
"""
|
||||
``__repr__`` methods in Python 2 don't allow unicode objects to be
|
||||
returned. Therefore cast them to utf-8 bytes in this decorator.
|
||||
"""
|
||||
def wrapper(self):
|
||||
result = func(self)
|
||||
if isinstance(result, unicode):
|
||||
return result.encode('utf-8')
|
||||
else:
|
||||
return result
|
||||
|
||||
if is_py3:
|
||||
return func
|
||||
else:
|
||||
return wrapper
|
@ -0,0 +1,458 @@
|
||||
"""
|
||||
The API basically only provides one class. You can create a :class:`Script` and
|
||||
use its methods.
|
||||
|
||||
Additionally you can add a debug function with :func:`set_debug_function`.
|
||||
Alternatively, if you don't need a custom function and are happy with printing
|
||||
debug messages to stdout, simply call :func:`set_debug_function` without
|
||||
arguments.
|
||||
|
||||
.. warning:: Please, note that Jedi is **not thread safe**.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
import parso
|
||||
from parso.python import tree
|
||||
from parso import python_bytes_to_unicode, split_lines
|
||||
|
||||
from jedi.parser_utils import get_executable_nodes, get_statement_of_position
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi import cache
|
||||
from jedi.api import classes
|
||||
from jedi.api import interpreter
|
||||
from jedi.api import usages
|
||||
from jedi.api import helpers
|
||||
from jedi.api.completion import Completion
|
||||
from jedi.evaluate import Evaluator
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.param import try_iter_content
|
||||
from jedi.evaluate.helpers import get_module_names, evaluate_call_of_leaf
|
||||
from jedi.evaluate.sys_path import get_venv_path, dotted_path_in_sys_path
|
||||
from jedi.evaluate.iterable import unpack_tuple_to_dict
|
||||
from jedi.evaluate.filters import TreeNameDefinition
|
||||
|
||||
# Jedi uses lots and lots of recursion. By setting this a little bit higher, we
|
||||
# can remove some "maximum recursion depth" errors.
|
||||
sys.setrecursionlimit(3000)
|
||||
|
||||
|
||||
class Script(object):
|
||||
"""
|
||||
A Script is the base for completions, goto or whatever you want to do with
|
||||
|jedi|.
|
||||
|
||||
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).
|
||||
:type line: int
|
||||
:param column: The column of the cursor (starting with 0).
|
||||
:type column: int
|
||||
:param path: The path of the file in the file system, or ``''`` if
|
||||
it hasn't been saved yet.
|
||||
:type path: str or None
|
||||
:param encoding: The encoding of ``source``, if it is not a
|
||||
``unicode`` object (default ``'utf-8'``).
|
||||
:type encoding: str
|
||||
: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', sys_path=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
|
||||
|
||||
if source is None:
|
||||
# TODO add a better warning than the traceback!
|
||||
with open(path, 'rb') as f:
|
||||
source = f.read()
|
||||
|
||||
# TODO do we really want that?
|
||||
self._source = python_bytes_to_unicode(source, encoding, errors='replace')
|
||||
self._code_lines = split_lines(self._source)
|
||||
line = max(len(self._code_lines), 1) if line is None else line
|
||||
if not (0 < line <= len(self._code_lines)):
|
||||
raise ValueError('`line` parameter is not in a valid range.')
|
||||
|
||||
line_len = len(self._code_lines[line - 1])
|
||||
column = line_len if column is None else column
|
||||
if not (0 <= column <= line_len):
|
||||
raise ValueError('`column` parameter is not in a valid range.')
|
||||
self._pos = line, column
|
||||
self._path = path
|
||||
|
||||
cache.clear_time_caches()
|
||||
debug.reset_time()
|
||||
|
||||
# Load the Python grammar of the current interpreter.
|
||||
self._grammar = parso.load_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')
|
||||
|
||||
@cache.memoize_method
|
||||
def _get_module_node(self):
|
||||
return self._grammar.parse(
|
||||
code=self._source,
|
||||
path=self.path,
|
||||
cache=False, # No disk cache, because the current script often changes.
|
||||
diff_cache=True,
|
||||
cache_path=settings.cache_directory
|
||||
)
|
||||
|
||||
@cache.memoize_method
|
||||
def _get_module(self):
|
||||
module = er.ModuleContext(
|
||||
self._evaluator,
|
||||
self._get_module_node(),
|
||||
self.path
|
||||
)
|
||||
if self.path is not None:
|
||||
name = dotted_path_in_sys_path(self._evaluator.sys_path, self.path)
|
||||
if name is not None:
|
||||
imports.add_module(self._evaluator, name, module)
|
||||
return module
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, repr(self._orig_path))
|
||||
|
||||
def completions(self):
|
||||
"""
|
||||
Return :class:`classes.Completion` objects. Those objects contain
|
||||
information about the completions, more than just names.
|
||||
|
||||
:return: Completion objects, sorted by name and __ comes last.
|
||||
:rtype: list of :class:`classes.Completion`
|
||||
"""
|
||||
debug.speed('completions start')
|
||||
completion = Completion(
|
||||
self._evaluator, self._get_module(), self._code_lines,
|
||||
self._pos, self.call_signatures
|
||||
)
|
||||
completions = completion.completions()
|
||||
debug.speed('completions end')
|
||||
return completions
|
||||
|
||||
def goto_definitions(self):
|
||||
"""
|
||||
Return the definitions of a the path under the cursor. goto function!
|
||||
This follows complicated paths and returns the end, not the first
|
||||
definition. The big difference between :meth:`goto_assignments` and
|
||||
:meth:`goto_definitions` is that :meth:`goto_assignments` doesn't
|
||||
follow imports and statements. Multiple objects may be returned,
|
||||
because Python itself is a dynamic language, which means depending on
|
||||
an option you can have two different versions of a function.
|
||||
|
||||
:rtype: list of :class:`classes.Definition`
|
||||
"""
|
||||
module_node = self._get_module_node()
|
||||
leaf = module_node.get_name_of_position(self._pos)
|
||||
if leaf is None:
|
||||
leaf = module_node.get_leaf_for_position(self._pos)
|
||||
if leaf is None:
|
||||
return []
|
||||
|
||||
context = self._evaluator.create_context(self._get_module(), leaf)
|
||||
definitions = helpers.evaluate_goto_definition(self._evaluator, context, leaf)
|
||||
|
||||
names = [s.name for s in definitions]
|
||||
defs = [classes.Definition(self._evaluator, name) for name in names]
|
||||
# The additional set here allows the definitions to become unique in an
|
||||
# API sense. In the internals we want to separate more things than in
|
||||
# the API.
|
||||
return helpers.sorted_definitions(set(defs))
|
||||
|
||||
def goto_assignments(self, follow_imports=False):
|
||||
"""
|
||||
Return the first definition found, while optionally following imports.
|
||||
Multiple objects may be returned, because Python itself is a
|
||||
dynamic language, which means depending on an option you can have two
|
||||
different versions of a function.
|
||||
|
||||
:rtype: list of :class:`classes.Definition`
|
||||
"""
|
||||
def filter_follow_imports(names, check):
|
||||
for name in names:
|
||||
if check(name):
|
||||
for result in filter_follow_imports(name.goto(), check):
|
||||
yield result
|
||||
else:
|
||||
yield name
|
||||
|
||||
names = self._goto()
|
||||
if follow_imports:
|
||||
def check(name):
|
||||
if isinstance(name, er.ModuleName):
|
||||
return False
|
||||
return name.api_type == 'module'
|
||||
else:
|
||||
def check(name):
|
||||
return isinstance(name, imports.SubModuleName)
|
||||
|
||||
names = filter_follow_imports(names, check)
|
||||
|
||||
defs = [classes.Definition(self._evaluator, d) for d in set(names)]
|
||||
return helpers.sorted_definitions(defs)
|
||||
|
||||
def _goto(self):
|
||||
"""
|
||||
Used for goto_assignments and usages.
|
||||
"""
|
||||
name = self._get_module_node().get_name_of_position(self._pos)
|
||||
if name is None:
|
||||
return []
|
||||
context = self._evaluator.create_context(self._get_module(), name)
|
||||
return list(self._evaluator.goto(context, name))
|
||||
|
||||
def usages(self, additional_module_paths=()):
|
||||
"""
|
||||
Return :class:`classes.Definition` objects, which contain all
|
||||
names that point to the definition of the name under the cursor. This
|
||||
is very useful for refactoring (renaming), or to show all usages of a
|
||||
variable.
|
||||
|
||||
.. todo:: Implement additional_module_paths
|
||||
|
||||
:rtype: list of :class:`classes.Definition`
|
||||
"""
|
||||
temp, settings.dynamic_flow_information = \
|
||||
settings.dynamic_flow_information, False
|
||||
try:
|
||||
module_node = self._get_module_node()
|
||||
user_stmt = get_statement_of_position(module_node, self._pos)
|
||||
definition_names = self._goto()
|
||||
if not definition_names and isinstance(user_stmt, tree.Import):
|
||||
# For not defined imports (goto doesn't find something, we take
|
||||
# the name as a definition. This is enough, because every name
|
||||
# points to it.
|
||||
name = user_stmt.get_name_of_position(self._pos)
|
||||
if name is None:
|
||||
# Must be syntax
|
||||
return []
|
||||
definition_names = [TreeNameDefinition(self._get_module(), name)]
|
||||
|
||||
if not definition_names:
|
||||
# Without a definition for a name we cannot find references.
|
||||
return []
|
||||
|
||||
definition_names = usages.resolve_potential_imports(self._evaluator,
|
||||
definition_names)
|
||||
|
||||
modules = set([d.get_root_context() for d in definition_names])
|
||||
modules.add(self._get_module())
|
||||
definitions = usages.usages(self._evaluator, definition_names, modules)
|
||||
finally:
|
||||
settings.dynamic_flow_information = temp
|
||||
|
||||
return helpers.sorted_definitions(set(definitions))
|
||||
|
||||
def call_signatures(self):
|
||||
"""
|
||||
Return the function object of the call you're currently in.
|
||||
|
||||
E.g. if the cursor is here::
|
||||
|
||||
abs(# <-- cursor is here
|
||||
|
||||
This would return the ``abs`` function. On the other hand::
|
||||
|
||||
abs()# <-- cursor is here
|
||||
|
||||
This would return an empty list..
|
||||
|
||||
:rtype: list of :class:`classes.CallSignature`
|
||||
"""
|
||||
call_signature_details = \
|
||||
helpers.get_call_signature_details(self._get_module_node(), self._pos)
|
||||
if call_signature_details is None:
|
||||
return []
|
||||
|
||||
context = self._evaluator.create_context(
|
||||
self._get_module(),
|
||||
call_signature_details.bracket_leaf
|
||||
)
|
||||
definitions = helpers.cache_call_signatures(
|
||||
self._evaluator,
|
||||
context,
|
||||
call_signature_details.bracket_leaf,
|
||||
self._code_lines,
|
||||
self._pos
|
||||
)
|
||||
debug.speed('func_call followed')
|
||||
|
||||
return [classes.CallSignature(self._evaluator, d.name,
|
||||
call_signature_details.bracket_leaf.start_pos,
|
||||
call_signature_details.call_index,
|
||||
call_signature_details.keyword_name_str)
|
||||
for d in definitions if hasattr(d, 'py__call__')]
|
||||
|
||||
def _analysis(self):
|
||||
self._evaluator.is_analysis = True
|
||||
module_node = self._get_module_node()
|
||||
self._evaluator.analysis_modules = [module_node]
|
||||
try:
|
||||
for node in get_executable_nodes(module_node):
|
||||
context = self._get_module().create_context(node)
|
||||
if node.type in ('funcdef', 'classdef'):
|
||||
# TODO This is stupid, should be private
|
||||
from jedi.evaluate.finder import _name_to_types
|
||||
# Resolve the decorators.
|
||||
_name_to_types(self._evaluator, context, node.children[1])
|
||||
elif isinstance(node, tree.Import):
|
||||
import_names = set(node.get_defined_names())
|
||||
if node.is_nested():
|
||||
import_names |= set(path[-1] for path in node.get_paths())
|
||||
for n in import_names:
|
||||
imports.infer_import(context, n)
|
||||
elif node.type == 'expr_stmt':
|
||||
types = context.eval_node(node)
|
||||
for testlist in node.children[:-1:2]:
|
||||
# Iterate tuples.
|
||||
unpack_tuple_to_dict(context, types, testlist)
|
||||
else:
|
||||
if node.type == 'name':
|
||||
defs = self._evaluator.goto_definitions(context, node)
|
||||
else:
|
||||
defs = evaluate_call_of_leaf(context, node)
|
||||
try_iter_content(defs)
|
||||
self._evaluator.reset_recursion_limitations()
|
||||
|
||||
ana = [a for a in self._evaluator.analysis if self.path == a.path]
|
||||
return sorted(set(ana), key=lambda x: x.line)
|
||||
finally:
|
||||
self._evaluator.is_analysis = False
|
||||
|
||||
|
||||
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.completions()[0].name)
|
||||
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`.
|
||||
"""
|
||||
try:
|
||||
namespaces = [dict(n) for n in namespaces]
|
||||
except Exception:
|
||||
raise TypeError("namespaces must be a non-empty list of dicts.")
|
||||
|
||||
super(Interpreter, self).__init__(source, **kwds)
|
||||
self.namespaces = namespaces
|
||||
|
||||
def _get_module(self):
|
||||
parser_module = super(Interpreter, self)._get_module_node()
|
||||
return interpreter.MixedModuleContext(
|
||||
self._evaluator,
|
||||
parser_module,
|
||||
self.namespaces,
|
||||
path=self.path
|
||||
)
|
||||
|
||||
|
||||
def names(source=None, path=None, encoding='utf-8', all_scopes=False,
|
||||
definitions=True, references=False):
|
||||
"""
|
||||
Returns a list of `Definition` objects, containing name parts.
|
||||
This means you can call ``Definition.goto_assignments()`` and get the
|
||||
reference of a name.
|
||||
The parameters are the same as in :py:class:`Script`, except or the
|
||||
following ones:
|
||||
|
||||
:param all_scopes: If True lists the names of all scopes instead of only
|
||||
the module namespace.
|
||||
:param definitions: If True lists the names that have been defined by a
|
||||
class, function or a statement (``a = b`` returns ``a``).
|
||||
:param references: If True lists all the names that are not listed by
|
||||
``definitions=True``. E.g. ``a = b`` returns ``b``.
|
||||
"""
|
||||
def def_ref_filter(_def):
|
||||
is_def = _def._name.tree_name.is_definition()
|
||||
return definitions and is_def or references and not is_def
|
||||
|
||||
# Set line/column to a random position, because they don't matter.
|
||||
script = Script(source, line=1, column=0, path=path, encoding=encoding)
|
||||
module_context = script._get_module()
|
||||
defs = [
|
||||
classes.Definition(
|
||||
script._evaluator,
|
||||
TreeNameDefinition(
|
||||
module_context.create_context(name if name.parent.type == 'file_input' else name.parent),
|
||||
name
|
||||
)
|
||||
) for name in get_module_names(script._get_module_node(), all_scopes)
|
||||
]
|
||||
return sorted(filter(def_ref_filter, defs), key=lambda x: (x.line, x.column))
|
||||
|
||||
|
||||
def preload_module(*modules):
|
||||
"""
|
||||
Preloading modules tells Jedi to load a module now, instead of lazy parsing
|
||||
of modules. Usful for IDEs, to control which modules to load on startup.
|
||||
|
||||
:param modules: different module names, list of string.
|
||||
"""
|
||||
for m in modules:
|
||||
s = "import %s as x; x." % m
|
||||
Script(s, 1, len(s), None).completions()
|
||||
|
||||
|
||||
def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
|
||||
notices=True, speed=True):
|
||||
"""
|
||||
Define a callback debug function to get all the debug messages.
|
||||
|
||||
If you don't specify any arguments, debug messages will be printed to stdout.
|
||||
|
||||
:param func_cb: The callback function for debug messages, with n params.
|
||||
"""
|
||||
debug.debug_function = func_cb
|
||||
debug.enable_warning = warnings
|
||||
debug.enable_notice = notices
|
||||
debug.enable_speed = speed
|
@ -0,0 +1,678 @@
|
||||
"""
|
||||
The :mod:`jedi.api.classes` module contains the return classes of the API.
|
||||
These classes are the much bigger part of the whole API, because they contain
|
||||
the interesting information about completion and goto operations.
|
||||
"""
|
||||
import re
|
||||
|
||||
from parso.cache import parser_cache
|
||||
from parso.python.tree import search_ancestor
|
||||
|
||||
from jedi._compatibility import u
|
||||
from jedi import settings
|
||||
from jedi import common
|
||||
from jedi.cache import memoize_method
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate import instance
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.filters import ParamName
|
||||
from jedi.evaluate.imports import ImportName
|
||||
from jedi.api.keywords import KeywordName
|
||||
|
||||
|
||||
def _sort_names_by_start_pos(names):
|
||||
return sorted(names, key=lambda s: s.start_pos or (0, 0))
|
||||
|
||||
|
||||
def defined_names(evaluator, context):
|
||||
"""
|
||||
List sub-definitions (e.g., methods in class).
|
||||
|
||||
:type scope: Scope
|
||||
:rtype: list of Definition
|
||||
"""
|
||||
filter = next(context.get_filters(search_global=True))
|
||||
names = [name for name in filter.values()]
|
||||
return [Definition(evaluator, n) for n in _sort_names_by_start_pos(names)]
|
||||
|
||||
|
||||
class BaseDefinition(object):
|
||||
_mapping = {
|
||||
'posixpath': 'os.path',
|
||||
'riscospath': 'os.path',
|
||||
'ntpath': 'os.path',
|
||||
'os2emxpath': 'os.path',
|
||||
'macpath': 'os.path',
|
||||
'genericpath': 'os.path',
|
||||
'posix': 'os',
|
||||
'_io': 'io',
|
||||
'_functools': 'functools',
|
||||
'_sqlite3': 'sqlite3',
|
||||
'__builtin__': '',
|
||||
'builtins': '',
|
||||
}
|
||||
|
||||
_tuple_mapping = dict((tuple(k.split('.')), v) for (k, v) in {
|
||||
'argparse._ActionsContainer': 'argparse.ArgumentParser',
|
||||
}.items())
|
||||
|
||||
def __init__(self, evaluator, name):
|
||||
self._evaluator = evaluator
|
||||
self._name = name
|
||||
"""
|
||||
An instance of :class:`parso.reprsentation.Name` subclass.
|
||||
"""
|
||||
self.is_keyword = isinstance(self._name, KeywordName)
|
||||
|
||||
# generate a path to the definition
|
||||
self._module = name.get_root_context()
|
||||
if self.in_builtin_module():
|
||||
self.module_path = None
|
||||
else:
|
||||
self.module_path = self._module.py__file__()
|
||||
"""Shows the file path of a module. e.g. ``/usr/lib/python2.7/os.py``"""
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Name of variable/function/class/module.
|
||||
|
||||
For example, for ``x = None`` it returns ``'x'``.
|
||||
|
||||
:rtype: str or None
|
||||
"""
|
||||
return self._name.string_name
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""
|
||||
The type of the definition.
|
||||
|
||||
Here is an example of the value of this attribute. Let's consider
|
||||
the following source. As what is in ``variable`` is unambiguous
|
||||
to Jedi, :meth:`jedi.Script.goto_definitions` should return a list of
|
||||
definition for ``sys``, ``f``, ``C`` and ``x``.
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... import keyword
|
||||
...
|
||||
... class C:
|
||||
... pass
|
||||
...
|
||||
... class D:
|
||||
... pass
|
||||
...
|
||||
... x = D()
|
||||
...
|
||||
... def f():
|
||||
... pass
|
||||
...
|
||||
... for variable in [keyword, f, C, x]:
|
||||
... variable'''
|
||||
|
||||
>>> script = Script(source)
|
||||
>>> defs = script.goto_definitions()
|
||||
|
||||
Before showing what is in ``defs``, let's sort it by :attr:`line`
|
||||
so that it is easy to relate the result to the source code.
|
||||
|
||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||
>>> defs # doctest: +NORMALIZE_WHITESPACE
|
||||
[<Definition module keyword>, <Definition class C>,
|
||||
<Definition instance D>, <Definition def f>]
|
||||
|
||||
Finally, here is what you can get from :attr:`type`:
|
||||
|
||||
>>> defs[0].type
|
||||
'module'
|
||||
>>> defs[1].type
|
||||
'class'
|
||||
>>> defs[2].type
|
||||
'instance'
|
||||
>>> defs[3].type
|
||||
'function'
|
||||
|
||||
"""
|
||||
tree_name = self._name.tree_name
|
||||
resolve = False
|
||||
if tree_name is not None:
|
||||
# TODO move this to their respective names.
|
||||
definition = tree_name.get_definition()
|
||||
if definition is not None and definition.type == 'import_from' and \
|
||||
tree_name.is_definition():
|
||||
resolve = True
|
||||
|
||||
if isinstance(self._name, imports.SubModuleName) or resolve:
|
||||
for context in self._name.infer():
|
||||
return context.api_type
|
||||
return self._name.api_type
|
||||
|
||||
def _path(self):
|
||||
"""The path to a module/class/function definition."""
|
||||
def to_reverse():
|
||||
name = self._name
|
||||
if name.api_type == 'module':
|
||||
try:
|
||||
name = list(name.infer())[0].name
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if name.api_type == 'module':
|
||||
module_contexts = name.infer()
|
||||
if module_contexts:
|
||||
module_context, = module_contexts
|
||||
for n in reversed(module_context.py__name__().split('.')):
|
||||
yield n
|
||||
else:
|
||||
# We don't really know anything about the path here. This
|
||||
# module is just an import that would lead in an
|
||||
# ImportError. So simply return the name.
|
||||
yield name.string_name
|
||||
return
|
||||
else:
|
||||
yield name.string_name
|
||||
|
||||
parent_context = name.parent_context
|
||||
while parent_context is not None:
|
||||
try:
|
||||
method = parent_context.py__name__
|
||||
except AttributeError:
|
||||
try:
|
||||
yield parent_context.name.string_name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for name in reversed(method().split('.')):
|
||||
yield name
|
||||
parent_context = parent_context.parent_context
|
||||
return reversed(list(to_reverse()))
|
||||
|
||||
@property
|
||||
def module_name(self):
|
||||
"""
|
||||
The module name.
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = 'import json'
|
||||
>>> script = Script(source, path='example.py')
|
||||
>>> d = script.goto_definitions()[0]
|
||||
>>> print(d.module_name) # doctest: +ELLIPSIS
|
||||
json
|
||||
"""
|
||||
return self._module.name.string_name
|
||||
|
||||
def in_builtin_module(self):
|
||||
"""Whether this is a builtin module."""
|
||||
return isinstance(self._module, compiled.CompiledObject)
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
"""The line where the definition occurs (starting with 1)."""
|
||||
start_pos = self._name.start_pos
|
||||
if start_pos is None:
|
||||
return None
|
||||
return start_pos[0]
|
||||
|
||||
@property
|
||||
def column(self):
|
||||
"""The column where the definition occurs (starting with 0)."""
|
||||
start_pos = self._name.start_pos
|
||||
if start_pos is None:
|
||||
return None
|
||||
return start_pos[1]
|
||||
|
||||
def docstring(self, raw=False, fast=True):
|
||||
r"""
|
||||
Return a document string for this completion object.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''\
|
||||
... def f(a, b=1):
|
||||
... "Document for function f."
|
||||
... '''
|
||||
>>> script = Script(source, 1, len('def f'), 'example.py')
|
||||
>>> doc = script.goto_definitions()[0].docstring()
|
||||
>>> print(doc)
|
||||
f(a, b=1)
|
||||
<BLANKLINE>
|
||||
Document for function f.
|
||||
|
||||
Notice that useful extra information is added to the actual
|
||||
docstring. For function, it is call signature. If you need
|
||||
actual docstring, use ``raw=True`` instead.
|
||||
|
||||
>>> print(script.goto_definitions()[0].docstring(raw=True))
|
||||
Document for function f.
|
||||
|
||||
:param fast: Don't follow imports that are only one level deep like
|
||||
``import foo``, but follow ``from foo import bar``. This makes
|
||||
sense for speed reasons. Completing `import a` is slow if you use
|
||||
the ``foo.docstring(fast=False)`` on every object, because it
|
||||
parses all libraries starting with ``a``.
|
||||
"""
|
||||
return _Help(self._name).docstring(fast=fast, raw=raw)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""A textual description of the object."""
|
||||
return u(self._name.string_name)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
"""
|
||||
Dot-separated path of this object.
|
||||
|
||||
It is in the form of ``<module>[.<submodule>[...]][.<object>]``.
|
||||
It is useful when you want to look up Python manual of the
|
||||
object at hand.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... import os
|
||||
... os.path.join'''
|
||||
>>> script = Script(source, 3, len('os.path.join'), 'example.py')
|
||||
>>> print(script.goto_definitions()[0].full_name)
|
||||
os.path.join
|
||||
|
||||
Notice that it returns ``'os.path.join'`` instead of (for example)
|
||||
``'posixpath.join'``. This is not correct, since the modules name would
|
||||
be ``<module 'posixpath' ...>```. However most users find the latter
|
||||
more practical.
|
||||
"""
|
||||
path = list(self._path())
|
||||
# TODO add further checks, the mapping should only occur on stdlib.
|
||||
if not path:
|
||||
return None # for keywords the path is empty
|
||||
|
||||
with common.ignored(KeyError):
|
||||
path[0] = self._mapping[path[0]]
|
||||
for key, repl in self._tuple_mapping.items():
|
||||
if tuple(path[:len(key)]) == key:
|
||||
path = [repl] + path[len(key):]
|
||||
|
||||
return '.'.join(path if path[0] else path[1:])
|
||||
|
||||
def goto_assignments(self):
|
||||
if self._name.tree_name is None:
|
||||
return self
|
||||
|
||||
names = self._evaluator.goto(self._name.parent_context, self._name.tree_name)
|
||||
return [Definition(self._evaluator, n) for n in names]
|
||||
|
||||
def _goto_definitions(self):
|
||||
# TODO make this function public.
|
||||
return [Definition(self._evaluator, d.name) for d in self._name.infer()]
|
||||
|
||||
@property
|
||||
@memoize_method
|
||||
def params(self):
|
||||
"""
|
||||
Raises an ``AttributeError``if the definition is not callable.
|
||||
Otherwise returns a list of `Definition` that represents the params.
|
||||
"""
|
||||
def get_param_names(context):
|
||||
param_names = []
|
||||
if context.api_type == 'function':
|
||||
param_names = list(context.get_param_names())
|
||||
if isinstance(context, instance.BoundMethod):
|
||||
param_names = param_names[1:]
|
||||
elif isinstance(context, (instance.AbstractInstanceContext, er.ClassContext)):
|
||||
if isinstance(context, er.ClassContext):
|
||||
search = '__init__'
|
||||
else:
|
||||
search = '__call__'
|
||||
names = context.get_function_slot_names(search)
|
||||
if not names:
|
||||
return []
|
||||
|
||||
# Just take the first one here, not optimal, but currently
|
||||
# there's no better solution.
|
||||
inferred = names[0].infer()
|
||||
param_names = get_param_names(next(iter(inferred)))
|
||||
if isinstance(context, er.ClassContext):
|
||||
param_names = param_names[1:]
|
||||
return param_names
|
||||
elif isinstance(context, compiled.CompiledObject):
|
||||
return list(context.get_param_names())
|
||||
return param_names
|
||||
|
||||
followed = list(self._name.infer())
|
||||
if not followed or not hasattr(followed[0], 'py__call__'):
|
||||
raise AttributeError()
|
||||
context = followed[0] # only check the first one.
|
||||
|
||||
return [Definition(self._evaluator, n) for n in get_param_names(context)]
|
||||
|
||||
def parent(self):
|
||||
context = self._name.parent_context
|
||||
if context is None:
|
||||
return None
|
||||
|
||||
if isinstance(context, er.FunctionExecutionContext):
|
||||
# TODO the function context should be a part of the function
|
||||
# execution context.
|
||||
context = er.FunctionContext(
|
||||
self._evaluator, context.parent_context, context.tree_node)
|
||||
return Definition(self._evaluator, context.name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s %s>" % (type(self).__name__, self.description)
|
||||
|
||||
def get_line_code(self, before=0, after=0):
|
||||
"""
|
||||
Returns the line of code where this object was defined.
|
||||
|
||||
:param before: Add n lines before the current line to the output.
|
||||
:param after: Add n lines after the current line to the output.
|
||||
|
||||
:return str: Returns the line(s) of code or an empty string if it's a
|
||||
builtin.
|
||||
"""
|
||||
if self.in_builtin_module():
|
||||
return ''
|
||||
|
||||
path = self._name.get_root_context().py__file__()
|
||||
lines = parser_cache[self._evaluator.grammar._hashed][path].lines
|
||||
|
||||
index = self._name.start_pos[0] - 1
|
||||
start_index = max(index - before, 0)
|
||||
return ''.join(lines[start_index:index + after + 1])
|
||||
|
||||
|
||||
class Completion(BaseDefinition):
|
||||
"""
|
||||
`Completion` objects are returned from :meth:`api.Script.completions`. They
|
||||
provide additional information about a completion.
|
||||
"""
|
||||
def __init__(self, evaluator, name, stack, like_name_length):
|
||||
super(Completion, self).__init__(evaluator, name)
|
||||
|
||||
self._like_name_length = like_name_length
|
||||
self._stack = stack
|
||||
|
||||
# Completion objects with the same Completion name (which means
|
||||
# duplicate items in the completion)
|
||||
self._same_name_completions = []
|
||||
|
||||
def _complete(self, like_name):
|
||||
append = ''
|
||||
if settings.add_bracket_after_function \
|
||||
and self.type == 'Function':
|
||||
append = '('
|
||||
|
||||
if isinstance(self._name, ParamName) and self._stack is not None:
|
||||
node_names = list(self._stack.get_node_names(self._evaluator.grammar._pgen_grammar))
|
||||
if 'trailer' in node_names and 'argument' not in node_names:
|
||||
append += '='
|
||||
|
||||
name = self._name.string_name
|
||||
if like_name:
|
||||
name = name[self._like_name_length:]
|
||||
return name + append
|
||||
|
||||
@property
|
||||
def complete(self):
|
||||
"""
|
||||
Return the rest of the word, e.g. completing ``isinstance``::
|
||||
|
||||
isinstan# <-- Cursor is here
|
||||
|
||||
would return the string 'ce'. It also adds additional stuff, depending
|
||||
on your `settings.py`.
|
||||
|
||||
Assuming the following function definition::
|
||||
|
||||
def foo(param=0):
|
||||
pass
|
||||
|
||||
completing ``foo(par`` would give a ``Completion`` which `complete`
|
||||
would be `am=`
|
||||
|
||||
|
||||
"""
|
||||
return self._complete(True)
|
||||
|
||||
@property
|
||||
def name_with_symbols(self):
|
||||
"""
|
||||
Similar to :attr:`name`, but like :attr:`name` returns also the
|
||||
symbols, for example assuming the following function definition::
|
||||
|
||||
def foo(param=0):
|
||||
pass
|
||||
|
||||
completing ``foo(`` would give a ``Completion`` which
|
||||
``name_with_symbols`` would be "param=".
|
||||
|
||||
"""
|
||||
return self._complete(False)
|
||||
|
||||
def docstring(self, raw=False, fast=True):
|
||||
if self._like_name_length >= 3:
|
||||
# In this case we can just resolve the like name, because we
|
||||
# wouldn't load like > 100 Python modules anymore.
|
||||
fast = False
|
||||
return super(Completion, self).docstring(raw=raw, fast=fast)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""Provide a description of the completion object."""
|
||||
# TODO improve the class structure.
|
||||
return Definition.description.__get__(self)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self._name.string_name)
|
||||
|
||||
@memoize_method
|
||||
def follow_definition(self):
|
||||
"""
|
||||
Return the original definitions. I strongly recommend not using it for
|
||||
your completions, because it might slow down |jedi|. If you want to
|
||||
read only a few objects (<=20), it might be useful, especially to get
|
||||
the original docstrings. The basic problem of this function is that it
|
||||
follows all results. This means with 1000 completions (e.g. numpy),
|
||||
it's just PITA-slow.
|
||||
"""
|
||||
defs = self._name.infer()
|
||||
return [Definition(self._evaluator, d.name) for d in defs]
|
||||
|
||||
|
||||
class Definition(BaseDefinition):
|
||||
"""
|
||||
*Definition* objects are returned from :meth:`api.Script.goto_assignments`
|
||||
or :meth:`api.Script.goto_definitions`.
|
||||
"""
|
||||
def __init__(self, evaluator, definition):
|
||||
super(Definition, self).__init__(evaluator, definition)
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""
|
||||
A description of the :class:`.Definition` object, which is heavily used
|
||||
in testing. e.g. for ``isinstance`` it returns ``def isinstance``.
|
||||
|
||||
Example:
|
||||
|
||||
>>> from jedi import Script
|
||||
>>> source = '''
|
||||
... def f():
|
||||
... pass
|
||||
...
|
||||
... class C:
|
||||
... pass
|
||||
...
|
||||
... variable = f if random.choice([0,1]) else C'''
|
||||
>>> script = Script(source, column=3) # line is maximum by default
|
||||
>>> defs = script.goto_definitions()
|
||||
>>> defs = sorted(defs, key=lambda d: d.line)
|
||||
>>> defs
|
||||
[<Definition def f>, <Definition class C>]
|
||||
>>> str(defs[0].description) # strip literals in python2
|
||||
'def f'
|
||||
>>> str(defs[1].description)
|
||||
'class C'
|
||||
|
||||
"""
|
||||
typ = self.type
|
||||
tree_name = self._name.tree_name
|
||||
if typ in ('function', 'class', 'module', 'instance') or tree_name is None:
|
||||
if typ == 'function':
|
||||
# For the description we want a short and a pythonic way.
|
||||
typ = 'def'
|
||||
return typ + ' ' + u(self._name.string_name)
|
||||
elif typ == 'param':
|
||||
code = search_ancestor(tree_name, 'param').get_code(
|
||||
include_prefix=False,
|
||||
include_comma=False
|
||||
)
|
||||
return typ + ' ' + code
|
||||
|
||||
|
||||
definition = tree_name.get_definition() or tree_name
|
||||
# Remove the prefix, because that's not what we want for get_code
|
||||
# here.
|
||||
txt = definition.get_code(include_prefix=False)
|
||||
# Delete comments:
|
||||
txt = re.sub('#[^\n]+\n', ' ', txt)
|
||||
# Delete multi spaces/newlines
|
||||
txt = re.sub('\s+', ' ', txt).strip()
|
||||
return txt
|
||||
|
||||
@property
|
||||
def desc_with_module(self):
|
||||
"""
|
||||
In addition to the definition, also return the module.
|
||||
|
||||
.. warning:: Don't use this function yet, its behaviour may change. If
|
||||
you really need it, talk to me.
|
||||
|
||||
.. todo:: Add full path. This function is should return a
|
||||
`module.class.function` path.
|
||||
"""
|
||||
position = '' if self.in_builtin_module else '@%s' % (self.line)
|
||||
return "%s:%s%s" % (self.module_name, self.description, position)
|
||||
|
||||
@memoize_method
|
||||
def defined_names(self):
|
||||
"""
|
||||
List sub-definitions (e.g., methods in class).
|
||||
|
||||
:rtype: list of Definition
|
||||
"""
|
||||
defs = self._name.infer()
|
||||
return sorted(
|
||||
common.unite(defined_names(self._evaluator, d) for d in defs),
|
||||
key=lambda s: s._name.start_pos or (0, 0)
|
||||
)
|
||||
|
||||
def is_definition(self):
|
||||
"""
|
||||
Returns True, if defined as a name in a statement, function or class.
|
||||
Returns False, if it's a reference to such a definition.
|
||||
"""
|
||||
if self._name.tree_name is None:
|
||||
return True
|
||||
else:
|
||||
return self._name.tree_name.is_definition()
|
||||
|
||||
def __eq__(self, other):
|
||||
return self._name.start_pos == other._name.start_pos \
|
||||
and self.module_path == other.module_path \
|
||||
and self.name == other.name \
|
||||
and self._evaluator == other._evaluator
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self._name.start_pos, self.module_path, self.name, self._evaluator))
|
||||
|
||||
|
||||
class CallSignature(Definition):
|
||||
"""
|
||||
`CallSignature` objects is the return value of `Script.function_definition`.
|
||||
It knows what functions you are currently in. e.g. `isinstance(` would
|
||||
return the `isinstance` function. without `(` it would return nothing.
|
||||
"""
|
||||
def __init__(self, evaluator, executable_name, bracket_start_pos, index, key_name_str):
|
||||
super(CallSignature, self).__init__(evaluator, executable_name)
|
||||
self._index = index
|
||||
self._key_name_str = key_name_str
|
||||
self._bracket_start_pos = bracket_start_pos
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
"""
|
||||
The Param index of the current call.
|
||||
Returns None if the index cannot be found in the curent call.
|
||||
"""
|
||||
if self._key_name_str is not None:
|
||||
for i, param in enumerate(self.params):
|
||||
if self._key_name_str == param.name:
|
||||
return i
|
||||
if self.params:
|
||||
param_name = self.params[-1]._name
|
||||
if param_name.tree_name is not None:
|
||||
if param_name.tree_name.get_definition().star_count == 2:
|
||||
return i
|
||||
return None
|
||||
|
||||
if self._index >= len(self.params):
|
||||
for i, param in enumerate(self.params):
|
||||
tree_name = param._name.tree_name
|
||||
if tree_name is not None:
|
||||
# *args case
|
||||
if tree_name.get_definition().star_count == 1:
|
||||
return i
|
||||
return None
|
||||
return self._index
|
||||
|
||||
@property
|
||||
def bracket_start(self):
|
||||
"""
|
||||
The indent of the bracket that is responsible for the last function
|
||||
call.
|
||||
"""
|
||||
return self._bracket_start_pos
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s index %s>' % \
|
||||
(type(self).__name__, self._name.string_name, self.index)
|
||||
|
||||
|
||||
class _Help(object):
|
||||
"""
|
||||
Temporary implementation, will be used as `Script.help() or something in
|
||||
the future.
|
||||
"""
|
||||
def __init__(self, definition):
|
||||
self._name = definition
|
||||
|
||||
@memoize_method
|
||||
def _get_contexts(self, fast):
|
||||
if isinstance(self._name, ImportName) and fast:
|
||||
return {}
|
||||
|
||||
if self._name.api_type == 'statement':
|
||||
return {}
|
||||
|
||||
return self._name.infer()
|
||||
|
||||
def docstring(self, fast=True, raw=True):
|
||||
"""
|
||||
The docstring ``__doc__`` for any object.
|
||||
|
||||
See :attr:`doc` for example.
|
||||
"""
|
||||
# TODO: Use all of the followed objects as output. Possibly divinding
|
||||
# them by a few dashes.
|
||||
for context in self._get_contexts(fast=fast):
|
||||
return context.py__doc__(include_call_signature=not raw)
|
||||
|
||||
return ''
|
@ -0,0 +1,291 @@
|
||||
from parso.python import token
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor, Leaf
|
||||
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.api import classes
|
||||
from jedi.api import helpers
|
||||
from jedi.evaluate import imports
|
||||
from jedi.api import keywords
|
||||
from jedi.evaluate.helpers import evaluate_call_of_leaf
|
||||
from jedi.evaluate.filters import get_global_filters
|
||||
from jedi.parser_utils import get_statement_of_position
|
||||
|
||||
|
||||
def get_call_signature_param_names(call_signatures):
|
||||
# add named params
|
||||
for call_sig in call_signatures:
|
||||
for p in call_sig.params:
|
||||
# Allow protected access, because it's a public API.
|
||||
tree_name = p._name.tree_name
|
||||
# Compiled modules typically don't allow keyword arguments.
|
||||
if tree_name is not None:
|
||||
# Allow access on _definition here, because it's a
|
||||
# public API and we don't want to make the internal
|
||||
# Name object public.
|
||||
tree_param = tree.search_ancestor(tree_name, 'param')
|
||||
if tree_param.star_count == 0: # no *args/**kwargs
|
||||
yield p._name
|
||||
|
||||
|
||||
def filter_names(evaluator, completion_names, stack, like_name):
|
||||
comp_dct = {}
|
||||
for name in completion_names:
|
||||
if settings.case_insensitive_completion \
|
||||
and name.string_name.lower().startswith(like_name.lower()) \
|
||||
or name.string_name.startswith(like_name):
|
||||
|
||||
new = classes.Completion(
|
||||
evaluator,
|
||||
name,
|
||||
stack,
|
||||
len(like_name)
|
||||
)
|
||||
k = (new.name, new.complete) # key
|
||||
if k in comp_dct and settings.no_completion_duplicates:
|
||||
comp_dct[k]._same_name_completions.append(new)
|
||||
else:
|
||||
comp_dct[k] = new
|
||||
yield new
|
||||
|
||||
|
||||
def get_user_scope(module_context, position):
|
||||
"""
|
||||
Returns the scope in which the user resides. This includes flows.
|
||||
"""
|
||||
user_stmt = get_statement_of_position(module_context.tree_node, position)
|
||||
if user_stmt is None:
|
||||
def scan(scope):
|
||||
for s in scope.children:
|
||||
if s.start_pos <= position <= s.end_pos:
|
||||
if isinstance(s, (tree.Scope, tree.Flow)):
|
||||
return scan(s) or s
|
||||
elif s.type in ('suite', 'decorated'):
|
||||
return scan(s)
|
||||
return None
|
||||
|
||||
scanned_node = scan(module_context.tree_node)
|
||||
if scanned_node:
|
||||
return module_context.create_context(scanned_node, node_is_context=True)
|
||||
return module_context
|
||||
else:
|
||||
return module_context.create_context(user_stmt)
|
||||
|
||||
|
||||
def get_flow_scope_node(module_node, position):
|
||||
node = module_node.get_leaf_for_position(position, include_prefixes=True)
|
||||
while not isinstance(node, (tree.Scope, tree.Flow)):
|
||||
node = node.parent
|
||||
|
||||
return node
|
||||
|
||||
|
||||
class Completion:
|
||||
def __init__(self, evaluator, module, code_lines, position, call_signatures_method):
|
||||
self._evaluator = evaluator
|
||||
self._module_context = module
|
||||
self._module_node = module.tree_node
|
||||
self._code_lines = code_lines
|
||||
|
||||
# The first step of completions is to get the name
|
||||
self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
|
||||
# The actual cursor position is not what we need to calculate
|
||||
# everything. We want the start of the name we're on.
|
||||
self._position = position[0], position[1] - len(self._like_name)
|
||||
self._call_signatures_method = call_signatures_method
|
||||
|
||||
def completions(self):
|
||||
completion_names = self._get_context_completions()
|
||||
|
||||
completions = filter_names(self._evaluator, completion_names,
|
||||
self.stack, self._like_name)
|
||||
|
||||
return sorted(completions, key=lambda x: (x.name.startswith('__'),
|
||||
x.name.startswith('_'),
|
||||
x.name.lower()))
|
||||
|
||||
def _get_context_completions(self):
|
||||
"""
|
||||
Analyzes the context that a completion is made in and decides what to
|
||||
return.
|
||||
|
||||
Technically this works by generating a parser stack and analysing the
|
||||
current stack for possible grammar nodes.
|
||||
|
||||
Possible enhancements:
|
||||
- global/nonlocal search global
|
||||
- yield from / raise from <- could be only exceptions/generators
|
||||
- In args: */**: no completion
|
||||
- In params (also lambda): no completion before =
|
||||
"""
|
||||
|
||||
grammar = self._evaluator.grammar
|
||||
|
||||
try:
|
||||
self.stack = helpers.get_stack_at_position(
|
||||
grammar, self._code_lines, self._module_node, self._position
|
||||
)
|
||||
except helpers.OnErrorLeaf as e:
|
||||
self.stack = None
|
||||
if e.error_leaf.value == '.':
|
||||
# After ErrorLeaf's that are dots, we will not do any
|
||||
# completions since this probably just confuses the user.
|
||||
return []
|
||||
# If we don't have a context, just use global completion.
|
||||
|
||||
return self._global_completions()
|
||||
|
||||
allowed_keywords, allowed_tokens = \
|
||||
helpers.get_possible_completion_types(grammar._pgen_grammar, self.stack)
|
||||
|
||||
if 'if' in allowed_keywords:
|
||||
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
|
||||
previous_leaf = leaf.get_previous_leaf()
|
||||
|
||||
indent = self._position[1]
|
||||
if not (leaf.start_pos <= self._position <= leaf.end_pos):
|
||||
indent = leaf.start_pos[1]
|
||||
|
||||
if previous_leaf is not None:
|
||||
stmt = previous_leaf
|
||||
while True:
|
||||
stmt = search_ancestor(
|
||||
stmt, 'if_stmt', 'for_stmt', 'while_stmt', 'try_stmt',
|
||||
'error_node',
|
||||
)
|
||||
if stmt is None:
|
||||
break
|
||||
|
||||
type_ = stmt.type
|
||||
if type_ == 'error_node':
|
||||
first = stmt.children[0]
|
||||
if isinstance(first, Leaf):
|
||||
type_ = first.value + '_stmt'
|
||||
# Compare indents
|
||||
if stmt.start_pos[1] == indent:
|
||||
if type_ == 'if_stmt':
|
||||
allowed_keywords += ['elif', 'else']
|
||||
elif type_ == 'try_stmt':
|
||||
allowed_keywords += ['except', 'finally', 'else']
|
||||
elif type_ == 'for_stmt':
|
||||
allowed_keywords.append('else')
|
||||
|
||||
completion_names = list(self._get_keyword_completion_names(allowed_keywords))
|
||||
|
||||
if token.NAME in allowed_tokens or token.INDENT in allowed_tokens:
|
||||
# This means that we actually have to do type inference.
|
||||
|
||||
symbol_names = list(self.stack.get_node_names(grammar._pgen_grammar))
|
||||
|
||||
nodes = list(self.stack.get_nodes())
|
||||
|
||||
if nodes and nodes[-1] in ('as', 'def', 'class'):
|
||||
# No completions for ``with x as foo`` and ``import x as foo``.
|
||||
# Also true for defining names as a class or function.
|
||||
return list(self._get_class_context_completions(is_function=True))
|
||||
elif "import_stmt" in symbol_names:
|
||||
level, names = self._parse_dotted_names(nodes, "import_from" in symbol_names)
|
||||
|
||||
only_modules = not ("import_from" in symbol_names and 'import' in nodes)
|
||||
completion_names += self._get_importer_names(
|
||||
names,
|
||||
level,
|
||||
only_modules=only_modules,
|
||||
)
|
||||
elif symbol_names[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
|
||||
dot = self._module_node.get_leaf_for_position(self._position)
|
||||
completion_names += self._trailer_completions(dot.get_previous_leaf())
|
||||
else:
|
||||
completion_names += self._global_completions()
|
||||
completion_names += self._get_class_context_completions(is_function=False)
|
||||
|
||||
if 'trailer' in symbol_names:
|
||||
call_signatures = self._call_signatures_method()
|
||||
completion_names += get_call_signature_param_names(call_signatures)
|
||||
|
||||
return completion_names
|
||||
|
||||
def _get_keyword_completion_names(self, keywords_):
|
||||
for k in keywords_:
|
||||
yield keywords.keyword(self._evaluator, k).name
|
||||
|
||||
def _global_completions(self):
|
||||
context = get_user_scope(self._module_context, self._position)
|
||||
debug.dbg('global completion scope: %s', context)
|
||||
flow_scope_node = get_flow_scope_node(self._module_node, self._position)
|
||||
filters = get_global_filters(
|
||||
self._evaluator,
|
||||
context,
|
||||
self._position,
|
||||
origin_scope=flow_scope_node
|
||||
)
|
||||
completion_names = []
|
||||
for filter in filters:
|
||||
completion_names += filter.values()
|
||||
return completion_names
|
||||
|
||||
def _trailer_completions(self, previous_leaf):
|
||||
user_context = get_user_scope(self._module_context, self._position)
|
||||
evaluation_context = self._evaluator.create_context(
|
||||
self._module_context, previous_leaf
|
||||
)
|
||||
contexts = evaluate_call_of_leaf(evaluation_context, previous_leaf)
|
||||
completion_names = []
|
||||
debug.dbg('trailer completion contexts: %s', contexts)
|
||||
for context in contexts:
|
||||
for filter in context.get_filters(
|
||||
search_global=False, origin_scope=user_context.tree_node):
|
||||
completion_names += filter.values()
|
||||
return completion_names
|
||||
|
||||
def _parse_dotted_names(self, nodes, is_import_from):
|
||||
level = 0
|
||||
names = []
|
||||
for node in nodes[1:]:
|
||||
if node in ('.', '...'):
|
||||
if not names:
|
||||
level += len(node.value)
|
||||
elif node.type == 'dotted_name':
|
||||
names += node.children[::2]
|
||||
elif node.type == 'name':
|
||||
names.append(node)
|
||||
elif node == ',':
|
||||
if not is_import_from:
|
||||
names = []
|
||||
else:
|
||||
# Here if the keyword `import` comes along it stops checking
|
||||
# for names.
|
||||
break
|
||||
return level, names
|
||||
|
||||
def _get_importer_names(self, names, level=0, only_modules=True):
|
||||
names = [n.value for n in names]
|
||||
i = imports.Importer(self._evaluator, names, self._module_context, level)
|
||||
return i.completion_names(self._evaluator, only_modules=only_modules)
|
||||
|
||||
def _get_class_context_completions(self, is_function=True):
|
||||
"""
|
||||
Autocomplete inherited methods when overriding in child class.
|
||||
"""
|
||||
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
|
||||
cls = tree.search_ancestor(leaf, 'classdef')
|
||||
if isinstance(cls, (tree.Class, tree.Function)):
|
||||
# Complete the methods that are defined in the super classes.
|
||||
random_context = self._module_context.create_context(
|
||||
cls,
|
||||
node_is_context=True
|
||||
)
|
||||
else:
|
||||
return
|
||||
|
||||
if cls.start_pos[1] >= leaf.start_pos[1]:
|
||||
return
|
||||
|
||||
filters = random_context.get_filters(search_global=False, is_instance=True)
|
||||
# The first dict is the dictionary of class itself.
|
||||
next(filters)
|
||||
for filter in filters:
|
||||
for name in filter.values():
|
||||
if (name.api_type == 'function') == is_function:
|
||||
yield name
|
@ -0,0 +1,315 @@
|
||||
"""
|
||||
Helpers for the API
|
||||
"""
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from textwrap import dedent
|
||||
|
||||
from parso.python.parser import Parser
|
||||
from parso.python import tree
|
||||
from parso import split_lines
|
||||
|
||||
from jedi._compatibility import u
|
||||
from jedi.evaluate.helpers import evaluate_call_of_leaf
|
||||
from jedi.cache import time_cache
|
||||
|
||||
|
||||
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
||||
|
||||
|
||||
def sorted_definitions(defs):
|
||||
# Note: `or ''` below is required because `module_path` could be
|
||||
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0))
|
||||
|
||||
|
||||
def get_on_completion_name(module_node, lines, position):
|
||||
leaf = module_node.get_leaf_for_position(position)
|
||||
if leaf is None or leaf.type in ('string', 'error_leaf'):
|
||||
# Completions inside strings are a bit special, we need to parse the
|
||||
# string. The same is true for comments and error_leafs.
|
||||
line = lines[position[0] - 1]
|
||||
# The first step of completions is to get the name
|
||||
return re.search(r'(?!\d)\w+$|$', line[:position[1]]).group(0)
|
||||
elif leaf.type not in ('name', 'keyword'):
|
||||
return ''
|
||||
|
||||
return leaf.value[:position[1] - leaf.start_pos[1]]
|
||||
|
||||
|
||||
def _get_code(code_lines, start_pos, end_pos):
|
||||
# Get relevant lines.
|
||||
lines = code_lines[start_pos[0] - 1:end_pos[0]]
|
||||
# Remove the parts at the end of the line.
|
||||
lines[-1] = lines[-1][:end_pos[1]]
|
||||
# Remove first line indentation.
|
||||
lines[0] = lines[0][start_pos[1]:]
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
class OnErrorLeaf(Exception):
|
||||
@property
|
||||
def error_leaf(self):
|
||||
return self.args[0]
|
||||
|
||||
|
||||
def _is_on_comment(leaf, position):
|
||||
comment_lines = split_lines(leaf.prefix)
|
||||
difference = leaf.start_pos[0] - position[0]
|
||||
prefix_start_pos = leaf.get_start_pos_of_prefix()
|
||||
if difference == 0:
|
||||
indent = leaf.start_pos[1]
|
||||
elif position[0] == prefix_start_pos[0]:
|
||||
indent = prefix_start_pos[1]
|
||||
else:
|
||||
indent = 0
|
||||
line = comment_lines[-difference - 1][:position[1] - indent]
|
||||
return '#' in line
|
||||
|
||||
|
||||
def _get_code_for_stack(code_lines, module_node, position):
|
||||
leaf = module_node.get_leaf_for_position(position, include_prefixes=True)
|
||||
# It might happen that we're on whitespace or on a comment. This means
|
||||
# that we would not get the right leaf.
|
||||
if leaf.start_pos >= position:
|
||||
if _is_on_comment(leaf, position):
|
||||
return u('')
|
||||
|
||||
# If we're not on a comment simply get the previous leaf and proceed.
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return u('') # At the beginning of the file.
|
||||
|
||||
is_after_newline = leaf.type == 'newline'
|
||||
while leaf.type == 'newline':
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return u('')
|
||||
|
||||
if leaf.type == 'error_leaf' or leaf.type == 'string':
|
||||
if leaf.start_pos[0] < position[0]:
|
||||
# On a different line, we just begin anew.
|
||||
return u('')
|
||||
|
||||
# Error leafs cannot be parsed, completion in strings is also
|
||||
# impossible.
|
||||
raise OnErrorLeaf(leaf)
|
||||
else:
|
||||
user_stmt = leaf
|
||||
while True:
|
||||
if user_stmt.parent.type in ('file_input', 'suite', 'simple_stmt'):
|
||||
break
|
||||
user_stmt = user_stmt.parent
|
||||
|
||||
if is_after_newline:
|
||||
if user_stmt.start_pos[1] > position[1]:
|
||||
# This means that it's actually a dedent and that means that we
|
||||
# start without context (part of a suite).
|
||||
return u('')
|
||||
|
||||
# This is basically getting the relevant lines.
|
||||
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
|
||||
|
||||
|
||||
def get_stack_at_position(grammar, code_lines, module_node, pos):
|
||||
"""
|
||||
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
|
||||
"""
|
||||
class EndMarkerReached(Exception):
|
||||
pass
|
||||
|
||||
def tokenize_without_endmarker(code):
|
||||
# TODO This is for now not an official parso API that exists purely
|
||||
# for Jedi.
|
||||
tokens = grammar._tokenize(code)
|
||||
for token_ in tokens:
|
||||
if token_.string == safeword:
|
||||
raise EndMarkerReached()
|
||||
else:
|
||||
yield token_
|
||||
|
||||
# The code might be indedented, just remove it.
|
||||
code = dedent(_get_code_for_stack(code_lines, module_node, pos))
|
||||
# We use a word to tell Jedi when we have reached the start of the
|
||||
# completion.
|
||||
# Use Z as a prefix because it's not part of a number suffix.
|
||||
safeword = 'ZZZ_USER_WANTS_TO_COMPLETE_HERE_WITH_JEDI'
|
||||
code = code + safeword
|
||||
|
||||
p = Parser(grammar._pgen_grammar, error_recovery=True)
|
||||
try:
|
||||
p.parse(tokens=tokenize_without_endmarker(code))
|
||||
except EndMarkerReached:
|
||||
return Stack(p.pgen_parser.stack)
|
||||
raise SystemError("This really shouldn't happen. There's a bug in Jedi.")
|
||||
|
||||
|
||||
class Stack(list):
|
||||
def get_node_names(self, grammar):
|
||||
for dfa, state, (node_number, nodes) in self:
|
||||
yield grammar.number2symbol[node_number]
|
||||
|
||||
def get_nodes(self):
|
||||
for dfa, state, (node_number, nodes) in self:
|
||||
for node in nodes:
|
||||
yield node
|
||||
|
||||
|
||||
def get_possible_completion_types(pgen_grammar, stack):
|
||||
def add_results(label_index):
|
||||
try:
|
||||
grammar_labels.append(inversed_tokens[label_index])
|
||||
except KeyError:
|
||||
try:
|
||||
keywords.append(inversed_keywords[label_index])
|
||||
except KeyError:
|
||||
t, v = pgen_grammar.labels[label_index]
|
||||
assert t >= 256
|
||||
# See if it's a symbol and if we're in its first set
|
||||
inversed_keywords
|
||||
itsdfa = pgen_grammar.dfas[t]
|
||||
itsstates, itsfirst = itsdfa
|
||||
for first_label_index in itsfirst.keys():
|
||||
add_results(first_label_index)
|
||||
|
||||
inversed_keywords = dict((v, k) for k, v in pgen_grammar.keywords.items())
|
||||
inversed_tokens = dict((v, k) for k, v in pgen_grammar.tokens.items())
|
||||
|
||||
keywords = []
|
||||
grammar_labels = []
|
||||
|
||||
def scan_stack(index):
|
||||
dfa, state, node = stack[index]
|
||||
states, first = dfa
|
||||
arcs = states[state]
|
||||
|
||||
for label_index, new_state in arcs:
|
||||
if label_index == 0:
|
||||
# An accepting state, check the stack below.
|
||||
scan_stack(index - 1)
|
||||
else:
|
||||
add_results(label_index)
|
||||
|
||||
scan_stack(-1)
|
||||
|
||||
return keywords, grammar_labels
|
||||
|
||||
|
||||
def evaluate_goto_definition(evaluator, context, leaf):
|
||||
if leaf.type == 'name':
|
||||
# In case of a name we can just use goto_definition which does all the
|
||||
# magic itself.
|
||||
return evaluator.goto_definitions(context, leaf)
|
||||
|
||||
parent = leaf.parent
|
||||
if parent.type == 'atom':
|
||||
return context.eval_node(leaf.parent)
|
||||
elif parent.type == 'trailer':
|
||||
return evaluate_call_of_leaf(context, leaf)
|
||||
elif isinstance(leaf, tree.Literal):
|
||||
return context.evaluator.eval_atom(context, leaf)
|
||||
return []
|
||||
|
||||
|
||||
CallSignatureDetails = namedtuple(
|
||||
'CallSignatureDetails',
|
||||
['bracket_leaf', 'call_index', 'keyword_name_str']
|
||||
)
|
||||
|
||||
|
||||
def _get_index_and_key(nodes, position):
|
||||
"""
|
||||
Returns the amount of commas and the keyword argument string.
|
||||
"""
|
||||
nodes_before = [c for c in nodes if c.start_pos < position]
|
||||
if nodes_before[-1].type == 'arglist':
|
||||
nodes_before = [c for c in nodes_before[-1].children if c.start_pos < position]
|
||||
|
||||
key_str = None
|
||||
|
||||
if nodes_before:
|
||||
last = nodes_before[-1]
|
||||
if last.type == 'argument' and last.children[1].end_pos <= position:
|
||||
# Checked if the argument
|
||||
key_str = last.children[0].value
|
||||
elif last == '=':
|
||||
key_str = nodes_before[-2].value
|
||||
|
||||
return nodes_before.count(','), key_str
|
||||
|
||||
|
||||
def _get_call_signature_details_from_error_node(node, position):
|
||||
for index, element in reversed(list(enumerate(node.children))):
|
||||
# `index > 0` means that it's a trailer and not an atom.
|
||||
if element == '(' and element.end_pos <= position and index > 0:
|
||||
# It's an error node, we don't want to match too much, just
|
||||
# until the parentheses is enough.
|
||||
children = node.children[index:]
|
||||
name = element.get_previous_leaf()
|
||||
if name is None:
|
||||
continue
|
||||
if name.type == 'name' or name.parent.type in ('trailer', 'atom'):
|
||||
return CallSignatureDetails(
|
||||
element,
|
||||
*_get_index_and_key(children, position)
|
||||
)
|
||||
|
||||
|
||||
def get_call_signature_details(module, position):
|
||||
leaf = module.get_leaf_for_position(position, include_prefixes=True)
|
||||
if leaf.start_pos >= position:
|
||||
# Whitespace / comments after the leaf count towards the previous leaf.
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
|
||||
if leaf == ')':
|
||||
if leaf.end_pos == position:
|
||||
leaf = leaf.get_next_leaf()
|
||||
|
||||
# Now that we know where we are in the syntax tree, we start to look at
|
||||
# parents for possible function definitions.
|
||||
node = leaf.parent
|
||||
while node is not None:
|
||||
if node.type in ('funcdef', 'classdef'):
|
||||
# Don't show call signatures if there's stuff before it that just
|
||||
# makes it feel strange to have a call signature.
|
||||
return None
|
||||
|
||||
for n in node.children[::-1]:
|
||||
if n.start_pos < position and n.type == 'error_node':
|
||||
result = _get_call_signature_details_from_error_node(n, position)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
if node.type == 'trailer' and node.children[0] == '(':
|
||||
leaf = node.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
return CallSignatureDetails(
|
||||
node.children[0], *_get_index_and_key(node.children, position))
|
||||
|
||||
node = node.parent
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@time_cache("call_signatures_validity")
|
||||
def cache_call_signatures(evaluator, context, bracket_leaf, code_lines, user_pos):
|
||||
"""This function calculates the cache key."""
|
||||
index = user_pos[0] - 1
|
||||
|
||||
before_cursor = code_lines[index][:user_pos[1]]
|
||||
other_lines = code_lines[bracket_leaf.start_pos[0]:index]
|
||||
whole = '\n'.join(other_lines + [before_cursor])
|
||||
before_bracket = re.match(r'.*\(', whole, re.DOTALL)
|
||||
|
||||
module_path = context.get_root_context().py__file__()
|
||||
if module_path is None:
|
||||
yield None # Don't cache!
|
||||
else:
|
||||
yield (module_path, before_bracket, bracket_leaf.start_pos)
|
||||
yield evaluate_goto_definition(
|
||||
evaluator,
|
||||
context,
|
||||
bracket_leaf.get_previous_leaf()
|
||||
)
|
@ -0,0 +1,47 @@
|
||||
"""
|
||||
TODO Some parts of this module are still not well documented.
|
||||
"""
|
||||
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.compiled import mixed
|
||||
from jedi.evaluate.context import Context
|
||||
|
||||
|
||||
class NamespaceObject(object):
|
||||
def __init__(self, dct):
|
||||
self.__dict__ = dct
|
||||
|
||||
|
||||
class MixedModuleContext(Context):
|
||||
resets_positions = True
|
||||
type = 'mixed_module'
|
||||
|
||||
def __init__(self, evaluator, tree_module, namespaces, path):
|
||||
self.evaluator = evaluator
|
||||
self._namespaces = namespaces
|
||||
|
||||
self._namespace_objects = [NamespaceObject(n) for n in namespaces]
|
||||
self._module_context = ModuleContext(evaluator, tree_module, path=path)
|
||||
self.tree_node = tree_module
|
||||
|
||||
def get_node(self):
|
||||
return self.tree_node
|
||||
|
||||
def get_filters(self, *args, **kwargs):
|
||||
for filter in self._module_context.get_filters(*args, **kwargs):
|
||||
yield filter
|
||||
|
||||
for namespace_obj in self._namespace_objects:
|
||||
compiled_object = compiled.create(self.evaluator, namespace_obj)
|
||||
mixed_object = mixed.MixedObject(
|
||||
self.evaluator,
|
||||
parent_context=self,
|
||||
compiled_object=compiled_object,
|
||||
tree_context=self._module_context
|
||||
)
|
||||
for filter in mixed_object.get_filters(*args, **kwargs):
|
||||
yield filter
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._module_context, name)
|
@ -0,0 +1,144 @@
|
||||
import pydoc
|
||||
import keyword
|
||||
|
||||
from jedi._compatibility import is_py3, is_py35
|
||||
from jedi import common
|
||||
from jedi.evaluate.filters import AbstractNameDefinition
|
||||
from parso.python.tree import Leaf
|
||||
|
||||
try:
|
||||
from pydoc_data import topics as pydoc_topics
|
||||
except ImportError:
|
||||
# Python 2
|
||||
try:
|
||||
import pydoc_topics
|
||||
except ImportError:
|
||||
# This is for Python 3 embeddable version, which dont have
|
||||
# pydoc_data module in its file python3x.zip.
|
||||
pydoc_topics = None
|
||||
|
||||
if is_py3:
|
||||
if is_py35:
|
||||
# in python 3.5 async and await are not proper keywords, but for
|
||||
# completion pursposes should as as though they are
|
||||
keys = keyword.kwlist + ["async", "await"]
|
||||
else:
|
||||
keys = keyword.kwlist
|
||||
else:
|
||||
keys = keyword.kwlist + ['None', 'False', 'True']
|
||||
|
||||
|
||||
def has_inappropriate_leaf_keyword(pos, module):
|
||||
relevant_errors = filter(
|
||||
lambda error: error.first_pos[0] == pos[0],
|
||||
module.error_statement_stacks)
|
||||
|
||||
for error in relevant_errors:
|
||||
if error.next_token in keys:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def completion_names(evaluator, stmt, pos, module):
|
||||
keyword_list = all_keywords(evaluator)
|
||||
|
||||
if not isinstance(stmt, Leaf) or has_inappropriate_leaf_keyword(pos, module):
|
||||
keyword_list = filter(
|
||||
lambda keyword: not keyword.only_valid_as_leaf,
|
||||
keyword_list
|
||||
)
|
||||
return [keyword.name for keyword in keyword_list]
|
||||
|
||||
|
||||
def all_keywords(evaluator, pos=(0, 0)):
|
||||
return set([Keyword(evaluator, k, pos) for k in keys])
|
||||
|
||||
|
||||
def keyword(evaluator, string, pos=(0, 0)):
|
||||
if string in keys:
|
||||
return Keyword(evaluator, string, pos)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_operator(evaluator, string, pos):
|
||||
return Keyword(evaluator, string, pos)
|
||||
|
||||
|
||||
keywords_only_valid_as_leaf = (
|
||||
'continue',
|
||||
'break',
|
||||
)
|
||||
|
||||
|
||||
class KeywordName(AbstractNameDefinition):
|
||||
api_type = 'keyword'
|
||||
|
||||
def __init__(self, evaluator, name):
|
||||
self.evaluator = evaluator
|
||||
self.string_name = name
|
||||
self.parent_context = evaluator.BUILTINS
|
||||
|
||||
def eval(self):
|
||||
return set()
|
||||
|
||||
def infer(self):
|
||||
return [Keyword(self.evaluator, self.string_name, (0, 0))]
|
||||
|
||||
|
||||
class Keyword(object):
|
||||
api_type = 'keyword'
|
||||
|
||||
def __init__(self, evaluator, name, pos):
|
||||
self.name = KeywordName(evaluator, name)
|
||||
self.start_pos = pos
|
||||
self.parent = evaluator.BUILTINS
|
||||
|
||||
@property
|
||||
def only_valid_as_leaf(self):
|
||||
return self.name.value in keywords_only_valid_as_leaf
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
""" For a `parsing.Name` like comparision """
|
||||
return [self.name]
|
||||
|
||||
def py__doc__(self, include_call_signature=False):
|
||||
return imitate_pydoc(self.name.string_name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self.name)
|
||||
|
||||
|
||||
def imitate_pydoc(string):
|
||||
"""
|
||||
It's not possible to get the pydoc's without starting the annoying pager
|
||||
stuff.
|
||||
"""
|
||||
if pydoc_topics is None:
|
||||
return ''
|
||||
|
||||
# str needed because of possible unicode stuff in py2k (pydoc doesn't work
|
||||
# with unicode strings)
|
||||
string = str(string)
|
||||
h = pydoc.help
|
||||
with common.ignored(KeyError):
|
||||
# try to access symbols
|
||||
string = h.symbols[string]
|
||||
string, _, related = string.partition(' ')
|
||||
|
||||
get_target = lambda s: h.topics.get(s, h.keywords.get(s))
|
||||
while isinstance(string, str):
|
||||
string = get_target(string)
|
||||
|
||||
try:
|
||||
# is a tuple now
|
||||
label, related = string
|
||||
except TypeError:
|
||||
return ''
|
||||
|
||||
try:
|
||||
return pydoc_topics.topics[label].strip() if pydoc_topics else ''
|
||||
except KeyError:
|
||||
return ''
|
@ -0,0 +1,27 @@
|
||||
"""
|
||||
To use Jedi completion in Python interpreter, add the following in your shell
|
||||
setup (e.g., ``.bashrc``)::
|
||||
|
||||
export PYTHONSTARTUP="$(python -m jedi repl)"
|
||||
|
||||
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
|
||||
from jedi import __version__ as __jedi_version__
|
||||
|
||||
print('REPL completion using Jedi %s' % __jedi_version__)
|
||||
jedi.utils.setup_readline()
|
||||
|
||||
del jedi
|
||||
|
||||
# Note: try not to do many things here, as it will contaminate global
|
||||
# namespace of the interpreter.
|
@ -0,0 +1,75 @@
|
||||
from jedi.api import classes
|
||||
from parso.python import tree
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.filters import TreeNameDefinition
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
|
||||
|
||||
def compare_contexts(c1, c2):
|
||||
return c1 == c2 or (c1[1] == c2[1] and c1[0].tree_node == c2[0].tree_node)
|
||||
|
||||
|
||||
def usages(evaluator, definition_names, mods):
|
||||
"""
|
||||
:param definitions: list of Name
|
||||
"""
|
||||
def resolve_names(definition_names):
|
||||
for name in definition_names:
|
||||
if name.api_type == 'module':
|
||||
found = False
|
||||
for context in name.infer():
|
||||
if isinstance(context, ModuleContext):
|
||||
found = True
|
||||
yield context.name
|
||||
if not found:
|
||||
yield name
|
||||
else:
|
||||
yield name
|
||||
|
||||
def compare_array(definition_names):
|
||||
""" `definitions` are being compared by module/start_pos, because
|
||||
sometimes the id's of the objects change (e.g. executions).
|
||||
"""
|
||||
return [
|
||||
(name.get_root_context(), name.start_pos)
|
||||
for name in resolve_names(definition_names)
|
||||
]
|
||||
|
||||
search_name = list(definition_names)[0].string_name
|
||||
compare_definitions = compare_array(definition_names)
|
||||
mods = mods | set([d.get_root_context() for d in definition_names])
|
||||
definition_names = set(resolve_names(definition_names))
|
||||
for m in imports.get_modules_containing_name(evaluator, mods, search_name):
|
||||
if isinstance(m, ModuleContext):
|
||||
for name_node in m.tree_node.get_used_names().get(search_name, []):
|
||||
context = evaluator.create_context(m, name_node)
|
||||
result = evaluator.goto(context, name_node)
|
||||
if any(compare_contexts(c1, c2)
|
||||
for c1 in compare_array(result)
|
||||
for c2 in compare_definitions):
|
||||
name = TreeNameDefinition(context, name_node)
|
||||
definition_names.add(name)
|
||||
# Previous definitions might be imports, so include them
|
||||
# (because goto might return that import name).
|
||||
compare_definitions += compare_array([name])
|
||||
else:
|
||||
# compiled objects
|
||||
definition_names.add(m.name)
|
||||
|
||||
return [classes.Definition(evaluator, n) for n in definition_names]
|
||||
|
||||
|
||||
def resolve_potential_imports(evaluator, definitions):
|
||||
""" Adds the modules of the imports """
|
||||
new = set()
|
||||
for d in definitions:
|
||||
if isinstance(d, TreeNameDefinition):
|
||||
imp_or_stmt = d.tree_name.get_definition()
|
||||
if isinstance(imp_or_stmt, tree.Import):
|
||||
new |= resolve_potential_imports(
|
||||
evaluator,
|
||||
set(imports.infer_import(
|
||||
d.parent_context, d.tree_name, is_goto=True
|
||||
))
|
||||
)
|
||||
return set(definitions) | new
|
@ -0,0 +1,124 @@
|
||||
"""
|
||||
This caching is very important for speed and memory optimizations. There's
|
||||
nothing really spectacular, just some decorators. The following cache types are
|
||||
available:
|
||||
|
||||
- ``time_cache`` can be used to cache something for just a limited time span,
|
||||
which can be useful if there's user interaction and the user cannot react
|
||||
faster than a certain time.
|
||||
|
||||
This module is one of the reasons why |jedi| is not thread-safe. As you can see
|
||||
there are global variables, which are holding the cache information. Some of
|
||||
these variables are being cleaned after every API usage.
|
||||
"""
|
||||
import time
|
||||
import inspect
|
||||
|
||||
from jedi import settings
|
||||
from parso.cache import parser_cache
|
||||
|
||||
_time_caches = {}
|
||||
|
||||
|
||||
def underscore_memoization(func):
|
||||
"""
|
||||
Decorator for methods::
|
||||
|
||||
class A(object):
|
||||
def x(self):
|
||||
if self._x:
|
||||
self._x = 10
|
||||
return self._x
|
||||
|
||||
Becomes::
|
||||
|
||||
class A(object):
|
||||
@underscore_memoization
|
||||
def x(self):
|
||||
return 10
|
||||
|
||||
A now has an attribute ``_x`` written by this decorator.
|
||||
"""
|
||||
name = '_' + func.__name__
|
||||
|
||||
def wrapper(self):
|
||||
try:
|
||||
return getattr(self, name)
|
||||
except AttributeError:
|
||||
result = func(self)
|
||||
if inspect.isgenerator(result):
|
||||
result = list(result)
|
||||
setattr(self, name, result)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def clear_time_caches(delete_all=False):
|
||||
""" Jedi caches many things, that should be completed after each completion
|
||||
finishes.
|
||||
|
||||
:param delete_all: Deletes also the cache that is normally not deleted,
|
||||
like parser cache, which is important for faster parsing.
|
||||
"""
|
||||
global _time_caches
|
||||
|
||||
if delete_all:
|
||||
for cache in _time_caches.values():
|
||||
cache.clear()
|
||||
parser_cache.clear()
|
||||
else:
|
||||
# normally just kill the expired entries, not all
|
||||
for tc in _time_caches.values():
|
||||
# check time_cache for expired entries
|
||||
for key, (t, value) in list(tc.items()):
|
||||
if t < time.time():
|
||||
# delete expired entries
|
||||
del tc[key]
|
||||
|
||||
|
||||
def time_cache(time_add_setting):
|
||||
"""
|
||||
This decorator works as follows: Call it with a setting and after that
|
||||
use the function with a callable that returns the key.
|
||||
But: This function is only called if the key is not available. After a
|
||||
certain amount of time (`time_add_setting`) the cache is invalid.
|
||||
|
||||
If the given key is None, the function will not be cached.
|
||||
"""
|
||||
def _temp(key_func):
|
||||
dct = {}
|
||||
_time_caches[time_add_setting] = dct
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
generator = key_func(*args, **kwargs)
|
||||
key = next(generator)
|
||||
try:
|
||||
expiry, value = dct[key]
|
||||
if expiry > time.time():
|
||||
return value
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
value = next(generator)
|
||||
time_add = getattr(settings, time_add_setting)
|
||||
if key is not None:
|
||||
dct[key] = time.time() + time_add, value
|
||||
return value
|
||||
return wrapper
|
||||
return _temp
|
||||
|
||||
|
||||
def memoize_method(method):
|
||||
"""A normal memoize function."""
|
||||
def wrapper(self, *args, **kwargs):
|
||||
cache_dict = self.__dict__.setdefault('_memoize_method_dct', {})
|
||||
dct = cache_dict.setdefault(method, {})
|
||||
key = (args, frozenset(kwargs.items()))
|
||||
try:
|
||||
return dct[key]
|
||||
except KeyError:
|
||||
result = method(self, *args, **kwargs)
|
||||
dct[key] = result
|
||||
return result
|
||||
return wrapper
|
@ -0,0 +1,111 @@
|
||||
""" A universal module with functions / classes without dependencies. """
|
||||
import sys
|
||||
import contextlib
|
||||
import functools
|
||||
|
||||
from jedi._compatibility import reraise
|
||||
from jedi import settings
|
||||
|
||||
|
||||
class UncaughtAttributeError(Exception):
|
||||
"""
|
||||
Important, because `__getattr__` and `hasattr` catch AttributeErrors
|
||||
implicitly. This is really evil (mainly because of `__getattr__`).
|
||||
`hasattr` in Python 2 is even more evil, because it catches ALL exceptions.
|
||||
Therefore this class originally had to be derived from `BaseException`
|
||||
instead of `Exception`. But because I removed relevant `hasattr` from
|
||||
the code base, we can now switch back to `Exception`.
|
||||
|
||||
:param base: return values of sys.exc_info().
|
||||
"""
|
||||
|
||||
|
||||
def safe_property(func):
|
||||
return property(reraise_uncaught(func))
|
||||
|
||||
|
||||
def reraise_uncaught(func):
|
||||
"""
|
||||
Re-throw uncaught `AttributeError`.
|
||||
|
||||
Usage: Put ``@rethrow_uncaught`` in front of the function
|
||||
which does **not** suppose to raise `AttributeError`.
|
||||
|
||||
AttributeError is easily get caught by `hasattr` and another
|
||||
``except AttributeError`` clause. This becomes problem when you use
|
||||
a lot of "dynamic" attributes (e.g., using ``@property``) because you
|
||||
can't distinguish if the property does not exist for real or some code
|
||||
inside of the "dynamic" attribute through that error. In a well
|
||||
written code, such error should not exist but getting there is very
|
||||
difficult. This decorator is to help us getting there by changing
|
||||
`AttributeError` to `UncaughtAttributeError` to avoid unexpected catch.
|
||||
This helps us noticing bugs earlier and facilitates debugging.
|
||||
|
||||
.. note:: Treating StopIteration here is easy.
|
||||
Add that feature when needed.
|
||||
"""
|
||||
@functools.wraps(func)
|
||||
def wrapper(*args, **kwds):
|
||||
try:
|
||||
return func(*args, **kwds)
|
||||
except AttributeError:
|
||||
exc_info = sys.exc_info()
|
||||
reraise(UncaughtAttributeError(exc_info[1]), exc_info[2])
|
||||
return wrapper
|
||||
|
||||
|
||||
class PushBackIterator(object):
|
||||
def __init__(self, iterator):
|
||||
self.pushes = []
|
||||
self.iterator = iterator
|
||||
self.current = None
|
||||
|
||||
def push_back(self, value):
|
||||
self.pushes.append(value)
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def next(self):
|
||||
""" Python 2 Compatibility """
|
||||
return self.__next__()
|
||||
|
||||
def __next__(self):
|
||||
if self.pushes:
|
||||
self.current = self.pushes.pop()
|
||||
else:
|
||||
self.current = next(self.iterator)
|
||||
return self.current
|
||||
|
||||
|
||||
def indent_block(text, indention=' '):
|
||||
"""This function indents a text block with a default of four spaces."""
|
||||
temp = ''
|
||||
while text and text[-1] == '\n':
|
||||
temp += text[-1]
|
||||
text = text[:-1]
|
||||
lines = text.split('\n')
|
||||
return '\n'.join(map(lambda s: indention + s, lines)) + temp
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def ignored(*exceptions):
|
||||
"""
|
||||
Context manager that ignores all of the specified exceptions. This will
|
||||
be in the standard library starting with Python 3.4.
|
||||
"""
|
||||
try:
|
||||
yield
|
||||
except exceptions:
|
||||
pass
|
||||
|
||||
|
||||
def unite(iterable):
|
||||
"""Turns a two dimensional array into a one dimensional."""
|
||||
return set(typ for types in iterable for typ in types)
|
||||
|
||||
|
||||
def to_list(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
return list(func(*args, **kwargs))
|
||||
return wrapper
|
@ -0,0 +1,128 @@
|
||||
from jedi._compatibility import encoding, is_py3, u
|
||||
import os
|
||||
import time
|
||||
|
||||
def _lazy_colorama_init():
|
||||
"""
|
||||
Lazily init colorama if necessary, not to screw up stdout is debug not
|
||||
enabled.
|
||||
|
||||
This version of the function does nothing.
|
||||
"""
|
||||
pass
|
||||
|
||||
_inited=False
|
||||
|
||||
try:
|
||||
if os.name == 'nt':
|
||||
# Does not work on Windows, as pyreadline and colorama interfere
|
||||
raise ImportError
|
||||
else:
|
||||
# Use colorama for nicer console output.
|
||||
from colorama import Fore, init
|
||||
from colorama import initialise
|
||||
def _lazy_colorama_init():
|
||||
"""
|
||||
Lazily init colorama if necessary, not to screw up stdout is
|
||||
debug not enabled.
|
||||
|
||||
This version of the function does init colorama.
|
||||
"""
|
||||
global _inited
|
||||
if not _inited:
|
||||
# pytest resets the stream at the end - causes troubles. Since
|
||||
# after every output the stream is reset automatically we don't
|
||||
# need this.
|
||||
initialise.atexit_done = True
|
||||
try:
|
||||
init()
|
||||
except Exception:
|
||||
# Colorama fails with initializing under vim and is buggy in
|
||||
# version 0.3.6.
|
||||
pass
|
||||
_inited = True
|
||||
|
||||
except ImportError:
|
||||
class Fore(object):
|
||||
RED = ''
|
||||
GREEN = ''
|
||||
YELLOW = ''
|
||||
MAGENTA = ''
|
||||
RESET = ''
|
||||
|
||||
NOTICE = object()
|
||||
WARNING = object()
|
||||
SPEED = object()
|
||||
|
||||
enable_speed = False
|
||||
enable_warning = False
|
||||
enable_notice = False
|
||||
|
||||
# callback, interface: level, str
|
||||
debug_function = None
|
||||
_debug_indent = 0
|
||||
_start_time = time.time()
|
||||
|
||||
|
||||
def reset_time():
|
||||
global _start_time, _debug_indent
|
||||
_start_time = time.time()
|
||||
_debug_indent = 0
|
||||
|
||||
|
||||
def increase_indent(func):
|
||||
"""Decorator for makin """
|
||||
def wrapper(*args, **kwargs):
|
||||
global _debug_indent
|
||||
_debug_indent += 1
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
_debug_indent -= 1
|
||||
return wrapper
|
||||
|
||||
|
||||
def dbg(message, *args, **kwargs):
|
||||
""" Looks at the stack, to see if a debug message should be printed. """
|
||||
# Python 2 compatibility, because it doesn't understand default args
|
||||
color = kwargs.pop('color', 'GREEN')
|
||||
assert color
|
||||
|
||||
if debug_function and enable_notice:
|
||||
i = ' ' * _debug_indent
|
||||
_lazy_colorama_init()
|
||||
debug_function(color, i + 'dbg: ' + message % tuple(u(repr(a)) for a in args))
|
||||
|
||||
|
||||
def warning(message, *args, **kwargs):
|
||||
format = kwargs.pop('format', True)
|
||||
assert not kwargs
|
||||
|
||||
if debug_function and enable_warning:
|
||||
i = ' ' * _debug_indent
|
||||
if format:
|
||||
message = message % tuple(u(repr(a)) for a in args)
|
||||
debug_function('RED', i + 'warning: ' + message)
|
||||
|
||||
|
||||
def speed(name):
|
||||
if debug_function and enable_speed:
|
||||
now = time.time()
|
||||
i = ' ' * _debug_indent
|
||||
debug_function('YELLOW', i + 'speed: ' + '%s %s' % (name, now - _start_time))
|
||||
|
||||
|
||||
def print_to_stdout(color, str_out):
|
||||
"""
|
||||
The default debug function that prints to standard out.
|
||||
|
||||
:param str color: A string that is an attribute of ``colorama.Fore``.
|
||||
"""
|
||||
col = getattr(Fore, color)
|
||||
_lazy_colorama_init()
|
||||
if not is_py3:
|
||||
str_out = str_out.encode(encoding, 'replace')
|
||||
print(col + str_out + Fore.RESET)
|
||||
|
||||
|
||||
# debug_function = print_to_stdout
|
@ -0,0 +1,629 @@
|
||||
"""
|
||||
Evaluation of Python code in |jedi| is based on three assumptions:
|
||||
|
||||
* The code uses as least side effects as possible. Jedi understands certain
|
||||
list/tuple/set modifications, but there's no guarantee that Jedi detects
|
||||
everything (list.append in different modules for example).
|
||||
* No magic is being used:
|
||||
|
||||
- metaclasses
|
||||
- ``setattr()`` / ``__import__()``
|
||||
- writing to ``globals()``, ``locals()``, ``object.__dict__``
|
||||
* The programmer is not a total dick, e.g. like `this
|
||||
<https://github.com/davidhalter/jedi/issues/24>`_ :-)
|
||||
|
||||
The actual algorithm is based on a principle called lazy evaluation. If you
|
||||
don't know about it, google it. That said, the typical entry point for static
|
||||
analysis is calling ``eval_statement``. There's separate logic for
|
||||
autocompletion in the API, the evaluator is all about evaluating an expression.
|
||||
|
||||
Now you need to understand what follows after ``eval_statement``. Let's
|
||||
make an example::
|
||||
|
||||
import datetime
|
||||
datetime.date.toda# <-- cursor here
|
||||
|
||||
First of all, this module doesn't care about completion. It really just cares
|
||||
about ``datetime.date``. At the end of the procedure ``eval_statement`` will
|
||||
return the ``date`` class.
|
||||
|
||||
To *visualize* this (simplified):
|
||||
|
||||
- ``Evaluator.eval_statement`` doesn't do much, because there's no assignment.
|
||||
- ``Evaluator.eval_element`` cares for resolving the dotted path
|
||||
- ``Evaluator.find_types`` searches for global definitions of datetime, which
|
||||
it finds in the definition of an import, by scanning the syntax tree.
|
||||
- Using the import logic, the datetime module is found.
|
||||
- Now ``find_types`` is called again by ``eval_element`` to find ``date``
|
||||
inside the datetime module.
|
||||
|
||||
Now what would happen if we wanted ``datetime.date.foo.bar``? Two more
|
||||
calls to ``find_types``. However the second call would be ignored, because the
|
||||
first one would return nothing (there's no foo attribute in ``date``).
|
||||
|
||||
What if the import would contain another ``ExprStmt`` like this::
|
||||
|
||||
from foo import bar
|
||||
Date = bar.baz
|
||||
|
||||
Well... You get it. Just another ``eval_statement`` recursion. It's really
|
||||
easy. Python can obviously get way more complicated then this. To understand
|
||||
tuple assignments, list comprehensions and everything else, a lot more code had
|
||||
to be written.
|
||||
|
||||
Jedi has been tested very well, so you can just start modifying code. It's best
|
||||
to write your own test first for your "new" feature. Don't be scared of
|
||||
breaking stuff. As long as the tests pass, you're most likely to be fine.
|
||||
|
||||
I need to mention now that lazy evaluation is really good because it
|
||||
only *evaluates* what needs to be *evaluated*. All the statements and modules
|
||||
that are not used are just being ignored.
|
||||
"""
|
||||
|
||||
import copy
|
||||
import sys
|
||||
|
||||
from parso.python import tree
|
||||
import parso
|
||||
|
||||
from jedi import debug
|
||||
from jedi.common import unite
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import recursion
|
||||
from jedi.evaluate import iterable
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate import stdlib
|
||||
from jedi.evaluate import finder
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import precedence
|
||||
from jedi.evaluate import param
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate import pep0484
|
||||
from jedi.evaluate.filters import TreeNameDefinition, ParamName
|
||||
from jedi.evaluate.instance import AnonymousInstance, BoundMethod
|
||||
from jedi.evaluate.context import ContextualizedName, ContextualizedNode
|
||||
from jedi import parser_utils
|
||||
|
||||
|
||||
def _limit_context_infers(func):
|
||||
"""
|
||||
This is for now the way how we limit type inference going wild. There are
|
||||
other ways to ensure recursion limits as well. This is mostly necessary
|
||||
because of instance (self) access that can be quite tricky to limit.
|
||||
|
||||
I'm still not sure this is the way to go, but it looks okay for now and we
|
||||
can still go anther way in the future. Tests are there. ~ dave
|
||||
"""
|
||||
def wrapper(evaluator, context, *args, **kwargs):
|
||||
n = context.tree_node
|
||||
try:
|
||||
evaluator.inferred_element_counts[n] += 1
|
||||
if evaluator.inferred_element_counts[n] > 300:
|
||||
debug.warning('In context %s there were too many inferences.', n)
|
||||
return set()
|
||||
except KeyError:
|
||||
evaluator.inferred_element_counts[n] = 1
|
||||
return func(evaluator, context, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
def __init__(self, grammar, sys_path=None):
|
||||
self.grammar = grammar
|
||||
self.latest_grammar = parso.load_grammar(version='3.6')
|
||||
self.memoize_cache = {} # for memoize decorators
|
||||
# To memorize modules -> equals `sys.modules`.
|
||||
self.modules = {} # like `sys.modules`.
|
||||
self.compiled_cache = {} # see `evaluate.compiled.create()`
|
||||
self.inferred_element_counts = {}
|
||||
self.mixed_cache = {} # see `evaluate.compiled.mixed._create()`
|
||||
self.analysis = []
|
||||
self.dynamic_params_depth = 0
|
||||
self.is_analysis = False
|
||||
self.python_version = sys.version_info[:2]
|
||||
|
||||
if sys_path is None:
|
||||
sys_path = sys.path
|
||||
self.sys_path = copy.copy(sys_path)
|
||||
try:
|
||||
self.sys_path.remove('')
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
self.reset_recursion_limitations()
|
||||
|
||||
# Constants
|
||||
self.BUILTINS = compiled.get_special_object(self, 'BUILTINS')
|
||||
|
||||
def reset_recursion_limitations(self):
|
||||
self.recursion_detector = recursion.RecursionDetector()
|
||||
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
|
||||
|
||||
def find_types(self, context, name_or_str, name_context, position=None,
|
||||
search_global=False, is_goto=False, analysis_errors=True):
|
||||
"""
|
||||
This is the search function. The most important part to debug.
|
||||
`remove_statements` and `filter_statements` really are the core part of
|
||||
this completion.
|
||||
|
||||
:param position: Position of the last statement -> tuple of line, column
|
||||
:return: List of Names. Their parents are the types.
|
||||
"""
|
||||
f = finder.NameFinder(self, context, name_context, name_or_str,
|
||||
position, analysis_errors=analysis_errors)
|
||||
filters = f.get_filters(search_global)
|
||||
if is_goto:
|
||||
return f.filter_name(filters)
|
||||
return f.find(filters, attribute_lookup=not search_global)
|
||||
|
||||
@_limit_context_infers
|
||||
def eval_statement(self, context, stmt, seek_name=None):
|
||||
with recursion.execution_allowed(self, stmt) as allowed:
|
||||
if allowed or context.get_root_context() == self.BUILTINS:
|
||||
return self._eval_stmt(context, stmt, seek_name)
|
||||
return set()
|
||||
|
||||
#@evaluator_function_cache(default=[])
|
||||
@debug.increase_indent
|
||||
def _eval_stmt(self, context, stmt, seek_name=None):
|
||||
"""
|
||||
The starting point of the completion. A statement always owns a call
|
||||
list, which are the calls, that a statement does. In case multiple
|
||||
names are defined in the statement, `seek_name` returns the result for
|
||||
this name.
|
||||
|
||||
:param stmt: A `tree.ExprStmt`.
|
||||
"""
|
||||
debug.dbg('eval_statement %s (%s)', stmt, seek_name)
|
||||
rhs = stmt.get_rhs()
|
||||
types = self.eval_element(context, rhs)
|
||||
|
||||
if seek_name:
|
||||
c_node = ContextualizedName(context, seek_name)
|
||||
types = finder.check_tuple_assignments(self, c_node, types)
|
||||
|
||||
first_operator = next(stmt.yield_operators(), None)
|
||||
if first_operator not in ('=', None) and first_operator.type == 'operator':
|
||||
# `=` is always the last character in aug assignments -> -1
|
||||
operator = copy.copy(first_operator)
|
||||
operator.value = operator.value[:-1]
|
||||
name = stmt.get_defined_names()[0].value
|
||||
left = context.py__getattribute__(
|
||||
name, position=stmt.start_pos, search_global=True)
|
||||
|
||||
for_stmt = tree.search_ancestor(stmt, 'for_stmt')
|
||||
if for_stmt is not None and for_stmt.type == 'for_stmt' and types \
|
||||
and parser_utils.for_stmt_defines_one_name(for_stmt):
|
||||
# Iterate through result and add the values, that's possible
|
||||
# only in for loops without clutter, because they are
|
||||
# predictable. Also only do it, if the variable is not a tuple.
|
||||
node = for_stmt.get_testlist()
|
||||
cn = ContextualizedNode(context, node)
|
||||
ordered = list(iterable.py__iter__(self, cn.infer(), cn))
|
||||
|
||||
for lazy_context in ordered:
|
||||
dct = {for_stmt.children[1].value: lazy_context.infer()}
|
||||
with helpers.predefine_names(context, for_stmt, dct):
|
||||
t = self.eval_element(context, rhs)
|
||||
left = precedence.calculate(self, context, left, operator, t)
|
||||
types = left
|
||||
else:
|
||||
types = precedence.calculate(self, context, left, operator, types)
|
||||
debug.dbg('eval_statement result %s', types)
|
||||
return types
|
||||
|
||||
def eval_element(self, context, element):
|
||||
if isinstance(context, iterable.CompForContext):
|
||||
return self._eval_element_not_cached(context, element)
|
||||
|
||||
if_stmt = element
|
||||
while if_stmt is not None:
|
||||
if_stmt = if_stmt.parent
|
||||
if if_stmt.type in ('if_stmt', 'for_stmt'):
|
||||
break
|
||||
if parser_utils.is_scope(if_stmt):
|
||||
if_stmt = None
|
||||
break
|
||||
predefined_if_name_dict = context.predefined_names.get(if_stmt)
|
||||
if predefined_if_name_dict is None and if_stmt and if_stmt.type == 'if_stmt':
|
||||
if_stmt_test = if_stmt.children[1]
|
||||
name_dicts = [{}]
|
||||
# If we already did a check, we don't want to do it again -> If
|
||||
# context.predefined_names is filled, we stop.
|
||||
# We don't want to check the if stmt itself, it's just about
|
||||
# the content.
|
||||
if element.start_pos > if_stmt_test.end_pos:
|
||||
# Now we need to check if the names in the if_stmt match the
|
||||
# names in the suite.
|
||||
if_names = helpers.get_names_of_node(if_stmt_test)
|
||||
element_names = helpers.get_names_of_node(element)
|
||||
str_element_names = [e.value for e in element_names]
|
||||
if any(i.value in str_element_names for i in if_names):
|
||||
for if_name in if_names:
|
||||
definitions = self.goto_definitions(context, if_name)
|
||||
# Every name that has multiple different definitions
|
||||
# causes the complexity to rise. The complexity should
|
||||
# never fall below 1.
|
||||
if len(definitions) > 1:
|
||||
if len(name_dicts) * len(definitions) > 16:
|
||||
debug.dbg('Too many options for if branch evaluation %s.', if_stmt)
|
||||
# There's only a certain amount of branches
|
||||
# Jedi can evaluate, otherwise it will take to
|
||||
# long.
|
||||
name_dicts = [{}]
|
||||
break
|
||||
|
||||
original_name_dicts = list(name_dicts)
|
||||
name_dicts = []
|
||||
for definition in definitions:
|
||||
new_name_dicts = list(original_name_dicts)
|
||||
for i, name_dict in enumerate(new_name_dicts):
|
||||
new_name_dicts[i] = name_dict.copy()
|
||||
new_name_dicts[i][if_name.value] = set([definition])
|
||||
|
||||
name_dicts += new_name_dicts
|
||||
else:
|
||||
for name_dict in name_dicts:
|
||||
name_dict[if_name.value] = definitions
|
||||
if len(name_dicts) > 1:
|
||||
result = set()
|
||||
for name_dict in name_dicts:
|
||||
with helpers.predefine_names(context, if_stmt, name_dict):
|
||||
result |= self._eval_element_not_cached(context, element)
|
||||
return result
|
||||
else:
|
||||
return self._eval_element_if_evaluated(context, element)
|
||||
else:
|
||||
if predefined_if_name_dict:
|
||||
return self._eval_element_not_cached(context, element)
|
||||
else:
|
||||
return self._eval_element_if_evaluated(context, element)
|
||||
|
||||
def _eval_element_if_evaluated(self, context, element):
|
||||
"""
|
||||
TODO This function is temporary: Merge with eval_element.
|
||||
"""
|
||||
parent = element
|
||||
while parent is not None:
|
||||
parent = parent.parent
|
||||
predefined_if_name_dict = context.predefined_names.get(parent)
|
||||
if predefined_if_name_dict is not None:
|
||||
return self._eval_element_not_cached(context, element)
|
||||
return self._eval_element_cached(context, element)
|
||||
|
||||
@evaluator_function_cache(default=set())
|
||||
def _eval_element_cached(self, context, element):
|
||||
return self._eval_element_not_cached(context, element)
|
||||
|
||||
@debug.increase_indent
|
||||
@_limit_context_infers
|
||||
def _eval_element_not_cached(self, context, element):
|
||||
debug.dbg('eval_element %s@%s', element, element.start_pos)
|
||||
types = set()
|
||||
typ = element.type
|
||||
if typ in ('name', 'number', 'string', 'atom'):
|
||||
types = self.eval_atom(context, element)
|
||||
elif typ == 'keyword':
|
||||
# For False/True/None
|
||||
if element.value in ('False', 'True', 'None'):
|
||||
types.add(compiled.builtin_from_name(self, element.value))
|
||||
# else: print e.g. could be evaluated like this in Python 2.7
|
||||
elif typ == 'lambdef':
|
||||
types = set([er.FunctionContext(self, context, element)])
|
||||
elif typ == 'expr_stmt':
|
||||
types = self.eval_statement(context, element)
|
||||
elif typ in ('power', 'atom_expr'):
|
||||
first_child = element.children[0]
|
||||
if not (first_child.type == 'keyword' and first_child.value == 'await'):
|
||||
types = self.eval_atom(context, first_child)
|
||||
for trailer in element.children[1:]:
|
||||
if trailer == '**': # has a power operation.
|
||||
right = self.eval_element(context, element.children[2])
|
||||
types = set(precedence.calculate(self, context, types, trailer, right))
|
||||
break
|
||||
types = self.eval_trailer(context, types, trailer)
|
||||
elif typ in ('testlist_star_expr', 'testlist',):
|
||||
# The implicit tuple in statements.
|
||||
types = set([iterable.SequenceLiteralContext(self, context, element)])
|
||||
elif typ in ('not_test', 'factor'):
|
||||
types = self.eval_element(context, element.children[-1])
|
||||
for operator in element.children[:-1]:
|
||||
types = set(precedence.factor_calculate(self, types, operator))
|
||||
elif typ == 'test':
|
||||
# `x if foo else y` case.
|
||||
types = (self.eval_element(context, element.children[0]) |
|
||||
self.eval_element(context, element.children[-1]))
|
||||
elif typ == 'operator':
|
||||
# Must be an ellipsis, other operators are not evaluated.
|
||||
# In Python 2 ellipsis is coded as three single dot tokens, not
|
||||
# as one token 3 dot token.
|
||||
assert element.value in ('.', '...')
|
||||
types = set([compiled.create(self, Ellipsis)])
|
||||
elif typ == 'dotted_name':
|
||||
types = self.eval_atom(context, element.children[0])
|
||||
for next_name in element.children[2::2]:
|
||||
# TODO add search_global=True?
|
||||
types = unite(
|
||||
typ.py__getattribute__(next_name, name_context=context)
|
||||
for typ in types
|
||||
)
|
||||
types = types
|
||||
elif typ == 'eval_input':
|
||||
types = self._eval_element_not_cached(context, element.children[0])
|
||||
elif typ == 'annassign':
|
||||
types = pep0484._evaluate_for_annotation(context, element.children[1])
|
||||
else:
|
||||
types = precedence.calculate_children(self, context, element.children)
|
||||
debug.dbg('eval_element result %s', types)
|
||||
return types
|
||||
|
||||
def eval_atom(self, context, atom):
|
||||
"""
|
||||
Basically to process ``atom`` nodes. The parser sometimes doesn't
|
||||
generate the node (because it has just one child). In that case an atom
|
||||
might be a name or a literal as well.
|
||||
"""
|
||||
if atom.type == 'name':
|
||||
# This is the first global lookup.
|
||||
stmt = tree.search_ancestor(
|
||||
atom, 'expr_stmt', 'lambdef'
|
||||
) or atom
|
||||
if stmt.type == 'lambdef':
|
||||
stmt = atom
|
||||
return context.py__getattribute__(
|
||||
name_or_str=atom,
|
||||
position=stmt.start_pos,
|
||||
search_global=True
|
||||
)
|
||||
elif isinstance(atom, tree.Literal):
|
||||
string = parser_utils.safe_literal_eval(atom.value)
|
||||
return set([compiled.create(self, string)])
|
||||
else:
|
||||
c = atom.children
|
||||
if c[0].type == 'string':
|
||||
# Will be one string.
|
||||
types = self.eval_atom(context, c[0])
|
||||
for string in c[1:]:
|
||||
right = self.eval_atom(context, string)
|
||||
types = precedence.calculate(self, context, types, '+', right)
|
||||
return types
|
||||
# Parentheses without commas are not tuples.
|
||||
elif c[0] == '(' and not len(c) == 2 \
|
||||
and not(c[1].type == 'testlist_comp' and
|
||||
len(c[1].children) > 1):
|
||||
return self.eval_element(context, c[1])
|
||||
|
||||
try:
|
||||
comp_for = c[1].children[1]
|
||||
except (IndexError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
if comp_for == ':':
|
||||
# Dict comprehensions have a colon at the 3rd index.
|
||||
try:
|
||||
comp_for = c[1].children[3]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if comp_for.type == 'comp_for':
|
||||
return set([iterable.Comprehension.from_atom(self, context, atom)])
|
||||
|
||||
# It's a dict/list/tuple literal.
|
||||
array_node = c[1]
|
||||
try:
|
||||
array_node_c = array_node.children
|
||||
except AttributeError:
|
||||
array_node_c = []
|
||||
if c[0] == '{' and (array_node == '}' or ':' in array_node_c):
|
||||
context = iterable.DictLiteralContext(self, context, atom)
|
||||
else:
|
||||
context = iterable.SequenceLiteralContext(self, context, atom)
|
||||
return set([context])
|
||||
|
||||
def eval_trailer(self, context, types, trailer):
|
||||
trailer_op, node = trailer.children[:2]
|
||||
if node == ')': # `arglist` is optional.
|
||||
node = ()
|
||||
|
||||
new_types = set()
|
||||
if trailer_op == '[':
|
||||
new_types |= iterable.py__getitem__(self, context, types, trailer)
|
||||
else:
|
||||
for typ in types:
|
||||
debug.dbg('eval_trailer: %s in scope %s', trailer, typ)
|
||||
if trailer_op == '.':
|
||||
new_types |= typ.py__getattribute__(
|
||||
name_context=context,
|
||||
name_or_str=node
|
||||
)
|
||||
elif trailer_op == '(':
|
||||
arguments = param.TreeArguments(self, context, node, trailer)
|
||||
new_types |= self.execute(typ, arguments)
|
||||
return new_types
|
||||
|
||||
@debug.increase_indent
|
||||
def execute(self, obj, arguments):
|
||||
if self.is_analysis:
|
||||
arguments.eval_all()
|
||||
|
||||
debug.dbg('execute: %s %s', obj, arguments)
|
||||
try:
|
||||
# Some stdlib functions like super(), namedtuple(), etc. have been
|
||||
# hard-coded in Jedi to support them.
|
||||
return stdlib.execute(self, obj, arguments)
|
||||
except stdlib.NotInStdLib:
|
||||
pass
|
||||
|
||||
try:
|
||||
func = obj.py__call__
|
||||
except AttributeError:
|
||||
debug.warning("no execution possible %s", obj)
|
||||
return set()
|
||||
else:
|
||||
types = func(arguments)
|
||||
debug.dbg('execute result: %s in %s', types, obj)
|
||||
return types
|
||||
|
||||
def goto_definitions(self, context, name):
|
||||
def_ = name.get_definition(import_name_always=True)
|
||||
if def_ is not None:
|
||||
type_ = def_.type
|
||||
if type_ == 'classdef':
|
||||
return [er.ClassContext(self, name.parent, context)]
|
||||
elif type_ == 'funcdef':
|
||||
return [er.FunctionContext(self, context, name.parent)]
|
||||
|
||||
if type_ == 'expr_stmt':
|
||||
is_simple_name = name.parent.type not in ('power', 'trailer')
|
||||
if is_simple_name:
|
||||
return self.eval_statement(context, def_, name)
|
||||
if type_ == 'for_stmt':
|
||||
container_types = self.eval_element(context, def_.children[3])
|
||||
cn = ContextualizedNode(context, def_.children[3])
|
||||
for_types = iterable.py__iter__types(self, container_types, cn)
|
||||
c_node = ContextualizedName(context, name)
|
||||
return finder.check_tuple_assignments(self, c_node, for_types)
|
||||
if type_ in ('import_from', 'import_name'):
|
||||
return imports.infer_import(context, name)
|
||||
|
||||
return helpers.evaluate_call_of_leaf(context, name)
|
||||
|
||||
def goto(self, context, name):
|
||||
definition = name.get_definition(import_name_always=True)
|
||||
if definition is not None:
|
||||
type_ = definition.type
|
||||
if type_ == 'expr_stmt':
|
||||
# Only take the parent, because if it's more complicated than just
|
||||
# a name it's something you can "goto" again.
|
||||
is_simple_name = name.parent.type not in ('power', 'trailer')
|
||||
if is_simple_name:
|
||||
return [TreeNameDefinition(context, name)]
|
||||
elif type_ == 'param':
|
||||
return [ParamName(context, name)]
|
||||
elif type_ in ('funcdef', 'classdef'):
|
||||
return [TreeNameDefinition(context, name)]
|
||||
elif type_ in ('import_from', 'import_name'):
|
||||
module_names = imports.infer_import(context, name, is_goto=True)
|
||||
return module_names
|
||||
|
||||
par = name.parent
|
||||
typ = par.type
|
||||
if typ == 'argument' and par.children[1] == '=' and par.children[0] == name:
|
||||
# Named param goto.
|
||||
trailer = par.parent
|
||||
if trailer.type == 'arglist':
|
||||
trailer = trailer.parent
|
||||
if trailer.type != 'classdef':
|
||||
if trailer.type == 'decorator':
|
||||
types = self.eval_element(context, trailer.children[1])
|
||||
else:
|
||||
i = trailer.parent.children.index(trailer)
|
||||
to_evaluate = trailer.parent.children[:i]
|
||||
types = self.eval_element(context, to_evaluate[0])
|
||||
for trailer in to_evaluate[1:]:
|
||||
types = self.eval_trailer(context, types, trailer)
|
||||
param_names = []
|
||||
for typ in types:
|
||||
try:
|
||||
get_param_names = typ.get_param_names
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for param_name in get_param_names():
|
||||
if param_name.string_name == name.value:
|
||||
param_names.append(param_name)
|
||||
return param_names
|
||||
elif typ == 'dotted_name': # Is a decorator.
|
||||
index = par.children.index(name)
|
||||
if index > 0:
|
||||
new_dotted = helpers.deep_ast_copy(par)
|
||||
new_dotted.children[index - 1:] = []
|
||||
values = self.eval_element(context, new_dotted)
|
||||
return unite(
|
||||
value.py__getattribute__(name, name_context=context, is_goto=True)
|
||||
for value in values
|
||||
)
|
||||
|
||||
if typ == 'trailer' and par.children[0] == '.':
|
||||
values = helpers.evaluate_call_of_leaf(context, name, cut_own_trailer=True)
|
||||
return unite(
|
||||
value.py__getattribute__(name, name_context=context, is_goto=True)
|
||||
for value in values
|
||||
)
|
||||
else:
|
||||
stmt = tree.search_ancestor(
|
||||
name, 'expr_stmt', 'lambdef'
|
||||
) or name
|
||||
if stmt.type == 'lambdef':
|
||||
stmt = name
|
||||
return context.py__getattribute__(
|
||||
name,
|
||||
position=stmt.start_pos,
|
||||
search_global=True, is_goto=True
|
||||
)
|
||||
|
||||
def create_context(self, base_context, node, node_is_context=False, node_is_object=False):
|
||||
def parent_scope(node):
|
||||
while True:
|
||||
node = node.parent
|
||||
|
||||
if parser_utils.is_scope(node):
|
||||
return node
|
||||
elif node.type in ('argument', 'testlist_comp'):
|
||||
if node.children[1].type == 'comp_for':
|
||||
return node.children[1]
|
||||
elif node.type == 'dictorsetmaker':
|
||||
for n in node.children[1:4]:
|
||||
# In dictionaries it can be pretty much anything.
|
||||
if n.type == 'comp_for':
|
||||
return n
|
||||
|
||||
def from_scope_node(scope_node, child_is_funcdef=None, is_nested=True, node_is_object=False):
|
||||
if scope_node == base_node:
|
||||
return base_context
|
||||
|
||||
is_funcdef = scope_node.type in ('funcdef', 'lambdef')
|
||||
parent_scope = parser_utils.get_parent_scope(scope_node)
|
||||
parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef)
|
||||
|
||||
if is_funcdef:
|
||||
if isinstance(parent_context, AnonymousInstance):
|
||||
func = BoundMethod(
|
||||
self, parent_context, parent_context.class_context,
|
||||
parent_context.parent_context, scope_node
|
||||
)
|
||||
else:
|
||||
func = er.FunctionContext(
|
||||
self,
|
||||
parent_context,
|
||||
scope_node
|
||||
)
|
||||
if is_nested and not node_is_object:
|
||||
return func.get_function_execution()
|
||||
return func
|
||||
elif scope_node.type == 'classdef':
|
||||
class_context = er.ClassContext(self, scope_node, parent_context)
|
||||
if child_is_funcdef:
|
||||
# anonymous instance
|
||||
return AnonymousInstance(self, parent_context, class_context)
|
||||
else:
|
||||
return class_context
|
||||
elif scope_node.type == 'comp_for':
|
||||
if node.start_pos >= scope_node.children[-1].start_pos:
|
||||
return parent_context
|
||||
return iterable.CompForContext.from_comp_for(parent_context, scope_node)
|
||||
raise Exception("There's a scope that was not managed.")
|
||||
|
||||
base_node = base_context.tree_node
|
||||
|
||||
if node_is_context and parser_utils.is_scope(node):
|
||||
scope_node = node
|
||||
else:
|
||||
if node.parent.type in ('funcdef', 'classdef') and node.parent.name == node:
|
||||
# When we're on class/function names/leafs that define the
|
||||
# object itself and not its contents.
|
||||
node = node.parent
|
||||
scope_node = parent_scope(node)
|
||||
return from_scope_node(scope_node, is_nested=True, node_is_object=node_is_object)
|
@ -0,0 +1,214 @@
|
||||
"""
|
||||
Module for statical analysis.
|
||||
"""
|
||||
from jedi import debug
|
||||
from parso.python import tree
|
||||
from jedi.evaluate.compiled import CompiledObject
|
||||
|
||||
|
||||
CODES = {
|
||||
'attribute-error': (1, AttributeError, 'Potential AttributeError.'),
|
||||
'name-error': (2, NameError, 'Potential NameError.'),
|
||||
'import-error': (3, ImportError, 'Potential ImportError.'),
|
||||
'type-error-too-many-arguments': (4, TypeError, None),
|
||||
'type-error-too-few-arguments': (5, TypeError, None),
|
||||
'type-error-keyword-argument': (6, TypeError, None),
|
||||
'type-error-multiple-values': (7, TypeError, None),
|
||||
'type-error-star-star': (8, TypeError, None),
|
||||
'type-error-star': (9, TypeError, None),
|
||||
'type-error-operation': (10, TypeError, None),
|
||||
'type-error-not-iterable': (11, TypeError, None),
|
||||
'type-error-isinstance': (12, TypeError, None),
|
||||
'type-error-not-subscriptable': (13, TypeError, None),
|
||||
'value-error-too-many-values': (14, ValueError, None),
|
||||
'value-error-too-few-values': (15, ValueError, None),
|
||||
}
|
||||
|
||||
|
||||
class Error(object):
|
||||
def __init__(self, name, module_path, start_pos, message=None):
|
||||
self.path = module_path
|
||||
self._start_pos = start_pos
|
||||
self.name = name
|
||||
if message is None:
|
||||
message = CODES[self.name][2]
|
||||
self.message = message
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
return self._start_pos[0]
|
||||
|
||||
@property
|
||||
def column(self):
|
||||
return self._start_pos[1]
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
# The class name start
|
||||
first = self.__class__.__name__[0]
|
||||
return first + str(CODES[self.name][0])
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s:%s:%s: %s %s' % (self.path, self.line, self.column,
|
||||
self.code, self.message)
|
||||
|
||||
def __str__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.path == other.path and self.name == other.name and
|
||||
self._start_pos == other._start_pos)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.path, self._start_pos, self.name))
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %s: %s@%s,%s>' % (self.__class__.__name__,
|
||||
self.name, self.path,
|
||||
self._start_pos[0], self._start_pos[1])
|
||||
|
||||
|
||||
class Warning(Error):
|
||||
pass
|
||||
|
||||
|
||||
def add(node_context, error_name, node, message=None, typ=Error, payload=None):
|
||||
exception = CODES[error_name][1]
|
||||
if _check_for_exception_catch(node_context, node, exception, payload):
|
||||
return
|
||||
|
||||
# TODO this path is probably not right
|
||||
module_context = node_context.get_root_context()
|
||||
module_path = module_context.py__file__()
|
||||
instance = typ(error_name, module_path, node.start_pos, message)
|
||||
debug.warning(str(instance), format=False)
|
||||
node_context.evaluator.analysis.append(instance)
|
||||
|
||||
|
||||
def _check_for_setattr(instance):
|
||||
"""
|
||||
Check if there's any setattr method inside an instance. If so, return True.
|
||||
"""
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
module = instance.get_root_context()
|
||||
if not isinstance(module, ModuleContext):
|
||||
return False
|
||||
|
||||
node = module.tree_node
|
||||
try:
|
||||
stmts = node.get_used_names()['setattr']
|
||||
except KeyError:
|
||||
return False
|
||||
|
||||
return any(node.start_pos < stmt.start_pos < node.end_pos
|
||||
for stmt in stmts)
|
||||
|
||||
|
||||
def add_attribute_error(name_context, lookup_context, name):
|
||||
message = ('AttributeError: %s has no attribute %s.' % (lookup_context, name))
|
||||
from jedi.evaluate.instance import AbstractInstanceContext, CompiledInstanceName
|
||||
# Check for __getattr__/__getattribute__ existance and issue a warning
|
||||
# instead of an error, if that happens.
|
||||
typ = Error
|
||||
if isinstance(lookup_context, AbstractInstanceContext):
|
||||
slot_names = lookup_context.get_function_slot_names('__getattr__') + \
|
||||
lookup_context.get_function_slot_names('__getattribute__')
|
||||
for n in slot_names:
|
||||
if isinstance(name, CompiledInstanceName) and \
|
||||
n.parent_context.obj == object:
|
||||
typ = Warning
|
||||
break
|
||||
|
||||
if _check_for_setattr(lookup_context):
|
||||
typ = Warning
|
||||
|
||||
payload = lookup_context, name
|
||||
add(name_context, 'attribute-error', name, message, typ, payload)
|
||||
|
||||
|
||||
def _check_for_exception_catch(node_context, jedi_name, exception, payload=None):
|
||||
"""
|
||||
Checks if a jedi object (e.g. `Statement`) sits inside a try/catch and
|
||||
doesn't count as an error (if equal to `exception`).
|
||||
Also checks `hasattr` for AttributeErrors and uses the `payload` to compare
|
||||
it.
|
||||
Returns True if the exception was catched.
|
||||
"""
|
||||
def check_match(cls, exception):
|
||||
try:
|
||||
return isinstance(cls, CompiledObject) and issubclass(exception, cls.obj)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
def check_try_for_except(obj, exception):
|
||||
# Only nodes in try
|
||||
iterator = iter(obj.children)
|
||||
for branch_type in iterator:
|
||||
colon = next(iterator)
|
||||
suite = next(iterator)
|
||||
if branch_type == 'try' \
|
||||
and not (branch_type.start_pos < jedi_name.start_pos <= suite.end_pos):
|
||||
return False
|
||||
|
||||
for node in obj.get_except_clause_tests():
|
||||
if node is None:
|
||||
return True # An exception block that catches everything.
|
||||
else:
|
||||
except_classes = node_context.eval_node(node)
|
||||
for cls in except_classes:
|
||||
from jedi.evaluate import iterable
|
||||
if isinstance(cls, iterable.AbstractSequence) and \
|
||||
cls.array_type == 'tuple':
|
||||
# multiple exceptions
|
||||
for lazy_context in cls.py__iter__():
|
||||
for typ in lazy_context.infer():
|
||||
if check_match(typ, exception):
|
||||
return True
|
||||
else:
|
||||
if check_match(cls, exception):
|
||||
return True
|
||||
|
||||
def check_hasattr(node, suite):
|
||||
try:
|
||||
assert suite.start_pos <= jedi_name.start_pos < suite.end_pos
|
||||
assert node.type in ('power', 'atom_expr')
|
||||
base = node.children[0]
|
||||
assert base.type == 'name' and base.value == 'hasattr'
|
||||
trailer = node.children[1]
|
||||
assert trailer.type == 'trailer'
|
||||
arglist = trailer.children[1]
|
||||
assert arglist.type == 'arglist'
|
||||
from jedi.evaluate.param import TreeArguments
|
||||
args = list(TreeArguments(node_context.evaluator, node_context, arglist).unpack())
|
||||
# Arguments should be very simple
|
||||
assert len(args) == 2
|
||||
|
||||
# Check name
|
||||
key, lazy_context = args[1]
|
||||
names = list(lazy_context.infer())
|
||||
assert len(names) == 1 and isinstance(names[0], CompiledObject)
|
||||
assert names[0].obj == payload[1].value
|
||||
|
||||
# Check objects
|
||||
key, lazy_context = args[0]
|
||||
objects = lazy_context.infer()
|
||||
return payload[0] in objects
|
||||
except AssertionError:
|
||||
return False
|
||||
|
||||
obj = jedi_name
|
||||
while obj is not None and not isinstance(obj, (tree.Function, tree.Class)):
|
||||
if isinstance(obj, tree.Flow):
|
||||
# try/except catch check
|
||||
if obj.type == 'try_stmt' and check_try_for_except(obj, exception):
|
||||
return True
|
||||
# hasattr check
|
||||
if exception == AttributeError and obj.type in ('if_stmt', 'while_stmt'):
|
||||
if check_hasattr(obj.children[1], obj.children[3]):
|
||||
return True
|
||||
obj = obj.parent
|
||||
|
||||
return False
|
@ -0,0 +1,81 @@
|
||||
"""
|
||||
- the popular ``_memoize_default`` works like a typical memoize and returns the
|
||||
default otherwise.
|
||||
- ``CachedMetaClass`` uses ``_memoize_default`` to do the same with classes.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
|
||||
_NO_DEFAULT = object()
|
||||
|
||||
|
||||
def _memoize_default(default=_NO_DEFAULT, evaluator_is_first_arg=False, second_arg_is_evaluator=False):
|
||||
""" This is a typical memoization decorator, BUT there is one difference:
|
||||
To prevent recursion it sets defaults.
|
||||
|
||||
Preventing recursion is in this case the much bigger use than speed. I
|
||||
don't think, that there is a big speed difference, but there are many cases
|
||||
where recursion could happen (think about a = b; b = a).
|
||||
"""
|
||||
def func(function):
|
||||
def wrapper(obj, *args, **kwargs):
|
||||
# TODO These checks are kind of ugly and slow.
|
||||
if evaluator_is_first_arg:
|
||||
cache = obj.memoize_cache
|
||||
elif second_arg_is_evaluator:
|
||||
cache = args[0].memoize_cache # needed for meta classes
|
||||
else:
|
||||
cache = obj.evaluator.memoize_cache
|
||||
|
||||
try:
|
||||
memo = cache[function]
|
||||
except KeyError:
|
||||
memo = {}
|
||||
cache[function] = memo
|
||||
|
||||
key = (obj, args, frozenset(kwargs.items()))
|
||||
if key in memo:
|
||||
return memo[key]
|
||||
else:
|
||||
if default is not _NO_DEFAULT:
|
||||
memo[key] = default
|
||||
rv = function(obj, *args, **kwargs)
|
||||
if inspect.isgenerator(rv):
|
||||
rv = list(rv)
|
||||
memo[key] = rv
|
||||
return rv
|
||||
return wrapper
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def evaluator_function_cache(default=_NO_DEFAULT):
|
||||
def decorator(func):
|
||||
return _memoize_default(default=default, evaluator_is_first_arg=True)(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def evaluator_method_cache(default=_NO_DEFAULT):
|
||||
def decorator(func):
|
||||
return _memoize_default(default=default)(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def _memoize_meta_class():
|
||||
def decorator(call):
|
||||
return _memoize_default(second_arg_is_evaluator=True)(call)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class CachedMetaClass(type):
|
||||
"""
|
||||
This is basically almost the same than the decorator above, it just caches
|
||||
class initializations. Either you do it this way or with decorators, but
|
||||
with decorators you lose class access (isinstance, etc).
|
||||
"""
|
||||
@_memoize_meta_class()
|
||||
def __call__(self, *args, **kwargs):
|
||||
return super(CachedMetaClass, self).__call__(*args, **kwargs)
|
@ -0,0 +1,637 @@
|
||||
"""
|
||||
Imitate the parser representation.
|
||||
"""
|
||||
import inspect
|
||||
import re
|
||||
import sys
|
||||
import os
|
||||
import types
|
||||
from functools import partial
|
||||
|
||||
from jedi._compatibility import builtins as _builtins, unicode, py_version
|
||||
from jedi import debug
|
||||
from jedi.cache import underscore_memoization, memoize_method
|
||||
from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
|
||||
ContextNameMixin
|
||||
from jedi.evaluate.context import Context, LazyKnownContext
|
||||
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||
from . import fake
|
||||
|
||||
|
||||
_sep = os.path.sep
|
||||
if os.path.altsep is not None:
|
||||
_sep += os.path.altsep
|
||||
_path_re = re.compile('(?:\.[^{0}]+|[{0}]__init__\.py)$'.format(re.escape(_sep)))
|
||||
del _sep
|
||||
|
||||
# Those types don't exist in typing.
|
||||
MethodDescriptorType = type(str.replace)
|
||||
WrapperDescriptorType = type(set.__iter__)
|
||||
# `object.__subclasshook__` is an already executed descriptor.
|
||||
object_class_dict = type.__dict__["__dict__"].__get__(object)
|
||||
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
|
||||
|
||||
ALLOWED_DESCRIPTOR_ACCESS = (
|
||||
types.FunctionType,
|
||||
types.GetSetDescriptorType,
|
||||
types.MemberDescriptorType,
|
||||
MethodDescriptorType,
|
||||
WrapperDescriptorType,
|
||||
ClassMethodDescriptorType,
|
||||
staticmethod,
|
||||
classmethod,
|
||||
)
|
||||
|
||||
class CheckAttribute(object):
|
||||
"""Raises an AttributeError if the attribute X isn't available."""
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
# Remove the py in front of e.g. py__call__.
|
||||
self.check_name = func.__name__[2:]
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
# This might raise an AttributeError. That's wanted.
|
||||
if self.check_name == '__iter__':
|
||||
# Python iterators are a bit strange, because there's no need for
|
||||
# the __iter__ function as long as __getitem__ is defined (it will
|
||||
# just start with __getitem__(0). This is especially true for
|
||||
# Python 2 strings, where `str.__iter__` is not even defined.
|
||||
try:
|
||||
iter(instance.obj)
|
||||
except TypeError:
|
||||
raise AttributeError
|
||||
else:
|
||||
getattr(instance.obj, self.check_name)
|
||||
return partial(self.func, instance)
|
||||
|
||||
|
||||
class CompiledObject(Context):
|
||||
path = None # modules have this attribute - set it to None.
|
||||
used_names = lambda self: {} # To be consistent with modules.
|
||||
|
||||
def __init__(self, evaluator, obj, parent_context=None, faked_class=None):
|
||||
super(CompiledObject, self).__init__(evaluator, parent_context)
|
||||
self.obj = obj
|
||||
# This attribute will not be set for most classes, except for fakes.
|
||||
self.tree_node = faked_class
|
||||
|
||||
def get_root_node(self):
|
||||
# To make things a bit easier with filters we add this method here.
|
||||
return self.get_root_context()
|
||||
|
||||
@CheckAttribute
|
||||
def py__call__(self, params):
|
||||
if inspect.isclass(self.obj):
|
||||
from jedi.evaluate.instance import CompiledInstance
|
||||
return set([CompiledInstance(self.evaluator, self.parent_context, self, params)])
|
||||
else:
|
||||
return set(self._execute_function(params))
|
||||
|
||||
@CheckAttribute
|
||||
def py__class__(self):
|
||||
return create(self.evaluator, self.obj.__class__)
|
||||
|
||||
@CheckAttribute
|
||||
def py__mro__(self):
|
||||
return (self,) + tuple(create(self.evaluator, cls) for cls in self.obj.__mro__[1:])
|
||||
|
||||
@CheckAttribute
|
||||
def py__bases__(self):
|
||||
return tuple(create(self.evaluator, cls) for cls in self.obj.__bases__)
|
||||
|
||||
def py__bool__(self):
|
||||
return bool(self.obj)
|
||||
|
||||
def py__file__(self):
|
||||
try:
|
||||
return self.obj.__file__
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def is_class(self):
|
||||
return inspect.isclass(self.obj)
|
||||
|
||||
def py__doc__(self, include_call_signature=False):
|
||||
return inspect.getdoc(self.obj) or ''
|
||||
|
||||
def get_param_names(self):
|
||||
obj = self.obj
|
||||
try:
|
||||
if py_version < 33:
|
||||
raise ValueError("inspect.signature was introduced in 3.3")
|
||||
if py_version == 34:
|
||||
# In 3.4 inspect.signature are wrong for str and int. This has
|
||||
# been fixed in 3.5. The signature of object is returned,
|
||||
# because no signature was found for str. Here we imitate 3.5
|
||||
# logic and just ignore the signature if the magic methods
|
||||
# don't match object.
|
||||
# 3.3 doesn't even have the logic and returns nothing for str
|
||||
# and classes that inherit from object.
|
||||
user_def = inspect._signature_get_user_defined_method
|
||||
if (inspect.isclass(obj)
|
||||
and not user_def(type(obj), '__init__')
|
||||
and not user_def(type(obj), '__new__')
|
||||
and (obj.__init__ != object.__init__
|
||||
or obj.__new__ != object.__new__)):
|
||||
raise ValueError
|
||||
|
||||
signature = inspect.signature(obj)
|
||||
except ValueError: # Has no signature
|
||||
params_str, ret = self._parse_function_doc()
|
||||
tokens = params_str.split(',')
|
||||
if inspect.ismethoddescriptor(obj):
|
||||
tokens.insert(0, 'self')
|
||||
for p in tokens:
|
||||
parts = p.strip().split('=')
|
||||
yield UnresolvableParamName(self, parts[0])
|
||||
else:
|
||||
for signature_param in signature.parameters.values():
|
||||
yield SignatureParamName(self, signature_param)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, repr(self.obj))
|
||||
|
||||
@underscore_memoization
|
||||
def _parse_function_doc(self):
|
||||
doc = self.py__doc__()
|
||||
if doc is None:
|
||||
return '', ''
|
||||
|
||||
return _parse_function_doc(doc)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
obj = self.obj
|
||||
if inspect.isclass(obj):
|
||||
return 'class'
|
||||
elif inspect.ismodule(obj):
|
||||
return 'module'
|
||||
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
|
||||
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
|
||||
return 'function'
|
||||
# Everything else...
|
||||
return 'instance'
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
"""Imitate the tree.Node.type values."""
|
||||
cls = self._get_class()
|
||||
if inspect.isclass(cls):
|
||||
return 'classdef'
|
||||
elif inspect.ismodule(cls):
|
||||
return 'file_input'
|
||||
elif inspect.isbuiltin(cls) or inspect.ismethod(cls) or \
|
||||
inspect.ismethoddescriptor(cls):
|
||||
return 'funcdef'
|
||||
|
||||
@underscore_memoization
|
||||
def _cls(self):
|
||||
"""
|
||||
We used to limit the lookups for instantiated objects like list(), but
|
||||
this is not the case anymore. Python itself
|
||||
"""
|
||||
# Ensures that a CompiledObject is returned that is not an instance (like list)
|
||||
return self
|
||||
|
||||
def _get_class(self):
|
||||
if not fake.is_class_instance(self.obj) or \
|
||||
inspect.ismethoddescriptor(self.obj): # slots
|
||||
return self.obj
|
||||
|
||||
try:
|
||||
return self.obj.__class__
|
||||
except AttributeError:
|
||||
# happens with numpy.core.umath._UFUNC_API (you get it
|
||||
# automatically by doing `import numpy`.
|
||||
return type
|
||||
|
||||
def get_filters(self, search_global=False, is_instance=False,
|
||||
until_position=None, origin_scope=None):
|
||||
yield self._ensure_one_filter(is_instance)
|
||||
|
||||
@memoize_method
|
||||
def _ensure_one_filter(self, is_instance):
|
||||
"""
|
||||
search_global shouldn't change the fact that there's one dict, this way
|
||||
there's only one `object`.
|
||||
"""
|
||||
return CompiledObjectFilter(self.evaluator, self, is_instance)
|
||||
|
||||
@CheckAttribute
|
||||
def py__getitem__(self, index):
|
||||
if type(self.obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
|
||||
# Get rid of side effects, we won't call custom `__getitem__`s.
|
||||
return set()
|
||||
|
||||
return set([create(self.evaluator, self.obj[index])])
|
||||
|
||||
@CheckAttribute
|
||||
def py__iter__(self):
|
||||
if type(self.obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
|
||||
# Get rid of side effects, we won't call custom `__getitem__`s.
|
||||
return
|
||||
|
||||
for i, part in enumerate(self.obj):
|
||||
if i > 20:
|
||||
# Should not go crazy with large iterators
|
||||
break
|
||||
yield LazyKnownContext(create(self.evaluator, part))
|
||||
|
||||
def py__name__(self):
|
||||
try:
|
||||
return self._get_class().__name__
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
try:
|
||||
name = self._get_class().__name__
|
||||
except AttributeError:
|
||||
name = repr(self.obj)
|
||||
return CompiledContextName(self, name)
|
||||
|
||||
def _execute_function(self, params):
|
||||
from jedi.evaluate import docstrings
|
||||
if self.type != 'funcdef':
|
||||
return
|
||||
for name in self._parse_function_doc()[1].split():
|
||||
try:
|
||||
bltn_obj = getattr(_builtins, name)
|
||||
except AttributeError:
|
||||
continue
|
||||
else:
|
||||
if bltn_obj is None:
|
||||
# We want to evaluate everything except None.
|
||||
# TODO do we?
|
||||
continue
|
||||
bltn_obj = create(self.evaluator, bltn_obj)
|
||||
for result in self.evaluator.execute(bltn_obj, params):
|
||||
yield result
|
||||
for type_ in docstrings.infer_return_types(self):
|
||||
yield type_
|
||||
|
||||
def get_self_attributes(self):
|
||||
return [] # Instance compatibility
|
||||
|
||||
def get_imports(self):
|
||||
return [] # Builtins don't have imports
|
||||
|
||||
def dict_values(self):
|
||||
return set(create(self.evaluator, v) for v in self.obj.values())
|
||||
|
||||
|
||||
class CompiledName(AbstractNameDefinition):
|
||||
def __init__(self, evaluator, parent_context, name):
|
||||
self._evaluator = evaluator
|
||||
self.parent_context = parent_context
|
||||
self.string_name = name
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
name = self.parent_context.name # __name__ is not defined all the time
|
||||
except AttributeError:
|
||||
name = None
|
||||
return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return next(iter(self.infer())).api_type
|
||||
|
||||
@underscore_memoization
|
||||
def infer(self):
|
||||
module = self.parent_context.get_root_context()
|
||||
return [_create_from_name(self._evaluator, module, self.parent_context, self.string_name)]
|
||||
|
||||
|
||||
class SignatureParamName(AbstractNameDefinition):
|
||||
api_type = 'param'
|
||||
|
||||
def __init__(self, compiled_obj, signature_param):
|
||||
self.parent_context = compiled_obj.parent_context
|
||||
self._signature_param = signature_param
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
return self._signature_param.name
|
||||
|
||||
def infer(self):
|
||||
p = self._signature_param
|
||||
evaluator = self.parent_context.evaluator
|
||||
types = set()
|
||||
if p.default is not p.empty:
|
||||
types.add(create(evaluator, p.default))
|
||||
if p.annotation is not p.empty:
|
||||
annotation = create(evaluator, p.annotation)
|
||||
types |= annotation.execute_evaluated()
|
||||
return types
|
||||
|
||||
|
||||
class UnresolvableParamName(AbstractNameDefinition):
|
||||
api_type = 'param'
|
||||
|
||||
def __init__(self, compiled_obj, name):
|
||||
self.parent_context = compiled_obj.parent_context
|
||||
self.string_name = name
|
||||
|
||||
def infer(self):
|
||||
return set()
|
||||
|
||||
|
||||
class CompiledContextName(ContextNameMixin, AbstractNameDefinition):
|
||||
def __init__(self, context, name):
|
||||
self.string_name = name
|
||||
self._context = context
|
||||
self.parent_context = context.parent_context
|
||||
|
||||
|
||||
class EmptyCompiledName(AbstractNameDefinition):
|
||||
"""
|
||||
Accessing some names will raise an exception. To avoid not having any
|
||||
completions, just give Jedi the option to return this object. It infers to
|
||||
nothing.
|
||||
"""
|
||||
def __init__(self, evaluator, name):
|
||||
self.parent_context = evaluator.BUILTINS
|
||||
self.string_name = name
|
||||
|
||||
def infer(self):
|
||||
return []
|
||||
|
||||
|
||||
class CompiledObjectFilter(AbstractFilter):
|
||||
name_class = CompiledName
|
||||
|
||||
def __init__(self, evaluator, compiled_object, is_instance=False):
|
||||
self._evaluator = evaluator
|
||||
self._compiled_object = compiled_object
|
||||
self._is_instance = is_instance
|
||||
|
||||
@memoize_method
|
||||
def get(self, name):
|
||||
name = str(name)
|
||||
obj = self._compiled_object.obj
|
||||
try:
|
||||
attr, is_get_descriptor = getattr_static(obj, name)
|
||||
except AttributeError:
|
||||
return []
|
||||
else:
|
||||
if is_get_descriptor \
|
||||
and not type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
|
||||
# In case of descriptors that have get methods we cannot return
|
||||
# it's value, because that would mean code execution.
|
||||
return [EmptyCompiledName(self._evaluator, name)]
|
||||
if self._is_instance and name not in dir(obj):
|
||||
return []
|
||||
return [self._create_name(name)]
|
||||
|
||||
def values(self):
|
||||
obj = self._compiled_object.obj
|
||||
|
||||
names = []
|
||||
for name in dir(obj):
|
||||
names += self.get(name)
|
||||
|
||||
is_instance = self._is_instance or fake.is_class_instance(obj)
|
||||
# ``dir`` doesn't include the type names.
|
||||
if not inspect.ismodule(obj) and (obj is not type) and not is_instance:
|
||||
for filter in create(self._evaluator, type).get_filters():
|
||||
names += filter.values()
|
||||
return names
|
||||
|
||||
def _create_name(self, name):
|
||||
return self.name_class(self._evaluator, self._compiled_object, name)
|
||||
|
||||
|
||||
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 os.path.basename(fs_path).startswith('__init__.'):
|
||||
# We are calculating the path. __init__ files are not interesting.
|
||||
fs_path = os.path.dirname(fs_path)
|
||||
|
||||
# prefer
|
||||
# - UNIX
|
||||
# /path/to/pythonX.Y/lib-dynload
|
||||
# /path/to/pythonX.Y/site-packages
|
||||
# - Windows
|
||||
# C:\path\to\DLLs
|
||||
# C:\path\to\Lib\site-packages
|
||||
# over
|
||||
# - UNIX
|
||||
# /path/to/pythonX.Y
|
||||
# - Windows
|
||||
# C:\path\to\Lib
|
||||
path = ''
|
||||
for s in sys_path:
|
||||
if (fs_path.startswith(s) and len(path) < len(s)):
|
||||
path = s
|
||||
|
||||
# - Window
|
||||
# X:\path\to\lib-dynload/datetime.pyd => datetime
|
||||
module_path = fs_path[len(path):].lstrip(os.path.sep).lstrip('/')
|
||||
# - Window
|
||||
# Replace like X:\path\to\something/foo/bar.py
|
||||
return _path_re.sub('', module_path).replace(os.path.sep, '.').replace('/', '.')
|
||||
|
||||
|
||||
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, sys_path=sys_path)
|
||||
else:
|
||||
dotted_path = name
|
||||
|
||||
if dotted_path is None:
|
||||
p, _, dotted_path = path.partition(os.path.sep)
|
||||
sys_path.insert(0, p)
|
||||
|
||||
temp, sys.path = sys.path, sys_path
|
||||
try:
|
||||
__import__(dotted_path)
|
||||
except RuntimeError:
|
||||
if 'PySide' in dotted_path or 'PyQt' in dotted_path:
|
||||
# RuntimeError: the PyQt4.QtCore and PyQt5.QtCore modules both wrap
|
||||
# the QObject class.
|
||||
# See https://github.com/davidhalter/jedi/pull/483
|
||||
return None
|
||||
raise
|
||||
except ImportError:
|
||||
# If a module is "corrupt" or not really a Python module or whatever.
|
||||
debug.warning('Module %s not importable in path %s.', dotted_path, path)
|
||||
return None
|
||||
finally:
|
||||
sys.path = temp
|
||||
|
||||
# Just access the cache after import, because of #59 as well as the very
|
||||
# complicated import structure of Python.
|
||||
module = sys.modules[dotted_path]
|
||||
|
||||
return create(evaluator, module)
|
||||
|
||||
|
||||
docstr_defaults = {
|
||||
'floating point number': 'float',
|
||||
'character': 'str',
|
||||
'integer': 'int',
|
||||
'dictionary': 'dict',
|
||||
'string': 'str',
|
||||
}
|
||||
|
||||
|
||||
def _parse_function_doc(doc):
|
||||
"""
|
||||
Takes a function and returns the params and return value as a tuple.
|
||||
This is nothing more than a docstring parser.
|
||||
|
||||
TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
|
||||
TODO docstrings like 'tuple of integers'
|
||||
"""
|
||||
# parse round parentheses: def func(a, (b,c))
|
||||
try:
|
||||
count = 0
|
||||
start = doc.index('(')
|
||||
for i, s in enumerate(doc[start:]):
|
||||
if s == '(':
|
||||
count += 1
|
||||
elif s == ')':
|
||||
count -= 1
|
||||
if count == 0:
|
||||
end = start + i
|
||||
break
|
||||
param_str = doc[start + 1:end]
|
||||
except (ValueError, UnboundLocalError):
|
||||
# ValueError for doc.index
|
||||
# UnboundLocalError for undefined end in last line
|
||||
debug.dbg('no brackets found - no param')
|
||||
end = 0
|
||||
param_str = ''
|
||||
else:
|
||||
# remove square brackets, that show an optional param ( = None)
|
||||
def change_options(m):
|
||||
args = m.group(1).split(',')
|
||||
for i, a in enumerate(args):
|
||||
if a and '=' not in a:
|
||||
args[i] += '=None'
|
||||
return ','.join(args)
|
||||
|
||||
while True:
|
||||
param_str, changes = re.subn(r' ?\[([^\[\]]+)\]',
|
||||
change_options, param_str)
|
||||
if changes == 0:
|
||||
break
|
||||
param_str = param_str.replace('-', '_') # see: isinstance.__doc__
|
||||
|
||||
# parse return value
|
||||
r = re.search('-[>-]* ', doc[end:end + 7])
|
||||
if r is None:
|
||||
ret = ''
|
||||
else:
|
||||
index = end + r.end()
|
||||
# get result type, which can contain newlines
|
||||
pattern = re.compile(r'(,\n|[^\n-])+')
|
||||
ret_str = pattern.match(doc, index).group(0).strip()
|
||||
# New object -> object()
|
||||
ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str)
|
||||
|
||||
ret = docstr_defaults.get(ret_str, ret_str)
|
||||
|
||||
return param_str, ret
|
||||
|
||||
|
||||
def _create_from_name(evaluator, module, compiled_object, name):
|
||||
obj = compiled_object.obj
|
||||
faked = None
|
||||
try:
|
||||
faked = fake.get_faked(evaluator, module, obj, parent_context=compiled_object, name=name)
|
||||
if faked.type == 'funcdef':
|
||||
from jedi.evaluate.representation import FunctionContext
|
||||
return FunctionContext(evaluator, compiled_object, faked)
|
||||
except fake.FakeDoesNotExist:
|
||||
pass
|
||||
|
||||
try:
|
||||
obj = getattr(obj, name)
|
||||
except AttributeError:
|
||||
# Happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
obj = None
|
||||
return create(evaluator, obj, parent_context=compiled_object, faked=faked)
|
||||
|
||||
|
||||
def builtin_from_name(evaluator, string):
|
||||
bltn_obj = getattr(_builtins, string)
|
||||
return create(evaluator, bltn_obj)
|
||||
|
||||
|
||||
def _a_generator(foo):
|
||||
"""Used to have an object to return for generators."""
|
||||
yield 42
|
||||
yield foo
|
||||
|
||||
|
||||
_SPECIAL_OBJECTS = {
|
||||
'FUNCTION_CLASS': type(load_module),
|
||||
'METHOD_CLASS': type(CompiledObject.is_class),
|
||||
'MODULE_CLASS': type(os),
|
||||
'GENERATOR_OBJECT': _a_generator(1.0),
|
||||
'BUILTINS': _builtins,
|
||||
}
|
||||
|
||||
|
||||
def get_special_object(evaluator, identifier):
|
||||
obj = _SPECIAL_OBJECTS[identifier]
|
||||
return create(evaluator, obj, parent_context=create(evaluator, _builtins))
|
||||
|
||||
|
||||
def compiled_objects_cache(attribute_name):
|
||||
def decorator(func):
|
||||
"""
|
||||
This decorator caches just the ids, oopposed to caching the object itself.
|
||||
Caching the id has the advantage that an object doesn't need to be
|
||||
hashable.
|
||||
"""
|
||||
def wrapper(evaluator, obj, parent_context=None, module=None, faked=None):
|
||||
cache = getattr(evaluator, attribute_name)
|
||||
# Do a very cheap form of caching here.
|
||||
key = id(obj), id(parent_context)
|
||||
try:
|
||||
return cache[key][0]
|
||||
except KeyError:
|
||||
# TODO this whole decorator is way too ugly
|
||||
result = func(evaluator, obj, parent_context, module, faked)
|
||||
# Need to cache all of them, otherwise the id could be overwritten.
|
||||
cache[key] = result, obj, parent_context, module, faked
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@compiled_objects_cache('compiled_cache')
|
||||
def create(evaluator, obj, parent_context=None, module=None, faked=None):
|
||||
"""
|
||||
A very weird interface class to this module. The more options provided the
|
||||
more acurate loading compiled objects is.
|
||||
"""
|
||||
if inspect.ismodule(obj):
|
||||
if parent_context is not None:
|
||||
# Modules don't have parents, be careful with caching: recurse.
|
||||
return create(evaluator, obj)
|
||||
else:
|
||||
if parent_context is None and obj is not _builtins:
|
||||
return create(evaluator, obj, create(evaluator, _builtins))
|
||||
|
||||
try:
|
||||
faked = fake.get_faked(evaluator, module, obj, parent_context=parent_context)
|
||||
if faked.type == 'funcdef':
|
||||
from jedi.evaluate.representation import FunctionContext
|
||||
return FunctionContext(evaluator, parent_context, faked)
|
||||
except fake.FakeDoesNotExist:
|
||||
pass
|
||||
|
||||
return CompiledObject(evaluator, obj, parent_context, faked)
|
@ -0,0 +1,213 @@
|
||||
"""
|
||||
Loads functions that are mixed in to the standard library. E.g. builtins are
|
||||
written in C (binaries), but my autocompletion only understands Python code. By
|
||||
mixing in Python code, the autocompletion should work much better for builtins.
|
||||
"""
|
||||
|
||||
import os
|
||||
import inspect
|
||||
import types
|
||||
from itertools import chain
|
||||
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import is_py3, builtins, unicode, is_py34
|
||||
|
||||
modules = {}
|
||||
|
||||
|
||||
MethodDescriptorType = type(str.replace)
|
||||
# These are not considered classes and access is granted even though they have
|
||||
# a __class__ attribute.
|
||||
NOT_CLASS_TYPES = (
|
||||
types.BuiltinFunctionType,
|
||||
types.CodeType,
|
||||
types.FrameType,
|
||||
types.FunctionType,
|
||||
types.GeneratorType,
|
||||
types.GetSetDescriptorType,
|
||||
types.LambdaType,
|
||||
types.MemberDescriptorType,
|
||||
types.MethodType,
|
||||
types.ModuleType,
|
||||
types.TracebackType,
|
||||
MethodDescriptorType
|
||||
)
|
||||
|
||||
if is_py3:
|
||||
NOT_CLASS_TYPES += (
|
||||
types.MappingProxyType,
|
||||
types.SimpleNamespace
|
||||
)
|
||||
if is_py34:
|
||||
NOT_CLASS_TYPES += (types.DynamicClassAttribute,)
|
||||
|
||||
|
||||
class FakeDoesNotExist(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _load_faked_module(grammar, module):
|
||||
module_name = module.__name__
|
||||
if module_name == '__builtin__' and not is_py3:
|
||||
module_name = 'builtins'
|
||||
|
||||
try:
|
||||
return modules[module_name]
|
||||
except KeyError:
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
try:
|
||||
with open(os.path.join(path, 'fake', module_name) + '.pym') as f:
|
||||
source = f.read()
|
||||
except IOError:
|
||||
modules[module_name] = None
|
||||
return
|
||||
modules[module_name] = m = grammar.parse(unicode(source))
|
||||
|
||||
if module_name == 'builtins' and not is_py3:
|
||||
# There are two implementations of `open` for either python 2/3.
|
||||
# -> Rename the python2 version (`look at fake/builtins.pym`).
|
||||
open_func = _search_scope(m, 'open')
|
||||
open_func.children[1].value = 'open_python3'
|
||||
open_func = _search_scope(m, 'open_python2')
|
||||
open_func.children[1].value = 'open'
|
||||
return m
|
||||
|
||||
|
||||
def _search_scope(scope, obj_name):
|
||||
for s in chain(scope.iter_classdefs(), scope.iter_funcdefs()):
|
||||
if s.name.value == obj_name:
|
||||
return s
|
||||
|
||||
|
||||
def get_module(obj):
|
||||
if inspect.ismodule(obj):
|
||||
return obj
|
||||
try:
|
||||
obj = obj.__objclass__
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
try:
|
||||
imp_plz = obj.__module__
|
||||
except AttributeError:
|
||||
# Unfortunately in some cases like `int` there's no __module__
|
||||
return builtins
|
||||
else:
|
||||
if imp_plz is None:
|
||||
# Happens for example in `(_ for _ in []).send.__module__`.
|
||||
return builtins
|
||||
else:
|
||||
try:
|
||||
return __import__(imp_plz)
|
||||
except ImportError:
|
||||
# __module__ can be something arbitrary that doesn't exist.
|
||||
return builtins
|
||||
|
||||
|
||||
def _faked(grammar, module, obj, name):
|
||||
# Crazy underscore actions to try to escape all the internal madness.
|
||||
if module is None:
|
||||
module = get_module(obj)
|
||||
|
||||
faked_mod = _load_faked_module(grammar, module)
|
||||
if faked_mod is None:
|
||||
return None, None
|
||||
|
||||
# Having the module as a `parser.python.tree.Module`, we need to scan
|
||||
# for methods.
|
||||
if name is None:
|
||||
if inspect.isbuiltin(obj) or inspect.isclass(obj):
|
||||
return _search_scope(faked_mod, obj.__name__), faked_mod
|
||||
elif not inspect.isclass(obj):
|
||||
# object is a method or descriptor
|
||||
try:
|
||||
objclass = obj.__objclass__
|
||||
except AttributeError:
|
||||
return None, None
|
||||
else:
|
||||
cls = _search_scope(faked_mod, objclass.__name__)
|
||||
if cls is None:
|
||||
return None, None
|
||||
return _search_scope(cls, obj.__name__), faked_mod
|
||||
else:
|
||||
if obj is module:
|
||||
return _search_scope(faked_mod, name), faked_mod
|
||||
else:
|
||||
try:
|
||||
cls_name = obj.__name__
|
||||
except AttributeError:
|
||||
return None, None
|
||||
cls = _search_scope(faked_mod, cls_name)
|
||||
if cls is None:
|
||||
return None, None
|
||||
return _search_scope(cls, name), faked_mod
|
||||
return None, None
|
||||
|
||||
|
||||
def memoize_faked(obj):
|
||||
"""
|
||||
A typical memoize function that ignores issues with non hashable results.
|
||||
"""
|
||||
cache = obj.cache = {}
|
||||
|
||||
def memoizer(*args, **kwargs):
|
||||
key = (obj, args, frozenset(kwargs.items()))
|
||||
try:
|
||||
result = cache[key]
|
||||
except (TypeError, ValueError):
|
||||
return obj(*args, **kwargs)
|
||||
except KeyError:
|
||||
result = obj(*args, **kwargs)
|
||||
if result is not None:
|
||||
cache[key] = obj(*args, **kwargs)
|
||||
return result
|
||||
else:
|
||||
return result
|
||||
return memoizer
|
||||
|
||||
|
||||
@memoize_faked
|
||||
def _get_faked(grammar, module, obj, name=None):
|
||||
result, fake_module = _faked(grammar, module, obj, name)
|
||||
if result is None:
|
||||
# We're not interested in classes. What we want is functions.
|
||||
raise FakeDoesNotExist
|
||||
elif result.type == 'classdef':
|
||||
return result, fake_module
|
||||
else:
|
||||
# Set the docstr which was previously not set (faked modules don't
|
||||
# contain it).
|
||||
assert result.type == 'funcdef'
|
||||
doc = '"""%s"""' % obj.__doc__ # TODO need escapes.
|
||||
suite = result.children[-1]
|
||||
string = tree.String(doc, (0, 0), '')
|
||||
new_line = tree.Newline('\n', (0, 0))
|
||||
docstr_node = tree.PythonNode('simple_stmt', [string, new_line])
|
||||
suite.children.insert(1, docstr_node)
|
||||
return result, fake_module
|
||||
|
||||
|
||||
def get_faked(evaluator, module, obj, name=None, parent_context=None):
|
||||
if parent_context and parent_context.tree_node is not None:
|
||||
# Try to search in already clearly defined stuff.
|
||||
found = _search_scope(parent_context.tree_node, name)
|
||||
if found is not None:
|
||||
return found
|
||||
else:
|
||||
raise FakeDoesNotExist
|
||||
|
||||
faked, fake_module = _get_faked(evaluator.latest_grammar, module and module.obj, obj, name)
|
||||
if module is not None:
|
||||
module.get_used_names = fake_module.get_used_names
|
||||
return faked
|
||||
|
||||
|
||||
def is_class_instance(obj):
|
||||
"""Like inspect.* methods."""
|
||||
try:
|
||||
cls = obj.__class__
|
||||
except AttributeError:
|
||||
return False
|
||||
else:
|
||||
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)
|
@ -0,0 +1,9 @@
|
||||
class partial():
|
||||
def __init__(self, func, *args, **keywords):
|
||||
self.__func = func
|
||||
self.__args = args
|
||||
self.__keywords = keywords
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# TODO should be **dict(self.__keywords, **kwargs)
|
||||
return self.__func(*(self.__args + args), **self.__keywords)
|
@ -0,0 +1,26 @@
|
||||
def connect(database, timeout=None, isolation_level=None, detect_types=None, factory=None):
|
||||
return Connection()
|
||||
|
||||
|
||||
class Connection():
|
||||
def cursor(self):
|
||||
return Cursor()
|
||||
|
||||
|
||||
class Cursor():
|
||||
def cursor(self):
|
||||
return Cursor()
|
||||
|
||||
def fetchone(self):
|
||||
return Row()
|
||||
|
||||
def fetchmany(self, size=cursor.arraysize):
|
||||
return [self.fetchone()]
|
||||
|
||||
def fetchall(self):
|
||||
return [self.fetchone()]
|
||||
|
||||
|
||||
class Row():
|
||||
def keys(self):
|
||||
return ['']
|
@ -0,0 +1,99 @@
|
||||
def compile():
|
||||
class SRE_Match():
|
||||
endpos = int()
|
||||
lastgroup = int()
|
||||
lastindex = int()
|
||||
pos = int()
|
||||
string = str()
|
||||
regs = ((int(), int()),)
|
||||
|
||||
def __init__(self, pattern):
|
||||
self.re = pattern
|
||||
|
||||
def start(self):
|
||||
return int()
|
||||
|
||||
def end(self):
|
||||
return int()
|
||||
|
||||
def span(self):
|
||||
return int(), int()
|
||||
|
||||
def expand(self):
|
||||
return str()
|
||||
|
||||
def group(self, nr):
|
||||
return str()
|
||||
|
||||
def groupdict(self):
|
||||
return {str(): str()}
|
||||
|
||||
def groups(self):
|
||||
return (str(),)
|
||||
|
||||
class SRE_Pattern():
|
||||
flags = int()
|
||||
groupindex = {}
|
||||
groups = int()
|
||||
pattern = str()
|
||||
|
||||
def findall(self, string, pos=None, endpos=None):
|
||||
"""
|
||||
findall(string[, pos[, endpos]]) --> list.
|
||||
Return a list of all non-overlapping matches of pattern in string.
|
||||
"""
|
||||
return [str()]
|
||||
|
||||
def finditer(self, string, pos=None, endpos=None):
|
||||
"""
|
||||
finditer(string[, pos[, endpos]]) --> iterator.
|
||||
Return an iterator over all non-overlapping matches for the
|
||||
RE pattern in string. For each match, the iterator returns a
|
||||
match object.
|
||||
"""
|
||||
yield SRE_Match(self)
|
||||
|
||||
def match(self, string, pos=None, endpos=None):
|
||||
"""
|
||||
match(string[, pos[, endpos]]) --> match object or None.
|
||||
Matches zero or more characters at the beginning of the string
|
||||
pattern
|
||||
"""
|
||||
return SRE_Match(self)
|
||||
|
||||
def scanner(self, string, pos=None, endpos=None):
|
||||
pass
|
||||
|
||||
def search(self, string, pos=None, endpos=None):
|
||||
"""
|
||||
search(string[, pos[, endpos]]) --> match object or None.
|
||||
Scan through string looking for a match, and return a corresponding
|
||||
MatchObject instance. Return None if no position in the string matches.
|
||||
"""
|
||||
return SRE_Match(self)
|
||||
|
||||
def split(self, string, maxsplit=0]):
|
||||
"""
|
||||
split(string[, maxsplit = 0]) --> list.
|
||||
Split string by the occurrences of pattern.
|
||||
"""
|
||||
return [str()]
|
||||
|
||||
def sub(self, repl, string, count=0):
|
||||
"""
|
||||
sub(repl, string[, count = 0]) --> newstring
|
||||
Return the string obtained by replacing the leftmost non-overlapping
|
||||
occurrences of pattern in string by the replacement repl.
|
||||
"""
|
||||
return str()
|
||||
|
||||
def subn(self, repl, string, count=0):
|
||||
"""
|
||||
subn(repl, string[, count = 0]) --> (newstring, number of subs)
|
||||
Return the tuple (new_string, number_of_subs_made) found by replacing
|
||||
the leftmost non-overlapping occurrences of pattern with the
|
||||
replacement repl.
|
||||
"""
|
||||
return (str(), int())
|
||||
|
||||
return SRE_Pattern()
|
@ -0,0 +1,9 @@
|
||||
def proxy(object, callback=None):
|
||||
return object
|
||||
|
||||
class ref():
|
||||
def __init__(self, object, callback=None):
|
||||
self.__object = object
|
||||
|
||||
def __call__(self):
|
||||
return self.__object
|
@ -0,0 +1,274 @@
|
||||
"""
|
||||
Pure Python implementation of some builtins.
|
||||
This code is not going to be executed anywhere.
|
||||
These implementations are not always correct, but should work as good as
|
||||
possible for the auto completion.
|
||||
"""
|
||||
|
||||
|
||||
def next(iterator, default=None):
|
||||
if random.choice([0, 1]):
|
||||
if hasattr("next"):
|
||||
return iterator.next()
|
||||
else:
|
||||
return iterator.__next__()
|
||||
else:
|
||||
if default is not None:
|
||||
return default
|
||||
|
||||
|
||||
def iter(collection, sentinel=None):
|
||||
if sentinel:
|
||||
yield collection()
|
||||
else:
|
||||
for c in collection:
|
||||
yield c
|
||||
|
||||
|
||||
def range(start, stop=None, step=1):
|
||||
return [0]
|
||||
|
||||
|
||||
class file():
|
||||
def __iter__(self):
|
||||
yield ''
|
||||
|
||||
def next(self):
|
||||
return ''
|
||||
|
||||
def readlines(self):
|
||||
return ['']
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
|
||||
class xrange():
|
||||
# Attention: this function doesn't exist in Py3k (there it is range).
|
||||
def __iter__(self):
|
||||
yield 1
|
||||
|
||||
def count(self):
|
||||
return 1
|
||||
|
||||
def index(self):
|
||||
return 1
|
||||
|
||||
|
||||
def open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True):
|
||||
import io
|
||||
return io.TextIOWrapper(file, mode, buffering, encoding, errors, newline, closefd)
|
||||
|
||||
|
||||
def open_python2(name, mode=None, buffering=None):
|
||||
return file(name, mode, buffering)
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# descriptors
|
||||
#--------------------------------------------------------
|
||||
class property():
|
||||
def __init__(self, fget, fset=None, fdel=None, doc=None):
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
self.fdel = fdel
|
||||
self.__doc__ = doc
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.fget(obj)
|
||||
|
||||
def __set__(self, obj, value):
|
||||
self.fset(obj, value)
|
||||
|
||||
def __delete__(self, obj):
|
||||
self.fdel(obj)
|
||||
|
||||
def setter(self, func):
|
||||
self.fset = func
|
||||
return self
|
||||
|
||||
def getter(self, func):
|
||||
self.fget = func
|
||||
return self
|
||||
|
||||
def deleter(self, func):
|
||||
self.fdel = func
|
||||
return self
|
||||
|
||||
|
||||
class staticmethod():
|
||||
def __init__(self, func):
|
||||
self.__func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.__func
|
||||
|
||||
|
||||
class classmethod():
|
||||
def __init__(self, func):
|
||||
self.__func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
def _method(*args, **kwargs):
|
||||
return self.__func(cls, *args, **kwargs)
|
||||
return _method
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# array stuff
|
||||
#--------------------------------------------------------
|
||||
class list():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = []
|
||||
for i in iterable:
|
||||
self.__iterable += [i]
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def __getitem__(self, y):
|
||||
return self.__iterable[y]
|
||||
|
||||
def pop(self):
|
||||
return self.__iterable[int()]
|
||||
|
||||
|
||||
class tuple():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = []
|
||||
for i in iterable:
|
||||
self.__iterable += [i]
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def __getitem__(self, y):
|
||||
return self.__iterable[y]
|
||||
|
||||
def index(self):
|
||||
return 1
|
||||
|
||||
def count(self):
|
||||
return 1
|
||||
|
||||
|
||||
class set():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = iterable
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def pop(self):
|
||||
return list(self.__iterable)[-1]
|
||||
|
||||
def copy(self):
|
||||
return self
|
||||
|
||||
def difference(self, other):
|
||||
return self - other
|
||||
|
||||
def intersection(self, other):
|
||||
return self & other
|
||||
|
||||
def symmetric_difference(self, other):
|
||||
return self ^ other
|
||||
|
||||
def union(self, other):
|
||||
return self | other
|
||||
|
||||
|
||||
class frozenset():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = iterable
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def copy(self):
|
||||
return self
|
||||
|
||||
|
||||
class dict():
|
||||
def __init__(self, **elements):
|
||||
self.__elements = elements
|
||||
|
||||
def clear(self):
|
||||
# has a strange docstr
|
||||
pass
|
||||
|
||||
def get(self, k, d=None):
|
||||
# TODO implement
|
||||
try:
|
||||
#return self.__elements[k]
|
||||
pass
|
||||
except KeyError:
|
||||
return d
|
||||
|
||||
def values(self):
|
||||
return self.__elements.values()
|
||||
|
||||
def setdefault(self, k, d):
|
||||
# TODO maybe also return the content
|
||||
return d
|
||||
|
||||
|
||||
class enumerate():
|
||||
def __init__(self, sequence, start=0):
|
||||
self.__sequence = sequence
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__sequence:
|
||||
yield 1, i
|
||||
|
||||
def __next__(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
def next(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
|
||||
class reversed():
|
||||
def __init__(self, sequence):
|
||||
self.__sequence = sequence
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__sequence:
|
||||
yield i
|
||||
|
||||
def __next__(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
def next(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
|
||||
def sorted(iterable, cmp=None, key=None, reverse=False):
|
||||
return iterable
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# basic types
|
||||
#--------------------------------------------------------
|
||||
class int():
|
||||
def __init__(self, x, base=None):
|
||||
pass
|
||||
|
||||
|
||||
class str():
|
||||
def __init__(self, obj):
|
||||
pass
|
||||
|
||||
def strip(self):
|
||||
return str()
|
||||
|
||||
def split(self):
|
||||
return [str()]
|
||||
|
||||
class type():
|
||||
def mro():
|
||||
return [object]
|
@ -0,0 +1,4 @@
|
||||
class datetime():
|
||||
@staticmethod
|
||||
def now():
|
||||
return datetime()
|
@ -0,0 +1,12 @@
|
||||
class TextIOWrapper():
|
||||
def __next__(self):
|
||||
return str()
|
||||
|
||||
def __iter__(self):
|
||||
yield str()
|
||||
|
||||
def readlines(self):
|
||||
return ['']
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
@ -0,0 +1,33 @@
|
||||
# Just copied this code from Python 3.6.
|
||||
|
||||
class itemgetter:
|
||||
"""
|
||||
Return a callable object that fetches the given item(s) from its operand.
|
||||
After f = itemgetter(2), the call f(r) returns r[2].
|
||||
After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3])
|
||||
"""
|
||||
__slots__ = ('_items', '_call')
|
||||
|
||||
def __init__(self, item, *items):
|
||||
if not items:
|
||||
self._items = (item,)
|
||||
def func(obj):
|
||||
return obj[item]
|
||||
self._call = func
|
||||
else:
|
||||
self._items = items = (item,) + items
|
||||
def func(obj):
|
||||
return tuple(obj[i] for i in items)
|
||||
self._call = func
|
||||
|
||||
def __call__(self, obj):
|
||||
return self._call(obj)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s.%s(%s)' % (self.__class__.__module__,
|
||||
self.__class__.__name__,
|
||||
', '.join(map(repr, self._items)))
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, self._items
|
||||
|
@ -0,0 +1,5 @@
|
||||
def getcwd():
|
||||
return ''
|
||||
|
||||
def getcwdu():
|
||||
return ''
|
@ -0,0 +1,175 @@
|
||||
"""
|
||||
A static version of getattr.
|
||||
This is a backport of the Python 3 code with a little bit of additional
|
||||
information returned to enable Jedi to make decisions.
|
||||
"""
|
||||
|
||||
import types
|
||||
|
||||
from jedi._compatibility import py_version
|
||||
|
||||
_sentinel = object()
|
||||
|
||||
def _check_instance(obj, attr):
|
||||
instance_dict = {}
|
||||
try:
|
||||
instance_dict = object.__getattribute__(obj, "__dict__")
|
||||
except AttributeError:
|
||||
pass
|
||||
return dict.get(instance_dict, attr, _sentinel)
|
||||
|
||||
|
||||
def _check_class(klass, attr):
|
||||
for entry in _static_getmro(klass):
|
||||
if _shadowed_dict(type(entry)) is _sentinel:
|
||||
try:
|
||||
return entry.__dict__[attr]
|
||||
except KeyError:
|
||||
pass
|
||||
return _sentinel
|
||||
|
||||
def _is_type(obj):
|
||||
try:
|
||||
_static_getmro(obj)
|
||||
except TypeError:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _shadowed_dict_newstyle(klass):
|
||||
dict_attr = type.__dict__["__dict__"]
|
||||
for entry in _static_getmro(klass):
|
||||
try:
|
||||
class_dict = dict_attr.__get__(entry)["__dict__"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
if not (type(class_dict) is types.GetSetDescriptorType and
|
||||
class_dict.__name__ == "__dict__" and
|
||||
class_dict.__objclass__ is entry):
|
||||
return class_dict
|
||||
return _sentinel
|
||||
|
||||
|
||||
def _static_getmro_newstyle(klass):
|
||||
return type.__dict__['__mro__'].__get__(klass)
|
||||
|
||||
|
||||
if py_version >= 30:
|
||||
_shadowed_dict = _shadowed_dict_newstyle
|
||||
_get_type = type
|
||||
_static_getmro = _static_getmro_newstyle
|
||||
else:
|
||||
def _shadowed_dict(klass):
|
||||
"""
|
||||
In Python 2 __dict__ is not overwritable:
|
||||
|
||||
class Foo(object): pass
|
||||
setattr(Foo, '__dict__', 4)
|
||||
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: __dict__ must be a dictionary object
|
||||
|
||||
It applies to both newstyle and oldstyle classes:
|
||||
|
||||
class Foo(object): pass
|
||||
setattr(Foo, '__dict__', 4)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
AttributeError: attribute '__dict__' of 'type' objects is not writable
|
||||
|
||||
It also applies to instances of those objects. However to keep things
|
||||
straight forward, newstyle classes always use the complicated way of
|
||||
accessing it while oldstyle classes just use getattr.
|
||||
"""
|
||||
if type(klass) is _oldstyle_class_type:
|
||||
return getattr(klass, '__dict__', _sentinel)
|
||||
return _shadowed_dict_newstyle(klass)
|
||||
|
||||
class _OldStyleClass():
|
||||
pass
|
||||
|
||||
_oldstyle_instance_type = type(_OldStyleClass())
|
||||
_oldstyle_class_type = type(_OldStyleClass)
|
||||
|
||||
def _get_type(obj):
|
||||
type_ = object.__getattribute__(obj, '__class__')
|
||||
if type_ is _oldstyle_instance_type:
|
||||
# Somehow for old style classes we need to access it directly.
|
||||
return obj.__class__
|
||||
return type_
|
||||
|
||||
def _static_getmro(klass):
|
||||
if type(klass) is _oldstyle_class_type:
|
||||
def oldstyle_mro(klass):
|
||||
"""
|
||||
Oldstyle mro is a really simplistic way of look up mro:
|
||||
https://stackoverflow.com/questions/54867/what-is-the-difference-between-old-style-and-new-style-classes-in-python
|
||||
"""
|
||||
yield klass
|
||||
for base in klass.__bases__:
|
||||
for yield_from in oldstyle_mro(base):
|
||||
yield yield_from
|
||||
|
||||
return oldstyle_mro(klass)
|
||||
|
||||
return _static_getmro_newstyle(klass)
|
||||
|
||||
|
||||
def _safe_hasattr(obj, name):
|
||||
return _check_class(_get_type(obj), name) is not _sentinel
|
||||
|
||||
|
||||
def _safe_is_data_descriptor(obj):
|
||||
return (_safe_hasattr(obj, '__set__') or _safe_hasattr(obj, '__delete__'))
|
||||
|
||||
|
||||
def getattr_static(obj, attr, default=_sentinel):
|
||||
"""Retrieve attributes without triggering dynamic lookup via the
|
||||
descriptor protocol, __getattr__ or __getattribute__.
|
||||
|
||||
Note: this function may not be able to retrieve all attributes
|
||||
that getattr can fetch (like dynamically created attributes)
|
||||
and may find attributes that getattr can't (like descriptors
|
||||
that raise AttributeError). It can also return descriptor objects
|
||||
instead of instance members in some cases. See the
|
||||
documentation for details.
|
||||
|
||||
Returns a tuple `(attr, is_get_descriptor)`. is_get_descripter means that
|
||||
the attribute is a descriptor that has a `__get__` attribute.
|
||||
"""
|
||||
instance_result = _sentinel
|
||||
if not _is_type(obj):
|
||||
klass = _get_type(obj)
|
||||
dict_attr = _shadowed_dict(klass)
|
||||
if (dict_attr is _sentinel or
|
||||
type(dict_attr) is types.MemberDescriptorType):
|
||||
instance_result = _check_instance(obj, attr)
|
||||
else:
|
||||
klass = obj
|
||||
|
||||
klass_result = _check_class(klass, attr)
|
||||
|
||||
if instance_result is not _sentinel and klass_result is not _sentinel:
|
||||
if _safe_hasattr(klass_result, '__get__') \
|
||||
and _safe_is_data_descriptor(klass_result):
|
||||
# A get/set descriptor has priority over everything.
|
||||
return klass_result, True
|
||||
|
||||
if instance_result is not _sentinel:
|
||||
return instance_result, False
|
||||
if klass_result is not _sentinel:
|
||||
return klass_result, _safe_hasattr(klass_result, '__get__')
|
||||
|
||||
if obj is klass:
|
||||
# for types we check the metaclass too
|
||||
for entry in _static_getmro(type(klass)):
|
||||
if _shadowed_dict(type(entry)) is _sentinel:
|
||||
try:
|
||||
return entry.__dict__[attr], False
|
||||
except KeyError:
|
||||
pass
|
||||
if default is not _sentinel:
|
||||
return default, False
|
||||
raise AttributeError(attr)
|
@ -0,0 +1,232 @@
|
||||
"""
|
||||
Used only for REPL Completion.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from jedi import settings
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.cache import underscore_memoization
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.context import Context
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||
|
||||
|
||||
class MixedObject(object):
|
||||
"""
|
||||
A ``MixedObject`` is used in two ways:
|
||||
|
||||
1. It uses the default logic of ``parser.python.tree`` objects,
|
||||
2. except for getattr calls. The names dicts are generated in a fashion
|
||||
like ``CompiledObject``.
|
||||
|
||||
This combined logic makes it possible to provide more powerful REPL
|
||||
completion. It allows side effects that are not noticable with the default
|
||||
parser structure to still be completeable.
|
||||
|
||||
The biggest difference from CompiledObject to MixedObject is that we are
|
||||
generally dealing with Python code and not with C code. This will generate
|
||||
fewer special cases, because we in Python you don't have the same freedoms
|
||||
to modify the runtime.
|
||||
"""
|
||||
def __init__(self, evaluator, parent_context, compiled_object, tree_context):
|
||||
self.evaluator = evaluator
|
||||
self.parent_context = parent_context
|
||||
self.compiled_object = compiled_object
|
||||
self._context = tree_context
|
||||
self.obj = compiled_object.obj
|
||||
|
||||
# We have to overwrite everything that has to do with trailers, name
|
||||
# lookups and filters to make it possible to route name lookups towards
|
||||
# compiled objects and the rest towards tree node contexts.
|
||||
def eval_trailer(*args, **kwags):
|
||||
return Context.eval_trailer(*args, **kwags)
|
||||
|
||||
def py__getattribute__(*args, **kwargs):
|
||||
return Context.py__getattribute__(*args, **kwargs)
|
||||
|
||||
def get_filters(self, *args, **kwargs):
|
||||
yield MixedObjectFilter(self.evaluator, self)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, repr(self.obj))
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._context, name)
|
||||
|
||||
|
||||
class MixedName(compiled.CompiledName):
|
||||
"""
|
||||
The ``CompiledName._compiled_object`` is our MixedObject.
|
||||
"""
|
||||
@property
|
||||
def start_pos(self):
|
||||
contexts = list(self.infer())
|
||||
if not contexts:
|
||||
# This means a start_pos that doesn't exist (compiled objects).
|
||||
return (0, 0)
|
||||
return contexts[0].name.start_pos
|
||||
|
||||
@start_pos.setter
|
||||
def start_pos(self, value):
|
||||
# Ignore the __init__'s start_pos setter call.
|
||||
pass
|
||||
|
||||
@underscore_memoization
|
||||
def infer(self):
|
||||
obj = self.parent_context.obj
|
||||
try:
|
||||
# TODO use logic from compiled.CompiledObjectFilter
|
||||
obj = getattr(obj, self.string_name)
|
||||
except AttributeError:
|
||||
# Happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
obj = None
|
||||
return [_create(self._evaluator, obj, parent_context=self.parent_context)]
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return next(iter(self.infer())).api_type
|
||||
|
||||
|
||||
class MixedObjectFilter(compiled.CompiledObjectFilter):
|
||||
name_class = MixedName
|
||||
|
||||
def __init__(self, evaluator, mixed_object, is_instance=False):
|
||||
super(MixedObjectFilter, self).__init__(
|
||||
evaluator, mixed_object, is_instance)
|
||||
self._mixed_object = mixed_object
|
||||
|
||||
#def _create(self, name):
|
||||
#return MixedName(self._evaluator, self._compiled_object, name)
|
||||
|
||||
|
||||
@evaluator_function_cache()
|
||||
def _load_module(evaluator, path, python_object):
|
||||
module = evaluator.grammar.parse(
|
||||
path=path,
|
||||
cache=True,
|
||||
diff_cache=True,
|
||||
cache_path=settings.cache_directory
|
||||
).get_root_node()
|
||||
python_module = inspect.getmodule(python_object)
|
||||
|
||||
evaluator.modules[python_module.__name__] = module
|
||||
return module
|
||||
|
||||
|
||||
def _get_object_to_check(python_object):
|
||||
"""Check if inspect.getfile has a chance to find the source."""
|
||||
if (inspect.ismodule(python_object) or
|
||||
inspect.isclass(python_object) or
|
||||
inspect.ismethod(python_object) or
|
||||
inspect.isfunction(python_object) or
|
||||
inspect.istraceback(python_object) or
|
||||
inspect.isframe(python_object) or
|
||||
inspect.iscode(python_object)):
|
||||
return python_object
|
||||
|
||||
try:
|
||||
return python_object.__class__
|
||||
except AttributeError:
|
||||
raise TypeError # Prevents computation of `repr` within inspect.
|
||||
|
||||
|
||||
def find_syntax_node_name(evaluator, python_object):
|
||||
try:
|
||||
python_object = _get_object_to_check(python_object)
|
||||
path = inspect.getsourcefile(python_object)
|
||||
except TypeError:
|
||||
# The type might not be known (e.g. class_with_dict.__weakref__)
|
||||
return None, None
|
||||
if path is None or not os.path.exists(path):
|
||||
# The path might not exist or be e.g. <stdin>.
|
||||
return None, None
|
||||
|
||||
module = _load_module(evaluator, path, python_object)
|
||||
|
||||
if inspect.ismodule(python_object):
|
||||
# We don't need to check names for modules, because there's not really
|
||||
# a way to write a module in a module in Python (and also __name__ can
|
||||
# be something like ``email.utils``).
|
||||
return module, path
|
||||
|
||||
try:
|
||||
name_str = python_object.__name__
|
||||
except AttributeError:
|
||||
# Stuff like python_function.__code__.
|
||||
return None, None
|
||||
|
||||
if name_str == '<lambda>':
|
||||
return None, None # It's too hard to find lambdas.
|
||||
|
||||
# Doesn't always work (e.g. os.stat_result)
|
||||
try:
|
||||
names = module.get_used_names()[name_str]
|
||||
except KeyError:
|
||||
return None, None
|
||||
names = [n for n in names if n.is_definition()]
|
||||
|
||||
try:
|
||||
code = python_object.__code__
|
||||
# By using the line number of a code object we make the lookup in a
|
||||
# file pretty easy. There's still a possibility of people defining
|
||||
# stuff like ``a = 3; foo(a); a = 4`` on the same line, but if people
|
||||
# do so we just don't care.
|
||||
line_nr = code.co_firstlineno
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
line_names = [name for name in names if name.start_pos[0] == line_nr]
|
||||
# There's a chance that the object is not available anymore, because
|
||||
# the code has changed in the background.
|
||||
if line_names:
|
||||
return line_names[-1].parent, path
|
||||
|
||||
# It's really hard to actually get the right definition, here as a last
|
||||
# resort we just return the last one. This chance might lead to odd
|
||||
# completions at some points but will lead to mostly correct type
|
||||
# inference, because people tend to define a public name in a module only
|
||||
# once.
|
||||
return names[-1].parent, path
|
||||
|
||||
|
||||
@compiled.compiled_objects_cache('mixed_cache')
|
||||
def _create(evaluator, obj, parent_context=None, *args):
|
||||
tree_node, path = find_syntax_node_name(evaluator, obj)
|
||||
|
||||
compiled_object = compiled.create(
|
||||
evaluator, obj, parent_context=parent_context.compiled_object)
|
||||
if tree_node is None:
|
||||
return compiled_object
|
||||
|
||||
module_node = tree_node.get_root_node()
|
||||
if parent_context.tree_node.get_root_node() == module_node:
|
||||
module_context = parent_context.get_root_context()
|
||||
else:
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
module_context = ModuleContext(evaluator, module_node, path=path)
|
||||
# TODO this __name__ is probably wrong.
|
||||
name = compiled_object.get_root_context().py__name__()
|
||||
imports.add_module(evaluator, name, module_context)
|
||||
|
||||
tree_context = module_context.create_context(
|
||||
tree_node,
|
||||
node_is_context=True,
|
||||
node_is_object=True
|
||||
)
|
||||
if tree_node.type == 'classdef':
|
||||
if not inspect.isclass(obj):
|
||||
# Is an instance, not a class.
|
||||
tree_context, = tree_context.execute_evaluated()
|
||||
|
||||
return MixedObject(
|
||||
evaluator,
|
||||
parent_context,
|
||||
compiled_object,
|
||||
tree_context=tree_context
|
||||
)
|
||||
|
@ -0,0 +1,206 @@
|
||||
from jedi._compatibility import Python3Method
|
||||
from jedi.common import unite
|
||||
from parso.python.tree import ExprStmt, CompFor
|
||||
from jedi.parser_utils import clean_scope_docstring, get_doc_with_call_signature
|
||||
|
||||
|
||||
class Context(object):
|
||||
"""
|
||||
Should be defined, otherwise the API returns empty types.
|
||||
"""
|
||||
|
||||
"""
|
||||
To be defined by subclasses.
|
||||
"""
|
||||
predefined_names = {}
|
||||
tree_node = None
|
||||
|
||||
def __init__(self, evaluator, parent_context=None):
|
||||
self.evaluator = evaluator
|
||||
self.parent_context = parent_context
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
# By default just lower name of the class. Can and should be
|
||||
# overwritten.
|
||||
return self.__class__.__name__.lower()
|
||||
|
||||
def get_root_context(self):
|
||||
context = self
|
||||
while True:
|
||||
if context.parent_context is None:
|
||||
return context
|
||||
context = context.parent_context
|
||||
|
||||
def execute(self, arguments):
|
||||
return self.evaluator.execute(self, arguments)
|
||||
|
||||
def execute_evaluated(self, *value_list):
|
||||
"""
|
||||
Execute a function with already executed arguments.
|
||||
"""
|
||||
from jedi.evaluate.param import ValuesArguments
|
||||
arguments = ValuesArguments([[value] for value in value_list])
|
||||
return self.execute(arguments)
|
||||
|
||||
def eval_node(self, node):
|
||||
return self.evaluator.eval_element(self, node)
|
||||
|
||||
def eval_stmt(self, stmt, seek_name=None):
|
||||
return self.evaluator.eval_statement(self, stmt, seek_name)
|
||||
|
||||
def eval_trailer(self, types, trailer):
|
||||
return self.evaluator.eval_trailer(self, types, trailer)
|
||||
|
||||
@Python3Method
|
||||
def py__getattribute__(self, name_or_str, name_context=None, position=None,
|
||||
search_global=False, is_goto=False,
|
||||
analysis_errors=True):
|
||||
if name_context is None:
|
||||
name_context = self
|
||||
return self.evaluator.find_types(
|
||||
self, name_or_str, name_context, position, search_global, is_goto,
|
||||
analysis_errors)
|
||||
|
||||
def create_context(self, node, node_is_context=False, node_is_object=False):
|
||||
return self.evaluator.create_context(self, node, node_is_context, node_is_object)
|
||||
|
||||
def is_class(self):
|
||||
return False
|
||||
|
||||
def py__bool__(self):
|
||||
"""
|
||||
Since Wrapper is a super class for classes, functions and modules,
|
||||
the return value will always be true.
|
||||
"""
|
||||
return True
|
||||
|
||||
def py__doc__(self, include_call_signature=False):
|
||||
try:
|
||||
self.tree_node.get_doc_node
|
||||
except AttributeError:
|
||||
return ''
|
||||
else:
|
||||
if include_call_signature:
|
||||
return get_doc_with_call_signature(self.tree_node)
|
||||
else:
|
||||
return clean_scope_docstring(self.tree_node)
|
||||
return None
|
||||
|
||||
|
||||
class TreeContext(Context):
|
||||
def __init__(self, evaluator, parent_context=None):
|
||||
super(TreeContext, self).__init__(evaluator, parent_context)
|
||||
self.predefined_names = {}
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
|
||||
|
||||
|
||||
class AbstractLazyContext(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.data)
|
||||
|
||||
def infer(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class LazyKnownContext(AbstractLazyContext):
|
||||
"""data is a context."""
|
||||
def infer(self):
|
||||
return set([self.data])
|
||||
|
||||
|
||||
class LazyKnownContexts(AbstractLazyContext):
|
||||
"""data is a set of contexts."""
|
||||
def infer(self):
|
||||
return self.data
|
||||
|
||||
|
||||
class LazyUnknownContext(AbstractLazyContext):
|
||||
def __init__(self):
|
||||
super(LazyUnknownContext, self).__init__(None)
|
||||
|
||||
def infer(self):
|
||||
return set()
|
||||
|
||||
|
||||
class LazyTreeContext(AbstractLazyContext):
|
||||
def __init__(self, context, node):
|
||||
super(LazyTreeContext, self).__init__(node)
|
||||
self._context = context
|
||||
# We need to save the predefined names. It's an unfortunate side effect
|
||||
# that needs to be tracked otherwise results will be wrong.
|
||||
self._predefined_names = dict(context.predefined_names)
|
||||
|
||||
def infer(self):
|
||||
old, self._context.predefined_names = \
|
||||
self._context.predefined_names, self._predefined_names
|
||||
try:
|
||||
return self._context.eval_node(self.data)
|
||||
finally:
|
||||
self._context.predefined_names = old
|
||||
|
||||
|
||||
def get_merged_lazy_context(lazy_contexts):
|
||||
if len(lazy_contexts) > 1:
|
||||
return MergedLazyContexts(lazy_contexts)
|
||||
else:
|
||||
return lazy_contexts[0]
|
||||
|
||||
|
||||
class MergedLazyContexts(AbstractLazyContext):
|
||||
"""data is a list of lazy contexts."""
|
||||
def infer(self):
|
||||
return unite(l.infer() for l in self.data)
|
||||
|
||||
|
||||
class ContextualizedNode(object):
|
||||
def __init__(self, context, node):
|
||||
self.context = context
|
||||
self._node = node
|
||||
|
||||
def get_root_context(self):
|
||||
return self.context.get_root_context()
|
||||
|
||||
def infer(self):
|
||||
return self.context.eval_node(self._node)
|
||||
|
||||
|
||||
class ContextualizedName(ContextualizedNode):
|
||||
# TODO merge with TreeNameDefinition?!
|
||||
@property
|
||||
def name(self):
|
||||
return self._node
|
||||
|
||||
def assignment_indexes(self):
|
||||
"""
|
||||
Returns an array of tuple(int, node) of the indexes that are used in
|
||||
tuple assignments.
|
||||
|
||||
For example if the name is ``y`` in the following code::
|
||||
|
||||
x, (y, z) = 2, ''
|
||||
|
||||
would result in ``[(1, xyz_node), (0, yz_node)]``.
|
||||
"""
|
||||
indexes = []
|
||||
node = self._node.parent
|
||||
compare = self._node
|
||||
while node is not None:
|
||||
if node.type in ('testlist', 'testlist_comp', 'testlist_star_expr', 'exprlist'):
|
||||
for i, child in enumerate(node.children):
|
||||
if child == compare:
|
||||
indexes.insert(0, (int(i / 2), node))
|
||||
break
|
||||
else:
|
||||
raise LookupError("Couldn't find the assignment.")
|
||||
elif isinstance(node, (ExprStmt, CompFor)):
|
||||
break
|
||||
|
||||
compare = node
|
||||
node = node.parent
|
||||
return indexes
|
@ -0,0 +1,282 @@
|
||||
"""
|
||||
Docstrings are another source of information for functions and classes.
|
||||
:mod:`jedi.evaluate.dynamic` tries to find all executions of functions, while
|
||||
the docstring parsing is much easier. There are three different types of
|
||||
docstrings that |jedi| understands:
|
||||
|
||||
- `Sphinx <http://sphinx-doc.org/markup/desc.html#info-field-lists>`_
|
||||
- `Epydoc <http://epydoc.sourceforge.net/manual-fields.html>`_
|
||||
- `Numpydoc <https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt>`_
|
||||
|
||||
For example, the sphinx annotation ``:type foo: str`` clearly states that the
|
||||
type of ``foo`` is ``str``.
|
||||
|
||||
As an addition to parameter searching, this module also provides return
|
||||
annotations.
|
||||
"""
|
||||
|
||||
import re
|
||||
from textwrap import dedent
|
||||
|
||||
from parso import parse
|
||||
|
||||
from jedi._compatibility import u
|
||||
from jedi.common import unite
|
||||
from jedi.evaluate import context
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.common import indent_block
|
||||
from jedi.evaluate.iterable import SequenceLiteralContext, FakeSequence
|
||||
|
||||
|
||||
DOCSTRING_PARAM_PATTERNS = [
|
||||
r'\s*:type\s+%s:\s*([^\n]+)', # Sphinx
|
||||
r'\s*:param\s+(\w+)\s+%s:[^\n]*', # Sphinx param with type
|
||||
r'\s*@type\s+%s:\s*([^\n]+)', # Epydoc
|
||||
]
|
||||
|
||||
DOCSTRING_RETURN_PATTERNS = [
|
||||
re.compile(r'\s*:rtype:\s*([^\n]+)', re.M), # Sphinx
|
||||
re.compile(r'\s*@rtype:\s*([^\n]+)', re.M), # Epydoc
|
||||
]
|
||||
|
||||
REST_ROLE_PATTERN = re.compile(r':[^`]+:`([^`]+)`')
|
||||
|
||||
|
||||
try:
|
||||
from numpydoc.docscrape import NumpyDocString
|
||||
except ImportError:
|
||||
def _search_param_in_numpydocstr(docstr, param_str):
|
||||
return []
|
||||
|
||||
def _search_return_in_numpydocstr(docstr):
|
||||
return []
|
||||
else:
|
||||
def _search_param_in_numpydocstr(docstr, param_str):
|
||||
"""Search `docstr` (in numpydoc format) for type(-s) of `param_str`."""
|
||||
try:
|
||||
# This is a non-public API. If it ever changes we should be
|
||||
# prepared and return gracefully.
|
||||
params = NumpyDocString(docstr)._parsed_data['Parameters']
|
||||
except (KeyError, AttributeError):
|
||||
return []
|
||||
for p_name, p_type, p_descr in params:
|
||||
if p_name == param_str:
|
||||
m = re.match('([^,]+(,[^,]+)*?)(,[ ]*optional)?$', p_type)
|
||||
if m:
|
||||
p_type = m.group(1)
|
||||
return list(_expand_typestr(p_type))
|
||||
return []
|
||||
|
||||
def _search_return_in_numpydocstr(docstr):
|
||||
"""
|
||||
Search `docstr` (in numpydoc format) for type(-s) of function returns.
|
||||
"""
|
||||
doc = NumpyDocString(docstr)
|
||||
try:
|
||||
# This is a non-public API. If it ever changes we should be
|
||||
# prepared and return gracefully.
|
||||
returns = doc._parsed_data['Returns']
|
||||
returns += doc._parsed_data['Yields']
|
||||
except (KeyError, AttributeError):
|
||||
raise StopIteration
|
||||
for r_name, r_type, r_descr in returns:
|
||||
#Return names are optional and if so the type is in the name
|
||||
if not r_type:
|
||||
r_type = r_name
|
||||
for type_ in _expand_typestr(r_type):
|
||||
yield type_
|
||||
|
||||
|
||||
def _expand_typestr(type_str):
|
||||
"""
|
||||
Attempts to interpret the possible types in `type_str`
|
||||
"""
|
||||
# Check if alternative types are specified with 'or'
|
||||
if re.search('\\bor\\b', type_str):
|
||||
for t in type_str.split('or'):
|
||||
yield t.split('of')[0].strip()
|
||||
# Check if like "list of `type`" and set type to list
|
||||
elif re.search('\\bof\\b', type_str):
|
||||
yield type_str.split('of')[0]
|
||||
# Check if type has is a set of valid literal values eg: {'C', 'F', 'A'}
|
||||
elif type_str.startswith('{'):
|
||||
node = parse(type_str, version='3.6').children[0]
|
||||
if node.type == 'atom':
|
||||
for leaf in node.children[1].children:
|
||||
if leaf.type == 'number':
|
||||
if '.' in leaf.value:
|
||||
yield 'float'
|
||||
else:
|
||||
yield 'int'
|
||||
elif leaf.type == 'string':
|
||||
if 'b' in leaf.string_prefix.lower():
|
||||
yield 'bytes'
|
||||
else:
|
||||
yield 'str'
|
||||
# Ignore everything else.
|
||||
|
||||
# Otherwise just work with what we have.
|
||||
else:
|
||||
yield type_str
|
||||
|
||||
|
||||
def _search_param_in_docstr(docstr, param_str):
|
||||
"""
|
||||
Search `docstr` for type(-s) of `param_str`.
|
||||
|
||||
>>> _search_param_in_docstr(':type param: int', 'param')
|
||||
['int']
|
||||
>>> _search_param_in_docstr('@type param: int', 'param')
|
||||
['int']
|
||||
>>> _search_param_in_docstr(
|
||||
... ':type param: :class:`threading.Thread`', 'param')
|
||||
['threading.Thread']
|
||||
>>> bool(_search_param_in_docstr('no document', 'param'))
|
||||
False
|
||||
>>> _search_param_in_docstr(':param int param: some description', 'param')
|
||||
['int']
|
||||
|
||||
"""
|
||||
# look at #40 to see definitions of those params
|
||||
patterns = [re.compile(p % re.escape(param_str))
|
||||
for p in DOCSTRING_PARAM_PATTERNS]
|
||||
for pattern in patterns:
|
||||
match = pattern.search(docstr)
|
||||
if match:
|
||||
return [_strip_rst_role(match.group(1))]
|
||||
|
||||
return (_search_param_in_numpydocstr(docstr, param_str) or
|
||||
[])
|
||||
|
||||
|
||||
def _strip_rst_role(type_str):
|
||||
"""
|
||||
Strip off the part looks like a ReST role in `type_str`.
|
||||
|
||||
>>> _strip_rst_role(':class:`ClassName`') # strip off :class:
|
||||
'ClassName'
|
||||
>>> _strip_rst_role(':py:obj:`module.Object`') # works with domain
|
||||
'module.Object'
|
||||
>>> _strip_rst_role('ClassName') # do nothing when not ReST role
|
||||
'ClassName'
|
||||
|
||||
See also:
|
||||
http://sphinx-doc.org/domains.html#cross-referencing-python-objects
|
||||
|
||||
"""
|
||||
match = REST_ROLE_PATTERN.match(type_str)
|
||||
if match:
|
||||
return match.group(1)
|
||||
else:
|
||||
return type_str
|
||||
|
||||
|
||||
def _evaluate_for_statement_string(module_context, string):
|
||||
code = dedent(u("""
|
||||
def pseudo_docstring_stuff():
|
||||
'''
|
||||
Create a pseudo function for docstring statements.
|
||||
Need this docstring so that if the below part is not valid Python this
|
||||
is still a function.
|
||||
'''
|
||||
{0}
|
||||
"""))
|
||||
if string is None:
|
||||
return []
|
||||
|
||||
for element in re.findall('((?:\w+\.)*\w+)\.', string):
|
||||
# Try to import module part in dotted name.
|
||||
# (e.g., 'threading' in 'threading.Thread').
|
||||
string = 'import %s\n' % element + string
|
||||
|
||||
# Take the default grammar here, if we load the Python 2.7 grammar here, it
|
||||
# will be impossible to use `...` (Ellipsis) as a token. Docstring types
|
||||
# don't need to conform with the current grammar.
|
||||
grammar = module_context.evaluator.latest_grammar
|
||||
module = grammar.parse(code.format(indent_block(string)))
|
||||
try:
|
||||
funcdef = next(module.iter_funcdefs())
|
||||
# First pick suite, then simple_stmt and then the node,
|
||||
# which is also not the last item, because there's a newline.
|
||||
stmt = funcdef.children[-1].children[-1].children[-2]
|
||||
except (AttributeError, IndexError):
|
||||
return []
|
||||
|
||||
from jedi.evaluate.representation import FunctionContext
|
||||
function_context = FunctionContext(
|
||||
module_context.evaluator,
|
||||
module_context,
|
||||
funcdef
|
||||
)
|
||||
func_execution_context = function_context.get_function_execution()
|
||||
# Use the module of the param.
|
||||
# TODO this module is not the module of the param in case of a function
|
||||
# call. In that case it's the module of the function call.
|
||||
# stuffed with content from a function call.
|
||||
return list(_execute_types_in_stmt(func_execution_context, stmt))
|
||||
|
||||
|
||||
def _execute_types_in_stmt(module_context, stmt):
|
||||
"""
|
||||
Executing all types or general elements that we find in a statement. This
|
||||
doesn't include tuple, list and dict literals, because the stuff they
|
||||
contain is executed. (Used as type information).
|
||||
"""
|
||||
definitions = module_context.eval_node(stmt)
|
||||
return unite(_execute_array_values(module_context.evaluator, d) for d in definitions)
|
||||
|
||||
|
||||
def _execute_array_values(evaluator, array):
|
||||
"""
|
||||
Tuples indicate that there's not just one return value, but the listed
|
||||
ones. `(str, int)` means that it returns a tuple with both types.
|
||||
"""
|
||||
if isinstance(array, SequenceLiteralContext):
|
||||
values = []
|
||||
for lazy_context in array.py__iter__():
|
||||
objects = unite(_execute_array_values(evaluator, typ) for typ in lazy_context.infer())
|
||||
values.append(context.LazyKnownContexts(objects))
|
||||
return set([FakeSequence(evaluator, array.array_type, values)])
|
||||
else:
|
||||
return array.execute_evaluated()
|
||||
|
||||
|
||||
@evaluator_method_cache()
|
||||
def infer_param(execution_context, param):
|
||||
from jedi.evaluate.instance import AnonymousInstanceFunctionExecution
|
||||
|
||||
def eval_docstring(docstring):
|
||||
return set(
|
||||
p
|
||||
for param_str in _search_param_in_docstr(docstring, param.name.value)
|
||||
for p in _evaluate_for_statement_string(module_context, param_str)
|
||||
)
|
||||
module_context = execution_context.get_root_context()
|
||||
func = param.get_parent_function()
|
||||
if func.type == 'lambdef':
|
||||
return set()
|
||||
|
||||
types = eval_docstring(execution_context.py__doc__())
|
||||
if isinstance(execution_context, AnonymousInstanceFunctionExecution) and \
|
||||
execution_context.function_context.name.string_name == '__init__':
|
||||
class_context = execution_context.instance.class_context
|
||||
types |= eval_docstring(class_context.py__doc__())
|
||||
|
||||
return types
|
||||
|
||||
|
||||
@evaluator_method_cache()
|
||||
def infer_return_types(function_context):
|
||||
def search_return_in_docstr(code):
|
||||
for p in DOCSTRING_RETURN_PATTERNS:
|
||||
match = p.search(code)
|
||||
if match:
|
||||
yield _strip_rst_role(match.group(1))
|
||||
# Check for numpy style return hint
|
||||
for type_ in _search_return_in_numpydocstr(code):
|
||||
yield type_
|
||||
|
||||
for type_str in search_return_in_docstr(function_context.py__doc__()):
|
||||
for type_eval in _evaluate_for_statement_string(function_context.get_root_context(), type_str):
|
||||
yield type_eval
|
||||
|
@ -0,0 +1,212 @@
|
||||
"""
|
||||
One of the really important features of |jedi| is to have an option to
|
||||
understand code like this::
|
||||
|
||||
def foo(bar):
|
||||
bar. # completion here
|
||||
foo(1)
|
||||
|
||||
There's no doubt wheter bar is an ``int`` or not, but if there's also a call
|
||||
like ``foo('str')``, what would happen? Well, we'll just show both. Because
|
||||
that's what a human would expect.
|
||||
|
||||
It works as follows:
|
||||
|
||||
- |Jedi| sees a param
|
||||
- search for function calls named ``foo``
|
||||
- execute these calls and check the input. This work with a ``ParamListener``.
|
||||
"""
|
||||
|
||||
from parso.python import tree
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.param import TreeArguments, create_default_params
|
||||
from jedi.evaluate.helpers import is_stdlib_path
|
||||
from jedi.common import to_list, unite
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
MAX_PARAM_SEARCHES = 20
|
||||
|
||||
|
||||
class ParamListener(object):
|
||||
"""
|
||||
This listener is used to get the params for a function.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.param_possibilities = []
|
||||
|
||||
def execute(self, params):
|
||||
self.param_possibilities += params
|
||||
|
||||
|
||||
class MergedExecutedParams(object):
|
||||
"""
|
||||
Simulates being a parameter while actually just being multiple params.
|
||||
"""
|
||||
def __init__(self, executed_params):
|
||||
self._executed_params = executed_params
|
||||
|
||||
def infer(self):
|
||||
return unite(p.infer() for p in self._executed_params)
|
||||
|
||||
|
||||
@debug.increase_indent
|
||||
def search_params(evaluator, execution_context, funcdef):
|
||||
"""
|
||||
A dynamic search for param values. If you try to complete a type:
|
||||
|
||||
>>> def func(foo):
|
||||
... foo
|
||||
>>> func(1)
|
||||
>>> func("")
|
||||
|
||||
It is not known what the type ``foo`` without analysing the whole code. You
|
||||
have to look for all calls to ``func`` to find out what ``foo`` possibly
|
||||
is.
|
||||
"""
|
||||
if not settings.dynamic_params:
|
||||
return create_default_params(execution_context, funcdef)
|
||||
|
||||
evaluator.dynamic_params_depth += 1
|
||||
try:
|
||||
path = execution_context.get_root_context().py__file__()
|
||||
if path is not None and is_stdlib_path(path):
|
||||
# We don't want to search for usages in the stdlib. Usually people
|
||||
# don't work with it (except if you are a core maintainer, sorry).
|
||||
# This makes everything slower. Just disable it and run the tests,
|
||||
# you will see the slowdown, especially in 3.6.
|
||||
return create_default_params(execution_context, funcdef)
|
||||
|
||||
debug.dbg('Dynamic param search in %s.', funcdef.name.value, color='MAGENTA')
|
||||
|
||||
module_context = execution_context.get_root_context()
|
||||
function_executions = _search_function_executions(
|
||||
evaluator,
|
||||
module_context,
|
||||
funcdef
|
||||
)
|
||||
if function_executions:
|
||||
zipped_params = zip(*list(
|
||||
function_execution.get_params()
|
||||
for function_execution in function_executions
|
||||
))
|
||||
params = [MergedExecutedParams(executed_params) for executed_params in zipped_params]
|
||||
# Evaluate the ExecutedParams to types.
|
||||
else:
|
||||
return create_default_params(execution_context, funcdef)
|
||||
debug.dbg('Dynamic param result finished', color='MAGENTA')
|
||||
return params
|
||||
finally:
|
||||
evaluator.dynamic_params_depth -= 1
|
||||
|
||||
|
||||
@evaluator_function_cache(default=[])
|
||||
@to_list
|
||||
def _search_function_executions(evaluator, module_context, funcdef):
|
||||
"""
|
||||
Returns a list of param names.
|
||||
"""
|
||||
from jedi.evaluate import representation as er
|
||||
|
||||
func_string_name = funcdef.name.value
|
||||
compare_node = funcdef
|
||||
if func_string_name == '__init__':
|
||||
cls = get_parent_scope(funcdef)
|
||||
if isinstance(cls, tree.Class):
|
||||
func_string_name = cls.name.value
|
||||
compare_node = cls
|
||||
|
||||
found_executions = False
|
||||
i = 0
|
||||
for for_mod_context in imports.get_modules_containing_name(
|
||||
evaluator, [module_context], func_string_name):
|
||||
if not isinstance(module_context, er.ModuleContext):
|
||||
return
|
||||
for name, trailer in _get_possible_nodes(for_mod_context, func_string_name):
|
||||
i += 1
|
||||
|
||||
# This is a simple way to stop Jedi's dynamic param recursion
|
||||
# from going wild: The deeper Jedi's in the recursion, the less
|
||||
# code should be evaluated.
|
||||
if i * evaluator.dynamic_params_depth > MAX_PARAM_SEARCHES:
|
||||
return
|
||||
|
||||
random_context = evaluator.create_context(for_mod_context, name)
|
||||
for function_execution in _check_name_for_execution(
|
||||
evaluator, random_context, compare_node, name, trailer):
|
||||
found_executions = True
|
||||
yield function_execution
|
||||
|
||||
# If there are results after processing a module, we're probably
|
||||
# good to process. This is a speed optimization.
|
||||
if found_executions:
|
||||
return
|
||||
|
||||
|
||||
def _get_possible_nodes(module_context, func_string_name):
|
||||
try:
|
||||
names = module_context.tree_node.get_used_names()[func_string_name]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
for name in names:
|
||||
bracket = name.get_next_leaf()
|
||||
trailer = bracket.parent
|
||||
if trailer.type == 'trailer' and bracket == '(':
|
||||
yield name, trailer
|
||||
|
||||
|
||||
def _check_name_for_execution(evaluator, context, compare_node, name, trailer):
|
||||
from jedi.evaluate import representation as er, instance
|
||||
|
||||
def create_func_excs():
|
||||
arglist = trailer.children[1]
|
||||
if arglist == ')':
|
||||
arglist = ()
|
||||
args = TreeArguments(evaluator, context, arglist, trailer)
|
||||
if value_node.type == 'funcdef':
|
||||
yield value.get_function_execution(args)
|
||||
else:
|
||||
created_instance = instance.TreeInstance(
|
||||
evaluator,
|
||||
value.parent_context,
|
||||
value,
|
||||
args
|
||||
)
|
||||
for execution in created_instance.create_init_executions():
|
||||
yield execution
|
||||
|
||||
for value in evaluator.goto_definitions(context, name):
|
||||
value_node = value.tree_node
|
||||
if compare_node == value_node:
|
||||
for func_execution in create_func_excs():
|
||||
yield func_execution
|
||||
elif isinstance(value.parent_context, er.FunctionExecutionContext) and \
|
||||
compare_node.type == 'funcdef':
|
||||
# Here we're trying to find decorators by checking the first
|
||||
# parameter. It's not very generic though. Should find a better
|
||||
# solution that also applies to nested decorators.
|
||||
params = value.parent_context.get_params()
|
||||
if len(params) != 1:
|
||||
continue
|
||||
values = params[0].infer()
|
||||
nodes = [v.tree_node for v in values]
|
||||
if nodes == [compare_node]:
|
||||
# Found a decorator.
|
||||
module_context = context.get_root_context()
|
||||
execution_context = next(create_func_excs())
|
||||
for name, trailer in _get_possible_nodes(module_context, params[0].string_name):
|
||||
if value_node.start_pos < name.start_pos < value_node.end_pos:
|
||||
random_context = evaluator.create_context(execution_context, name)
|
||||
iterator = _check_name_for_execution(
|
||||
evaluator,
|
||||
random_context,
|
||||
compare_node,
|
||||
name,
|
||||
trailer
|
||||
)
|
||||
for function_execution in iterator:
|
||||
yield function_execution
|
@ -0,0 +1,345 @@
|
||||
"""
|
||||
Filters are objects that you can use to filter names in different scopes. They
|
||||
are needed for name resolution.
|
||||
"""
|
||||
from abc import abstractmethod
|
||||
|
||||
from parso.tree import search_ancestor
|
||||
from jedi.evaluate import flow_analysis
|
||||
from jedi.common import to_list, unite
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
class AbstractNameDefinition(object):
|
||||
start_pos = None
|
||||
string_name = None
|
||||
parent_context = None
|
||||
tree_name = None
|
||||
|
||||
@abstractmethod
|
||||
def infer(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def goto(self):
|
||||
# Typically names are already definitions and therefore a goto on that
|
||||
# name will always result on itself.
|
||||
return set([self])
|
||||
|
||||
def get_root_context(self):
|
||||
return self.parent_context.get_root_context()
|
||||
|
||||
def __repr__(self):
|
||||
if self.start_pos is None:
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.string_name)
|
||||
return '<%s: %s@%s>' % (self.__class__.__name__, self.string_name, self.start_pos)
|
||||
|
||||
def execute(self, arguments):
|
||||
return unite(context.execute(arguments) for context in self.infer())
|
||||
|
||||
def execute_evaluated(self, *args, **kwargs):
|
||||
return unite(context.execute_evaluated(*args, **kwargs) for context in self.infer())
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return self.parent_context.api_type
|
||||
|
||||
|
||||
class AbstractTreeName(AbstractNameDefinition):
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
self.tree_name = tree_name
|
||||
|
||||
def goto(self):
|
||||
return self.parent_context.evaluator.goto(self.parent_context, self.tree_name)
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
return self.tree_name.value
|
||||
|
||||
@property
|
||||
def start_pos(self):
|
||||
return self.tree_name.start_pos
|
||||
|
||||
|
||||
class ContextNameMixin(object):
|
||||
def infer(self):
|
||||
return set([self._context])
|
||||
|
||||
def get_root_context(self):
|
||||
if self.parent_context is None:
|
||||
return self._context
|
||||
return super(ContextNameMixin, self).get_root_context()
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return self._context.api_type
|
||||
|
||||
|
||||
class ContextName(ContextNameMixin, AbstractTreeName):
|
||||
def __init__(self, context, tree_name):
|
||||
super(ContextName, self).__init__(context.parent_context, tree_name)
|
||||
self._context = context
|
||||
|
||||
|
||||
class TreeNameDefinition(AbstractTreeName):
|
||||
_API_TYPES = dict(
|
||||
import_name='module',
|
||||
import_from='module',
|
||||
funcdef='function',
|
||||
param='param',
|
||||
classdef='class',
|
||||
)
|
||||
|
||||
def infer(self):
|
||||
# Refactor this, should probably be here.
|
||||
from jedi.evaluate.finder import _name_to_types
|
||||
return _name_to_types(self.parent_context.evaluator, self.parent_context, self.tree_name)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
definition = self.tree_name.get_definition(import_name_always=True)
|
||||
if definition is None:
|
||||
return 'statement'
|
||||
return self._API_TYPES.get(definition.type, 'statement')
|
||||
|
||||
|
||||
class ParamName(AbstractTreeName):
|
||||
api_type = 'param'
|
||||
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
self.tree_name = tree_name
|
||||
|
||||
def infer(self):
|
||||
return self.get_param().infer()
|
||||
|
||||
def get_param(self):
|
||||
params = self.parent_context.get_params()
|
||||
param_node = search_ancestor(self.tree_name, 'param')
|
||||
return params[param_node.position_index]
|
||||
|
||||
|
||||
class AnonymousInstanceParamName(ParamName):
|
||||
def infer(self):
|
||||
param_node = search_ancestor(self.tree_name, 'param')
|
||||
# TODO I think this should not belong here. It's not even really true,
|
||||
# because classmethod and other descriptors can change it.
|
||||
if param_node.position_index == 0:
|
||||
# This is a speed optimization, to return the self param (because
|
||||
# it's known). This only affects anonymous instances.
|
||||
return set([self.parent_context.instance])
|
||||
else:
|
||||
return self.get_param().infer()
|
||||
|
||||
|
||||
class AbstractFilter(object):
|
||||
_until_position = None
|
||||
|
||||
def _filter(self, names):
|
||||
if self._until_position is not None:
|
||||
return [n for n in names if n.start_pos < self._until_position]
|
||||
return names
|
||||
|
||||
@abstractmethod
|
||||
def get(self, name):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def values(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractUsedNamesFilter(AbstractFilter):
|
||||
name_class = TreeNameDefinition
|
||||
|
||||
def __init__(self, context, parser_scope):
|
||||
self._parser_scope = parser_scope
|
||||
self._used_names = self._parser_scope.get_root_node().get_used_names()
|
||||
self.context = context
|
||||
|
||||
def get(self, name):
|
||||
try:
|
||||
names = self._used_names[str(name)]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
return self._convert_names(self._filter(names))
|
||||
|
||||
def _convert_names(self, names):
|
||||
return [self.name_class(self.context, name) for name in names]
|
||||
|
||||
def values(self):
|
||||
return self._convert_names(name for name_list in self._used_names.values()
|
||||
for name in self._filter(name_list))
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.context)
|
||||
|
||||
|
||||
class ParserTreeFilter(AbstractUsedNamesFilter):
|
||||
def __init__(self, evaluator, context, node_context=None, until_position=None,
|
||||
origin_scope=None):
|
||||
"""
|
||||
node_context is an option to specify a second context for use cases
|
||||
like the class mro where the parent class of a new name would be the
|
||||
context, but for some type inference it's important to have a local
|
||||
context of the other classes.
|
||||
"""
|
||||
if node_context is None:
|
||||
node_context = context
|
||||
super(ParserTreeFilter, self).__init__(context, node_context.tree_node)
|
||||
self._node_context = node_context
|
||||
self._origin_scope = origin_scope
|
||||
self._until_position = until_position
|
||||
|
||||
def _filter(self, names):
|
||||
names = super(ParserTreeFilter, self)._filter(names)
|
||||
names = [n for n in names if self._is_name_reachable(n)]
|
||||
return list(self._check_flows(names))
|
||||
|
||||
def _is_name_reachable(self, name):
|
||||
if not name.is_definition():
|
||||
return False
|
||||
parent = name.parent
|
||||
if parent.type == 'trailer':
|
||||
return False
|
||||
base_node = parent if parent.type in ('classdef', 'funcdef') else name
|
||||
return get_parent_scope(base_node) == self._parser_scope
|
||||
|
||||
def _check_flows(self, names):
|
||||
for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
|
||||
check = flow_analysis.reachability_check(
|
||||
self._node_context, self._parser_scope, name, self._origin_scope
|
||||
)
|
||||
if check is not flow_analysis.UNREACHABLE:
|
||||
yield name
|
||||
|
||||
if check is flow_analysis.REACHABLE:
|
||||
break
|
||||
|
||||
|
||||
class FunctionExecutionFilter(ParserTreeFilter):
|
||||
param_name = ParamName
|
||||
|
||||
def __init__(self, evaluator, context, node_context=None,
|
||||
until_position=None, origin_scope=None):
|
||||
super(FunctionExecutionFilter, self).__init__(
|
||||
evaluator,
|
||||
context,
|
||||
node_context,
|
||||
until_position,
|
||||
origin_scope
|
||||
)
|
||||
|
||||
@to_list
|
||||
def _convert_names(self, names):
|
||||
for name in names:
|
||||
param = search_ancestor(name, 'param')
|
||||
if param:
|
||||
yield self.param_name(self.context, name)
|
||||
else:
|
||||
yield TreeNameDefinition(self.context, name)
|
||||
|
||||
|
||||
class AnonymousInstanceFunctionExecutionFilter(FunctionExecutionFilter):
|
||||
param_name = AnonymousInstanceParamName
|
||||
|
||||
|
||||
class GlobalNameFilter(AbstractUsedNamesFilter):
|
||||
def __init__(self, context, parser_scope):
|
||||
super(GlobalNameFilter, self).__init__(context, parser_scope)
|
||||
|
||||
@to_list
|
||||
def _filter(self, names):
|
||||
for name in names:
|
||||
if name.parent.type == 'global_stmt':
|
||||
yield name
|
||||
|
||||
|
||||
class DictFilter(AbstractFilter):
|
||||
def __init__(self, dct):
|
||||
self._dct = dct
|
||||
|
||||
def get(self, name):
|
||||
try:
|
||||
value = self._convert(name, self._dct[str(name)])
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
return list(self._filter([value]))
|
||||
|
||||
def values(self):
|
||||
return self._filter(self._convert(*item) for item in self._dct.items())
|
||||
|
||||
def _convert(self, name, value):
|
||||
return value
|
||||
|
||||
|
||||
def get_global_filters(evaluator, context, until_position, origin_scope):
|
||||
"""
|
||||
Returns all filters in order of priority for name resolution.
|
||||
|
||||
For global name lookups. The filters will handle name resolution
|
||||
themselves, but here we gather possible filters downwards.
|
||||
|
||||
>>> from jedi._compatibility import u, no_unicode_pprint
|
||||
>>> from jedi import Script
|
||||
>>> script = Script(u('''
|
||||
... x = ['a', 'b', 'c']
|
||||
... def func():
|
||||
... y = None
|
||||
... '''))
|
||||
>>> module_node = script._get_module_node()
|
||||
>>> scope = next(module_node.iter_funcdefs())
|
||||
>>> scope
|
||||
<Function: func@3-5>
|
||||
>>> context = script._get_module().create_context(scope)
|
||||
>>> filters = list(get_global_filters(context.evaluator, context, (4, 0), None))
|
||||
|
||||
First we get the names names from the function scope.
|
||||
|
||||
>>> no_unicode_pprint(filters[0])
|
||||
<ParserTreeFilter: <ModuleContext: @2-5>>
|
||||
>>> sorted(str(n) for n in filters[0].values())
|
||||
['<TreeNameDefinition: func@(3, 4)>', '<TreeNameDefinition: x@(2, 0)>']
|
||||
>>> filters[0]._until_position
|
||||
(4, 0)
|
||||
|
||||
Then it yields the names from one level "lower". In this example, this is
|
||||
the module scope. As a side note, you can see, that the position in the
|
||||
filter is now None, because typically the whole module is loaded before the
|
||||
function is called.
|
||||
|
||||
>>> filters[1].values() # global names -> there are none in our example.
|
||||
[]
|
||||
>>> list(filters[2].values()) # package modules -> Also empty.
|
||||
[]
|
||||
>>> sorted(name.string_name for name in filters[3].values()) # Module attributes
|
||||
['__doc__', '__file__', '__name__', '__package__']
|
||||
>>> print(filters[1]._until_position)
|
||||
None
|
||||
|
||||
Finally, it yields the builtin filter, if `include_builtin` is
|
||||
true (default).
|
||||
|
||||
>>> filters[4].values() #doctest: +ELLIPSIS
|
||||
[<CompiledName: ...>, ...]
|
||||
"""
|
||||
from jedi.evaluate.representation import FunctionExecutionContext
|
||||
while context is not None:
|
||||
# Names in methods cannot be resolved within the class.
|
||||
for filter in context.get_filters(
|
||||
search_global=True,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope):
|
||||
yield filter
|
||||
if isinstance(context, FunctionExecutionContext):
|
||||
# The position should be reset if the current scope is a function.
|
||||
until_position = None
|
||||
|
||||
context = context.parent_context
|
||||
|
||||
# Add builtins to the global scope.
|
||||
for filter in evaluator.BUILTINS.get_filters(search_global=True):
|
||||
yield filter
|
@ -0,0 +1,410 @@
|
||||
"""
|
||||
Searching for names with given scope and name. This is very central in Jedi and
|
||||
Python. The name resolution is quite complicated with descripter,
|
||||
``__getattribute__``, ``__getattr__``, ``global``, etc.
|
||||
|
||||
If you want to understand name resolution, please read the first few chapters
|
||||
in http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/.
|
||||
|
||||
Flow checks
|
||||
+++++++++++
|
||||
|
||||
Flow checks are not really mature. There's only a check for ``isinstance``. It
|
||||
would check whether a flow has the form of ``if isinstance(a, type_or_tuple)``.
|
||||
Unfortunately every other thing is being ignored (e.g. a == '' would be easy to
|
||||
check for -> a is a string). There's big potential in these checks.
|
||||
"""
|
||||
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor
|
||||
from jedi import debug
|
||||
from jedi.common import unite
|
||||
from jedi import settings
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate.instance import AbstractInstanceContext
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import pep0484
|
||||
from jedi.evaluate import iterable
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate import flow_analysis
|
||||
from jedi.evaluate import param
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.filters import get_global_filters, TreeNameDefinition
|
||||
from jedi.evaluate.context import ContextualizedName, ContextualizedNode
|
||||
from jedi.parser_utils import is_scope, get_parent_scope
|
||||
|
||||
|
||||
class NameFinder(object):
|
||||
def __init__(self, evaluator, context, name_context, name_or_str,
|
||||
position=None, analysis_errors=True):
|
||||
self._evaluator = evaluator
|
||||
# Make sure that it's not just a syntax tree node.
|
||||
self._context = context
|
||||
self._name_context = name_context
|
||||
self._name = name_or_str
|
||||
if isinstance(name_or_str, tree.Name):
|
||||
self._string_name = name_or_str.value
|
||||
else:
|
||||
self._string_name = name_or_str
|
||||
self._position = position
|
||||
self._found_predefined_types = None
|
||||
self._analysis_errors = analysis_errors
|
||||
|
||||
@debug.increase_indent
|
||||
def find(self, filters, attribute_lookup):
|
||||
"""
|
||||
:params bool attribute_lookup: Tell to logic if we're accessing the
|
||||
attribute or the contents of e.g. a function.
|
||||
"""
|
||||
names = self.filter_name(filters)
|
||||
if self._found_predefined_types is not None and names:
|
||||
check = flow_analysis.reachability_check(
|
||||
self._context, self._context.tree_node, self._name)
|
||||
if check is flow_analysis.UNREACHABLE:
|
||||
return set()
|
||||
return self._found_predefined_types
|
||||
|
||||
types = self._names_to_types(names, attribute_lookup)
|
||||
|
||||
if not names and self._analysis_errors and not types \
|
||||
and not (isinstance(self._name, tree.Name) and
|
||||
isinstance(self._name.parent.parent, tree.Param)):
|
||||
if isinstance(self._name, tree.Name):
|
||||
if attribute_lookup:
|
||||
analysis.add_attribute_error(
|
||||
self._name_context, self._context, self._name)
|
||||
else:
|
||||
message = ("NameError: name '%s' is not defined."
|
||||
% self._string_name)
|
||||
analysis.add(self._name_context, 'name-error', self._name, message)
|
||||
|
||||
return types
|
||||
|
||||
def _get_origin_scope(self):
|
||||
if isinstance(self._name, tree.Name):
|
||||
scope = self._name
|
||||
while scope.parent is not None:
|
||||
# TODO why if classes?
|
||||
if not isinstance(scope, tree.Scope):
|
||||
break
|
||||
scope = scope.parent
|
||||
return scope
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_filters(self, search_global=False):
|
||||
origin_scope = self._get_origin_scope()
|
||||
if search_global:
|
||||
return get_global_filters(self._evaluator, self._context, self._position, origin_scope)
|
||||
else:
|
||||
return self._context.get_filters(search_global, self._position, origin_scope=origin_scope)
|
||||
|
||||
def filter_name(self, filters):
|
||||
"""
|
||||
Searches names that are defined in a scope (the different
|
||||
``filters``), until a name fits.
|
||||
"""
|
||||
names = []
|
||||
if self._context.predefined_names:
|
||||
# TODO is this ok? node might not always be a tree.Name
|
||||
node = self._name
|
||||
while node is not None and not is_scope(node):
|
||||
node = node.parent
|
||||
if node.type in ("if_stmt", "for_stmt", "comp_for"):
|
||||
try:
|
||||
name_dict = self._context.predefined_names[node]
|
||||
types = name_dict[self._string_name]
|
||||
except KeyError:
|
||||
continue
|
||||
else:
|
||||
self._found_predefined_types = types
|
||||
break
|
||||
|
||||
for filter in filters:
|
||||
names = filter.get(self._string_name)
|
||||
if names:
|
||||
if len(names) == 1:
|
||||
n, = names
|
||||
if isinstance(n, TreeNameDefinition):
|
||||
# Something somewhere went terribly wrong. This
|
||||
# typically happens when using goto on an import in an
|
||||
# __init__ file. I think we need a better solution, but
|
||||
# it's kind of hard, because for Jedi it's not clear
|
||||
# that that name has not been defined, yet.
|
||||
if n.tree_name == self._name:
|
||||
if self._name.get_definition().type == 'import_from':
|
||||
continue
|
||||
break
|
||||
|
||||
debug.dbg('finder.filter_name "%s" in (%s): %s@%s', self._string_name,
|
||||
self._context, names, self._position)
|
||||
return list(names)
|
||||
|
||||
def _check_getattr(self, inst):
|
||||
"""Checks for both __getattr__ and __getattribute__ methods"""
|
||||
# str is important, because it shouldn't be `Name`!
|
||||
name = compiled.create(self._evaluator, self._string_name)
|
||||
|
||||
# This is a little bit special. `__getattribute__` is in Python
|
||||
# executed before `__getattr__`. But: I know no use case, where
|
||||
# this could be practical and where Jedi would return wrong types.
|
||||
# If you ever find something, let me know!
|
||||
# We are inversing this, because a hand-crafted `__getattribute__`
|
||||
# could still call another hand-crafted `__getattr__`, but not the
|
||||
# other way around.
|
||||
names = (inst.get_function_slot_names('__getattr__') or
|
||||
inst.get_function_slot_names('__getattribute__'))
|
||||
return inst.execute_function_slots(names, name)
|
||||
|
||||
def _names_to_types(self, names, attribute_lookup):
|
||||
types = set()
|
||||
|
||||
types = unite(name.infer() for name in names)
|
||||
|
||||
debug.dbg('finder._names_to_types: %s -> %s', names, types)
|
||||
if not names and isinstance(self._context, AbstractInstanceContext):
|
||||
# handling __getattr__ / __getattribute__
|
||||
return self._check_getattr(self._context)
|
||||
|
||||
# Add isinstance and other if/assert knowledge.
|
||||
if not types and isinstance(self._name, tree.Name) and \
|
||||
not isinstance(self._name_context, AbstractInstanceContext):
|
||||
flow_scope = self._name
|
||||
base_node = self._name_context.tree_node
|
||||
if base_node.type == 'comp_for':
|
||||
return types
|
||||
while True:
|
||||
flow_scope = get_parent_scope(flow_scope, include_flows=True)
|
||||
n = _check_flow_information(self._name_context, flow_scope,
|
||||
self._name, self._position)
|
||||
if n is not None:
|
||||
return n
|
||||
if flow_scope == base_node:
|
||||
break
|
||||
return types
|
||||
|
||||
|
||||
def _name_to_types(evaluator, context, tree_name):
|
||||
types = []
|
||||
node = tree_name.get_definition(import_name_always=True)
|
||||
if node is None:
|
||||
node = tree_name.parent
|
||||
if node.type == 'global_stmt':
|
||||
context = evaluator.create_context(context, tree_name)
|
||||
finder = NameFinder(evaluator, context, context, tree_name.value)
|
||||
filters = finder.get_filters(search_global=True)
|
||||
# For global_stmt lookups, we only need the first possible scope,
|
||||
# which means the function itself.
|
||||
filters = [next(filters)]
|
||||
return finder.find(filters, attribute_lookup=False)
|
||||
elif node.type not in ('import_from', 'import_name'):
|
||||
raise ValueError("Should not happen.")
|
||||
|
||||
typ = node.type
|
||||
if typ == 'for_stmt':
|
||||
types = pep0484.find_type_from_comment_hint_for(context, node, tree_name)
|
||||
if types:
|
||||
return types
|
||||
if typ == 'with_stmt':
|
||||
types = pep0484.find_type_from_comment_hint_with(context, node, tree_name)
|
||||
if types:
|
||||
return types
|
||||
if typ in ('for_stmt', 'comp_for'):
|
||||
try:
|
||||
types = context.predefined_names[node][tree_name.value]
|
||||
except KeyError:
|
||||
cn = ContextualizedNode(context, node.children[3])
|
||||
for_types = iterable.py__iter__types(evaluator, cn.infer(), cn)
|
||||
c_node = ContextualizedName(context, tree_name)
|
||||
types = check_tuple_assignments(evaluator, c_node, for_types)
|
||||
elif typ == 'expr_stmt':
|
||||
types = _remove_statements(evaluator, context, node, tree_name)
|
||||
elif typ == 'with_stmt':
|
||||
context_managers = context.eval_node(node.get_test_node_from_name(tree_name))
|
||||
enter_methods = unite(
|
||||
context_manager.py__getattribute__('__enter__')
|
||||
for context_manager in context_managers
|
||||
)
|
||||
types = unite(method.execute_evaluated() for method in enter_methods)
|
||||
elif typ in ('import_from', 'import_name'):
|
||||
types = imports.infer_import(context, tree_name)
|
||||
elif typ in ('funcdef', 'classdef'):
|
||||
types = _apply_decorators(evaluator, context, node)
|
||||
elif typ == 'try_stmt':
|
||||
# TODO an exception can also be a tuple. Check for those.
|
||||
# TODO check for types that are not classes and add it to
|
||||
# the static analysis report.
|
||||
exceptions = context.eval_node(tree_name.get_previous_sibling().get_previous_sibling())
|
||||
types = unite(
|
||||
evaluator.execute(t, param.ValuesArguments([]))
|
||||
for t in exceptions
|
||||
)
|
||||
else:
|
||||
raise ValueError("Should not happen.")
|
||||
return types
|
||||
|
||||
|
||||
def _apply_decorators(evaluator, context, node):
|
||||
"""
|
||||
Returns the function, that should to be executed in the end.
|
||||
This is also the places where the decorators are processed.
|
||||
"""
|
||||
if node.type == 'classdef':
|
||||
decoratee_context = er.ClassContext(
|
||||
evaluator,
|
||||
parent_context=context,
|
||||
classdef=node
|
||||
)
|
||||
else:
|
||||
decoratee_context = er.FunctionContext(
|
||||
evaluator,
|
||||
parent_context=context,
|
||||
funcdef=node
|
||||
)
|
||||
initial = values = set([decoratee_context])
|
||||
for dec in reversed(node.get_decorators()):
|
||||
debug.dbg('decorator: %s %s', dec, values)
|
||||
dec_values = context.eval_node(dec.children[1])
|
||||
trailer_nodes = dec.children[2:-1]
|
||||
if trailer_nodes:
|
||||
# Create a trailer and evaluate it.
|
||||
trailer = tree.PythonNode('trailer', trailer_nodes)
|
||||
trailer.parent = dec
|
||||
dec_values = evaluator.eval_trailer(context, dec_values, trailer)
|
||||
|
||||
if not len(dec_values):
|
||||
debug.warning('decorator not found: %s on %s', dec, node)
|
||||
return initial
|
||||
|
||||
values = unite(dec_value.execute(param.ValuesArguments([values]))
|
||||
for dec_value in dec_values)
|
||||
if not len(values):
|
||||
debug.warning('not possible to resolve wrappers found %s', node)
|
||||
return initial
|
||||
|
||||
debug.dbg('decorator end %s', values)
|
||||
return values
|
||||
|
||||
|
||||
def _remove_statements(evaluator, context, stmt, name):
|
||||
"""
|
||||
This is the part where statements are being stripped.
|
||||
|
||||
Due to lazy evaluation, statements like a = func; b = a; b() have to be
|
||||
evaluated.
|
||||
"""
|
||||
types = set()
|
||||
check_instance = None
|
||||
|
||||
pep0484types = \
|
||||
pep0484.find_type_from_comment_hint_assign(context, stmt, name)
|
||||
if pep0484types:
|
||||
return pep0484types
|
||||
types |= context.eval_stmt(stmt, seek_name=name)
|
||||
|
||||
if check_instance is not None:
|
||||
# class renames
|
||||
types = set([er.get_instance_el(evaluator, check_instance, a, True)
|
||||
if isinstance(a, er.Function) else a for a in types])
|
||||
return types
|
||||
|
||||
|
||||
def _check_flow_information(context, flow, search_name, pos):
|
||||
""" Try to find out the type of a variable just with the information that
|
||||
is given by the flows: e.g. It is also responsible for assert checks.::
|
||||
|
||||
if isinstance(k, str):
|
||||
k. # <- completion here
|
||||
|
||||
ensures that `k` is a string.
|
||||
"""
|
||||
if not settings.dynamic_flow_information:
|
||||
return None
|
||||
|
||||
result = None
|
||||
if is_scope(flow):
|
||||
# Check for asserts.
|
||||
module_node = flow.get_root_node()
|
||||
try:
|
||||
names = module_node.get_used_names()[search_name.value]
|
||||
except KeyError:
|
||||
return None
|
||||
names = reversed([
|
||||
n for n in names
|
||||
if flow.start_pos <= n.start_pos < (pos or flow.end_pos)
|
||||
])
|
||||
|
||||
for name in names:
|
||||
ass = search_ancestor(name, 'assert_stmt')
|
||||
if ass is not None:
|
||||
result = _check_isinstance_type(context, ass.assertion, search_name)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
if flow.type in ('if_stmt', 'while_stmt'):
|
||||
potential_ifs = [c for c in flow.children[1::4] if c != ':']
|
||||
for if_test in reversed(potential_ifs):
|
||||
if search_name.start_pos > if_test.end_pos:
|
||||
return _check_isinstance_type(context, if_test, search_name)
|
||||
return result
|
||||
|
||||
|
||||
def _check_isinstance_type(context, element, search_name):
|
||||
try:
|
||||
assert element.type in ('power', 'atom_expr')
|
||||
# this might be removed if we analyze and, etc
|
||||
assert len(element.children) == 2
|
||||
first, trailer = element.children
|
||||
assert first.type == 'name' and first.value == 'isinstance'
|
||||
assert trailer.type == 'trailer' and trailer.children[0] == '('
|
||||
assert len(trailer.children) == 3
|
||||
|
||||
# arglist stuff
|
||||
arglist = trailer.children[1]
|
||||
args = param.TreeArguments(context.evaluator, context, arglist, trailer)
|
||||
param_list = list(args.unpack())
|
||||
# Disallow keyword arguments
|
||||
assert len(param_list) == 2
|
||||
(key1, lazy_context_object), (key2, lazy_context_cls) = param_list
|
||||
assert key1 is None and key2 is None
|
||||
call = helpers.call_of_leaf(search_name)
|
||||
is_instance_call = helpers.call_of_leaf(lazy_context_object.data)
|
||||
# Do a simple get_code comparison. They should just have the same code,
|
||||
# and everything will be all right.
|
||||
normalize = context.evaluator.grammar._normalize
|
||||
assert normalize(is_instance_call) == normalize(call)
|
||||
except AssertionError:
|
||||
return None
|
||||
|
||||
result = set()
|
||||
for cls_or_tup in lazy_context_cls.infer():
|
||||
if isinstance(cls_or_tup, iterable.AbstractSequence) and \
|
||||
cls_or_tup.array_type == 'tuple':
|
||||
for lazy_context in cls_or_tup.py__iter__():
|
||||
for context in lazy_context.infer():
|
||||
result |= context.execute_evaluated()
|
||||
else:
|
||||
result |= cls_or_tup.execute_evaluated()
|
||||
return result
|
||||
|
||||
|
||||
def check_tuple_assignments(evaluator, contextualized_name, types):
|
||||
"""
|
||||
Checks if tuples are assigned.
|
||||
"""
|
||||
lazy_context = None
|
||||
for index, node in contextualized_name.assignment_indexes():
|
||||
cn = ContextualizedNode(contextualized_name.context, node)
|
||||
iterated = iterable.py__iter__(evaluator, types, cn)
|
||||
for _ in range(index + 1):
|
||||
try:
|
||||
lazy_context = next(iterated)
|
||||
except StopIteration:
|
||||
# We could do this with the default param in next. But this
|
||||
# would allow this loop to run for a very long time if the
|
||||
# index number is high. Therefore break if the loop is
|
||||
# finished.
|
||||
return set()
|
||||
types = lazy_context.infer()
|
||||
return types
|
@ -0,0 +1,112 @@
|
||||
from jedi.parser_utils import get_flow_branch_keyword, is_scope, get_parent_scope
|
||||
|
||||
|
||||
class Status(object):
|
||||
lookup_table = {}
|
||||
|
||||
def __init__(self, value, name):
|
||||
self._value = value
|
||||
self._name = name
|
||||
Status.lookup_table[value] = self
|
||||
|
||||
def invert(self):
|
||||
if self is REACHABLE:
|
||||
return UNREACHABLE
|
||||
elif self is UNREACHABLE:
|
||||
return REACHABLE
|
||||
else:
|
||||
return UNSURE
|
||||
|
||||
def __and__(self, other):
|
||||
if UNSURE in (self, other):
|
||||
return UNSURE
|
||||
else:
|
||||
return REACHABLE if self._value and other._value else UNREACHABLE
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self._name)
|
||||
|
||||
|
||||
REACHABLE = Status(True, 'reachable')
|
||||
UNREACHABLE = Status(False, 'unreachable')
|
||||
UNSURE = Status(None, 'unsure')
|
||||
|
||||
|
||||
def _get_flow_scopes(node):
|
||||
while True:
|
||||
node = get_parent_scope(node, include_flows=True)
|
||||
if node is None or is_scope(node):
|
||||
return
|
||||
yield node
|
||||
|
||||
|
||||
def reachability_check(context, context_scope, node, origin_scope=None):
|
||||
first_flow_scope = get_parent_scope(node, include_flows=True)
|
||||
if origin_scope is not None:
|
||||
origin_flow_scopes = list(_get_flow_scopes(origin_scope))
|
||||
node_flow_scopes = list(_get_flow_scopes(node))
|
||||
|
||||
branch_matches = True
|
||||
for flow_scope in origin_flow_scopes:
|
||||
if flow_scope in node_flow_scopes:
|
||||
node_keyword = get_flow_branch_keyword(flow_scope, node)
|
||||
origin_keyword = get_flow_branch_keyword(flow_scope, origin_scope)
|
||||
branch_matches = node_keyword == origin_keyword
|
||||
if flow_scope.type == 'if_stmt':
|
||||
if not branch_matches:
|
||||
return UNREACHABLE
|
||||
elif flow_scope.type == 'try_stmt':
|
||||
if not branch_matches and origin_keyword == 'else' \
|
||||
and node_keyword == 'except':
|
||||
return UNREACHABLE
|
||||
break
|
||||
|
||||
# Direct parents get resolved, we filter scopes that are separate
|
||||
# branches. This makes sense for autocompletion and static analysis.
|
||||
# For actual Python it doesn't matter, because we're talking about
|
||||
# potentially unreachable code.
|
||||
# e.g. `if 0:` would cause all name lookup within the flow make
|
||||
# unaccessible. This is not a "problem" in Python, because the code is
|
||||
# never called. In Jedi though, we still want to infer types.
|
||||
while origin_scope is not None:
|
||||
if first_flow_scope == origin_scope and branch_matches:
|
||||
return REACHABLE
|
||||
origin_scope = origin_scope.parent
|
||||
|
||||
return _break_check(context, context_scope, first_flow_scope, node)
|
||||
|
||||
|
||||
def _break_check(context, context_scope, flow_scope, node):
|
||||
reachable = REACHABLE
|
||||
if flow_scope.type == 'if_stmt':
|
||||
if flow_scope.is_node_after_else(node):
|
||||
for check_node in flow_scope.get_test_nodes():
|
||||
reachable = _check_if(context, check_node)
|
||||
if reachable in (REACHABLE, UNSURE):
|
||||
break
|
||||
reachable = reachable.invert()
|
||||
else:
|
||||
flow_node = flow_scope.get_corresponding_test_node(node)
|
||||
if flow_node is not None:
|
||||
reachable = _check_if(context, flow_node)
|
||||
elif flow_scope.type in ('try_stmt', 'while_stmt'):
|
||||
return UNSURE
|
||||
|
||||
# Only reachable branches need to be examined further.
|
||||
if reachable in (UNREACHABLE, UNSURE):
|
||||
return reachable
|
||||
|
||||
if context_scope != flow_scope and context_scope != flow_scope.parent:
|
||||
flow_scope = get_parent_scope(flow_scope, include_flows=True)
|
||||
return reachable & _break_check(context, context_scope, flow_scope, node)
|
||||
else:
|
||||
return reachable
|
||||
|
||||
|
||||
def _check_if(context, node):
|
||||
types = context.eval_node(node)
|
||||
values = set(x.py__bool__() for x in types)
|
||||
if len(values) == 1:
|
||||
return Status.lookup_table[values.pop()]
|
||||
else:
|
||||
return UNSURE
|
@ -0,0 +1,174 @@
|
||||
import copy
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
from itertools import chain
|
||||
from contextlib import contextmanager
|
||||
|
||||
from parso.python import tree
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
def is_stdlib_path(path):
|
||||
# Python standard library paths look like this:
|
||||
# /usr/lib/python3.5/...
|
||||
# TODO The implementation below is probably incorrect and not complete.
|
||||
if 'dist-packages' in path or 'site-packages' in path:
|
||||
return False
|
||||
|
||||
base_path = os.path.join(sys.prefix, 'lib', 'python')
|
||||
return bool(re.match(re.escape(base_path) + '\d.\d', path))
|
||||
|
||||
|
||||
def deep_ast_copy(obj):
|
||||
"""
|
||||
Much, much faster than copy.deepcopy, but just for parser tree nodes.
|
||||
"""
|
||||
# If it's already in the cache, just return it.
|
||||
new_obj = copy.copy(obj)
|
||||
|
||||
# Copy children
|
||||
new_children = []
|
||||
for child in obj.children:
|
||||
if isinstance(child, tree.Leaf):
|
||||
new_child = copy.copy(child)
|
||||
new_child.parent = new_obj
|
||||
else:
|
||||
new_child = deep_ast_copy(child)
|
||||
new_child.parent = new_obj
|
||||
new_children.append(new_child)
|
||||
new_obj.children = new_children
|
||||
|
||||
return new_obj
|
||||
|
||||
|
||||
def evaluate_call_of_leaf(context, leaf, cut_own_trailer=False):
|
||||
"""
|
||||
Creates a "call" node that consist of all ``trailer`` and ``power``
|
||||
objects. E.g. if you call it with ``append``::
|
||||
|
||||
list([]).append(3) or None
|
||||
|
||||
You would get a node with the content ``list([]).append`` back.
|
||||
|
||||
This generates a copy of the original ast node.
|
||||
|
||||
If you're using the leaf, e.g. the bracket `)` it will return ``list([])``.
|
||||
|
||||
# TODO remove cut_own_trailer option, since its always used with it. Just
|
||||
# ignore it, It's not what we want anyway. Or document it better?
|
||||
"""
|
||||
trailer = leaf.parent
|
||||
# The leaf may not be the last or first child, because there exist three
|
||||
# different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples
|
||||
# we should not match anything more than x.
|
||||
if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]):
|
||||
if trailer.type == 'atom':
|
||||
return context.eval_node(trailer)
|
||||
return context.eval_node(leaf)
|
||||
|
||||
power = trailer.parent
|
||||
index = power.children.index(trailer)
|
||||
if cut_own_trailer:
|
||||
cut = index
|
||||
else:
|
||||
cut = index + 1
|
||||
|
||||
if power.type == 'error_node':
|
||||
start = index
|
||||
while True:
|
||||
start -= 1
|
||||
base = power.children[start]
|
||||
if base.type != 'trailer':
|
||||
break
|
||||
trailers = power.children[start + 1: index + 1]
|
||||
else:
|
||||
base = power.children[0]
|
||||
trailers = power.children[1:cut]
|
||||
|
||||
values = context.eval_node(base)
|
||||
for trailer in trailers:
|
||||
values = context.eval_trailer(values, trailer)
|
||||
return values
|
||||
|
||||
|
||||
def call_of_leaf(leaf):
|
||||
"""
|
||||
Creates a "call" node that consist of all ``trailer`` and ``power``
|
||||
objects. E.g. if you call it with ``append``::
|
||||
|
||||
list([]).append(3) or None
|
||||
|
||||
You would get a node with the content ``list([]).append`` back.
|
||||
|
||||
This generates a copy of the original ast node.
|
||||
|
||||
If you're using the leaf, e.g. the bracket `)` it will return ``list([])``.
|
||||
"""
|
||||
# TODO this is the old version of this call. Try to remove it.
|
||||
trailer = leaf.parent
|
||||
# The leaf may not be the last or first child, because there exist three
|
||||
# different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples
|
||||
# we should not match anything more than x.
|
||||
if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]):
|
||||
if trailer.type == 'atom':
|
||||
return trailer
|
||||
return leaf
|
||||
|
||||
power = trailer.parent
|
||||
index = power.children.index(trailer)
|
||||
|
||||
new_power = copy.copy(power)
|
||||
new_power.children = list(new_power.children)
|
||||
new_power.children[index + 1:] = []
|
||||
|
||||
if power.type == 'error_node':
|
||||
start = index
|
||||
while True:
|
||||
start -= 1
|
||||
if power.children[start].type != 'trailer':
|
||||
break
|
||||
transformed = tree.Node('power', power.children[start:])
|
||||
transformed.parent = power.parent
|
||||
return transformed
|
||||
|
||||
return power
|
||||
|
||||
|
||||
def get_names_of_node(node):
|
||||
try:
|
||||
children = node.children
|
||||
except AttributeError:
|
||||
if node.type == 'name':
|
||||
return [node]
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
return list(chain.from_iterable(get_names_of_node(c) for c in children))
|
||||
|
||||
|
||||
def get_module_names(module, all_scopes):
|
||||
"""
|
||||
Returns a dictionary with name parts as keys and their call paths as
|
||||
values.
|
||||
"""
|
||||
names = chain.from_iterable(module.get_used_names().values())
|
||||
if not all_scopes:
|
||||
# We have to filter all the names that don't have the module as a
|
||||
# parent_scope. There's None as a parent, because nodes in the module
|
||||
# node have the parent module and not suite as all the others.
|
||||
# Therefore it's important to catch that case.
|
||||
names = [n for n in names if get_parent_scope(n).parent in (module, None)]
|
||||
return names
|
||||
|
||||
|
||||
@contextmanager
|
||||
def predefine_names(context, flow_scope, dct):
|
||||
predefined = context.predefined_names
|
||||
if flow_scope in predefined:
|
||||
raise NotImplementedError('Why does this happen?')
|
||||
predefined[flow_scope] = dct
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
del predefined[flow_scope]
|
@ -0,0 +1,564 @@
|
||||
"""
|
||||
:mod:`jedi.evaluate.imports` is here to resolve import statements and return
|
||||
the modules/classes/functions/whatever, which they stand for. However there's
|
||||
not any actual importing done. This module is about finding modules in the
|
||||
filesystem. This can be quite tricky sometimes, because Python imports are not
|
||||
always that simple.
|
||||
|
||||
This module uses imp for python up to 3.2 and importlib for python 3.3 on; the
|
||||
correct implementation is delegated to _compatibility.
|
||||
|
||||
This module also supports import autocompletion, which means to complete
|
||||
statements like ``from datetim`` (curser at the end would return ``datetime``).
|
||||
"""
|
||||
import imp
|
||||
import os
|
||||
import pkgutil
|
||||
import sys
|
||||
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor
|
||||
from parso.cache import parser_cache
|
||||
from parso import python_bytes_to_unicode
|
||||
|
||||
from jedi._compatibility import find_module, unicode, ImplicitNSInfo
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.common import unite
|
||||
from jedi.evaluate import sys_path
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.filters import AbstractNameDefinition
|
||||
|
||||
|
||||
# This memoization is needed, because otherwise we will infinitely loop on
|
||||
# certain imports.
|
||||
@evaluator_method_cache(default=set())
|
||||
def infer_import(context, tree_name, is_goto=False):
|
||||
module_context = context.get_root_context()
|
||||
import_node = search_ancestor(tree_name, 'import_name', 'import_from')
|
||||
import_path = import_node.get_path_for_name(tree_name)
|
||||
from_import_name = None
|
||||
evaluator = context.evaluator
|
||||
try:
|
||||
from_names = import_node.get_from_names()
|
||||
except AttributeError:
|
||||
# Is an import_name
|
||||
pass
|
||||
else:
|
||||
if len(from_names) + 1 == len(import_path):
|
||||
# We have to fetch the from_names part first and then check
|
||||
# if from_names exists in the modules.
|
||||
from_import_name = import_path[-1]
|
||||
import_path = from_names
|
||||
|
||||
importer = Importer(evaluator, tuple(import_path),
|
||||
module_context, import_node.level)
|
||||
|
||||
types = importer.follow()
|
||||
|
||||
#if import_node.is_nested() and not self.nested_resolve:
|
||||
# scopes = [NestedImportModule(module, import_node)]
|
||||
|
||||
if not types:
|
||||
return set()
|
||||
|
||||
if from_import_name is not None:
|
||||
types = unite(
|
||||
t.py__getattribute__(
|
||||
from_import_name,
|
||||
name_context=context,
|
||||
is_goto=is_goto,
|
||||
analysis_errors=False
|
||||
) for t in types
|
||||
)
|
||||
|
||||
if not types:
|
||||
path = import_path + [from_import_name]
|
||||
importer = Importer(evaluator, tuple(path),
|
||||
module_context, import_node.level)
|
||||
types = importer.follow()
|
||||
# goto only accepts `Name`
|
||||
if is_goto:
|
||||
types = set(s.name for s in types)
|
||||
else:
|
||||
# goto only accepts `Name`
|
||||
if is_goto:
|
||||
types = set(s.name for s in types)
|
||||
|
||||
debug.dbg('after import: %s', types)
|
||||
return types
|
||||
|
||||
|
||||
class NestedImportModule(tree.Module):
|
||||
"""
|
||||
TODO while there's no use case for nested import module right now, we might
|
||||
be able to use them for static analysis checks later on.
|
||||
"""
|
||||
def __init__(self, module, nested_import):
|
||||
self._module = module
|
||||
self._nested_import = nested_import
|
||||
|
||||
def _get_nested_import_name(self):
|
||||
"""
|
||||
Generates an Import statement, that can be used to fake nested imports.
|
||||
"""
|
||||
i = self._nested_import
|
||||
# This is not an existing Import statement. Therefore, set position to
|
||||
# 0 (0 is not a valid line number).
|
||||
zero = (0, 0)
|
||||
names = [unicode(name) for name in i.namespace_names[1:]]
|
||||
name = helpers.FakeName(names, self._nested_import)
|
||||
new = tree.Import(i._sub_module, zero, zero, name)
|
||||
new.parent = self._module
|
||||
debug.dbg('Generated a nested import: %s', new)
|
||||
return helpers.FakeName(str(i.namespace_names[1]), new)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._module, name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s of %s>" % (self.__class__.__name__, self._module,
|
||||
self._nested_import)
|
||||
|
||||
|
||||
def _add_error(context, name, message=None):
|
||||
# Should be a name, not a string!
|
||||
if hasattr(name, 'parent'):
|
||||
analysis.add(context, 'import-error', name, message)
|
||||
|
||||
|
||||
def get_init_path(directory_path):
|
||||
"""
|
||||
The __init__ file can be searched in a directory. If found return it, else
|
||||
None.
|
||||
"""
|
||||
for suffix, _, _ in imp.get_suffixes():
|
||||
path = os.path.join(directory_path, '__init__' + suffix)
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
class ImportName(AbstractNameDefinition):
|
||||
start_pos = (1, 0)
|
||||
_level = 0
|
||||
|
||||
def __init__(self, parent_context, string_name):
|
||||
self.parent_context = parent_context
|
||||
self.string_name = string_name
|
||||
|
||||
def infer(self):
|
||||
return Importer(
|
||||
self.parent_context.evaluator,
|
||||
[self.string_name],
|
||||
self.parent_context,
|
||||
level=self._level,
|
||||
).follow()
|
||||
|
||||
def goto(self):
|
||||
return [m.name for m in self.infer()]
|
||||
|
||||
def get_root_context(self):
|
||||
# Not sure if this is correct.
|
||||
return self.parent_context.get_root_context()
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return 'module'
|
||||
|
||||
|
||||
class SubModuleName(ImportName):
|
||||
_level = 1
|
||||
|
||||
|
||||
class Importer(object):
|
||||
def __init__(self, evaluator, import_path, module_context, level=0):
|
||||
"""
|
||||
An implementation similar to ``__import__``. Use `follow`
|
||||
to actually follow the imports.
|
||||
|
||||
*level* specifies whether to use absolute or relative imports. 0 (the
|
||||
default) means only perform absolute imports. Positive values for level
|
||||
indicate the number of parent directories to search relative to the
|
||||
directory of the module calling ``__import__()`` (see PEP 328 for the
|
||||
details).
|
||||
|
||||
:param import_path: List of namespaces (strings or Names).
|
||||
"""
|
||||
debug.speed('import %s' % (import_path,))
|
||||
self._evaluator = evaluator
|
||||
self.level = level
|
||||
self.module_context = module_context
|
||||
try:
|
||||
self.file_path = module_context.py__file__()
|
||||
except AttributeError:
|
||||
# Can be None for certain compiled modules like 'builtins'.
|
||||
self.file_path = None
|
||||
|
||||
if level:
|
||||
base = module_context.py__package__().split('.')
|
||||
if base == ['']:
|
||||
base = []
|
||||
if level > len(base):
|
||||
path = module_context.py__file__()
|
||||
if path is not None:
|
||||
import_path = list(import_path)
|
||||
p = path
|
||||
for i in range(level):
|
||||
p = os.path.dirname(p)
|
||||
dir_name = os.path.basename(p)
|
||||
# This is not the proper way to do relative imports. However, since
|
||||
# Jedi cannot be sure about the entry point, we just calculate an
|
||||
# absolute path here.
|
||||
if dir_name:
|
||||
# TODO those sys.modules modifications are getting
|
||||
# really stupid. this is the 3rd time that we're using
|
||||
# this. We should probably refactor.
|
||||
if path.endswith(os.path.sep + 'os.py'):
|
||||
import_path.insert(0, 'os')
|
||||
else:
|
||||
import_path.insert(0, dir_name)
|
||||
else:
|
||||
_add_error(module_context, import_path[-1])
|
||||
import_path = []
|
||||
# TODO add import error.
|
||||
debug.warning('Attempted relative import beyond top-level package.')
|
||||
# If no path is defined in the module we have no ideas where we
|
||||
# are in the file system. Therefore we cannot know what to do.
|
||||
# In this case we just let the path there and ignore that it's
|
||||
# a relative path. Not sure if that's a good idea.
|
||||
else:
|
||||
# Here we basically rewrite the level to 0.
|
||||
base = tuple(base)
|
||||
if level > 1:
|
||||
base = base[:-level + 1]
|
||||
|
||||
import_path = base + tuple(import_path)
|
||||
self.import_path = import_path
|
||||
|
||||
@property
|
||||
def str_import_path(self):
|
||||
"""Returns the import path as pure strings instead of `Name`."""
|
||||
return tuple(
|
||||
name.value if isinstance(name, tree.Name) else name
|
||||
for name in self.import_path)
|
||||
|
||||
def sys_path_with_modifications(self):
|
||||
in_path = []
|
||||
sys_path_mod = list(sys_path.sys_path_with_modifications(
|
||||
self._evaluator,
|
||||
self.module_context
|
||||
))
|
||||
if self.file_path is not None:
|
||||
# If you edit e.g. gunicorn, there will be imports like this:
|
||||
# `from gunicorn import something`. But gunicorn is not in the
|
||||
# sys.path. Therefore look if gunicorn is a parent directory, #56.
|
||||
if self.import_path: # TODO is this check really needed?
|
||||
for path in sys_path.traverse_parents(self.file_path):
|
||||
if os.path.basename(path) == self.str_import_path[0]:
|
||||
in_path.append(os.path.dirname(path))
|
||||
|
||||
# Since we know nothing about the call location of the sys.path,
|
||||
# it's a possibility that the current directory is the origin of
|
||||
# the Python execution.
|
||||
sys_path_mod.insert(0, os.path.dirname(self.file_path))
|
||||
|
||||
return in_path + sys_path_mod
|
||||
|
||||
def follow(self):
|
||||
if not self.import_path:
|
||||
return set()
|
||||
return self._do_import(self.import_path, self.sys_path_with_modifications())
|
||||
|
||||
def _do_import(self, import_path, sys_path):
|
||||
"""
|
||||
This method is very similar to importlib's `_gcd_import`.
|
||||
"""
|
||||
import_parts = [
|
||||
i.value if isinstance(i, tree.Name) else i
|
||||
for i in import_path
|
||||
]
|
||||
|
||||
# Handle "magic" Flask extension imports:
|
||||
# ``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
||||
if len(import_path) > 2 and import_parts[:2] == ['flask', 'ext']:
|
||||
# New style.
|
||||
ipath = ('flask_' + str(import_parts[2]),) + import_path[3:]
|
||||
modules = self._do_import(ipath, sys_path)
|
||||
if modules:
|
||||
return modules
|
||||
else:
|
||||
# Old style
|
||||
return self._do_import(('flaskext',) + import_path[2:], sys_path)
|
||||
|
||||
module_name = '.'.join(import_parts)
|
||||
try:
|
||||
return set([self._evaluator.modules[module_name]])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if len(import_path) > 1:
|
||||
# This is a recursive way of importing that works great with
|
||||
# the module cache.
|
||||
bases = self._do_import(import_path[:-1], sys_path)
|
||||
if not bases:
|
||||
return set()
|
||||
# We can take the first element, because only the os special
|
||||
# case yields multiple modules, which is not important for
|
||||
# further imports.
|
||||
parent_module = list(bases)[0]
|
||||
|
||||
# This is a huge exception, we follow a nested import
|
||||
# ``os.path``, because it's a very important one in Python
|
||||
# that is being achieved by messing with ``sys.modules`` in
|
||||
# ``os``.
|
||||
if import_parts == ['os', 'path']:
|
||||
return parent_module.py__getattribute__('path')
|
||||
|
||||
try:
|
||||
method = parent_module.py__path__
|
||||
except AttributeError:
|
||||
# The module is not a package.
|
||||
_add_error(self.module_context, import_path[-1])
|
||||
return set()
|
||||
else:
|
||||
paths = method()
|
||||
debug.dbg('search_module %s in paths %s', module_name, paths)
|
||||
for path in paths:
|
||||
# At the moment we are only using one path. So this is
|
||||
# not important to be correct.
|
||||
try:
|
||||
if not isinstance(path, list):
|
||||
path = [path]
|
||||
module_file, module_path, is_pkg = \
|
||||
find_module(import_parts[-1], path, fullname=module_name)
|
||||
break
|
||||
except ImportError:
|
||||
module_path = None
|
||||
if module_path is None:
|
||||
_add_error(self.module_context, import_path[-1])
|
||||
return set()
|
||||
else:
|
||||
parent_module = None
|
||||
try:
|
||||
debug.dbg('search_module %s in %s', import_parts[-1], self.file_path)
|
||||
# Override the sys.path. It works only good that way.
|
||||
# Injecting the path directly into `find_module` did not work.
|
||||
sys.path, temp = sys_path, sys.path
|
||||
try:
|
||||
module_file, module_path, is_pkg = \
|
||||
find_module(import_parts[-1], fullname=module_name)
|
||||
finally:
|
||||
sys.path = temp
|
||||
except ImportError:
|
||||
# The module is not a package.
|
||||
_add_error(self.module_context, import_path[-1])
|
||||
return set()
|
||||
|
||||
code = None
|
||||
if is_pkg:
|
||||
# In this case, we don't have a file yet. Search for the
|
||||
# __init__ file.
|
||||
if module_path.endswith(('.zip', '.egg')):
|
||||
code = module_file.loader.get_source(module_name)
|
||||
else:
|
||||
module_path = get_init_path(module_path)
|
||||
elif module_file:
|
||||
code = module_file.read()
|
||||
module_file.close()
|
||||
|
||||
if isinstance(module_path, ImplicitNSInfo):
|
||||
from jedi.evaluate.representation import ImplicitNamespaceContext
|
||||
fullname, paths = module_path.name, module_path.paths
|
||||
module = ImplicitNamespaceContext(self._evaluator, fullname=fullname)
|
||||
module.paths = paths
|
||||
elif module_file is None and not module_path.endswith(('.py', '.zip', '.egg')):
|
||||
module = compiled.load_module(self._evaluator, module_path)
|
||||
else:
|
||||
module = _load_module(self._evaluator, module_path, code, sys_path, parent_module)
|
||||
|
||||
if module is None:
|
||||
# The file might raise an ImportError e.g. and therefore not be
|
||||
# importable.
|
||||
return set()
|
||||
|
||||
self._evaluator.modules[module_name] = module
|
||||
return set([module])
|
||||
|
||||
def _generate_name(self, name, in_module=None):
|
||||
# Create a pseudo import to be able to follow them.
|
||||
if in_module is None:
|
||||
return ImportName(self.module_context, name)
|
||||
return SubModuleName(in_module, name)
|
||||
|
||||
def _get_module_names(self, search_path=None, in_module=None):
|
||||
"""
|
||||
Get the names of all modules in the search_path. This means file names
|
||||
and not names defined in the files.
|
||||
"""
|
||||
|
||||
names = []
|
||||
# add builtin module names
|
||||
if search_path is None and in_module is None:
|
||||
names += [self._generate_name(name) for name in sys.builtin_module_names]
|
||||
|
||||
if search_path is None:
|
||||
search_path = self.sys_path_with_modifications()
|
||||
for module_loader, name, is_pkg in pkgutil.iter_modules(search_path):
|
||||
names.append(self._generate_name(name, in_module=in_module))
|
||||
return names
|
||||
|
||||
def completion_names(self, evaluator, only_modules=False):
|
||||
"""
|
||||
:param only_modules: Indicates wheter it's possible to import a
|
||||
definition that is not defined in a module.
|
||||
"""
|
||||
from jedi.evaluate.representation import ModuleContext, ImplicitNamespaceContext
|
||||
names = []
|
||||
if self.import_path:
|
||||
# flask
|
||||
if self.str_import_path == ('flask', 'ext'):
|
||||
# List Flask extensions like ``flask_foo``
|
||||
for mod in self._get_module_names():
|
||||
modname = mod.string_name
|
||||
if modname.startswith('flask_'):
|
||||
extname = modname[len('flask_'):]
|
||||
names.append(self._generate_name(extname))
|
||||
# Now the old style: ``flaskext.foo``
|
||||
for dir in self.sys_path_with_modifications():
|
||||
flaskext = os.path.join(dir, 'flaskext')
|
||||
if os.path.isdir(flaskext):
|
||||
names += self._get_module_names([flaskext])
|
||||
|
||||
for context in self.follow():
|
||||
# Non-modules are not completable.
|
||||
if context.api_type != 'module': # not a module
|
||||
continue
|
||||
# namespace packages
|
||||
if isinstance(context, ModuleContext) and context.py__file__().endswith('__init__.py'):
|
||||
paths = context.py__path__()
|
||||
names += self._get_module_names(paths, in_module=context)
|
||||
|
||||
# implicit namespace packages
|
||||
elif isinstance(context, ImplicitNamespaceContext):
|
||||
paths = context.paths
|
||||
names += self._get_module_names(paths)
|
||||
|
||||
if only_modules:
|
||||
# In the case of an import like `from x.` we don't need to
|
||||
# add all the variables.
|
||||
if ('os',) == self.str_import_path and not self.level:
|
||||
# os.path is a hardcoded exception, because it's a
|
||||
# ``sys.modules`` modification.
|
||||
names.append(self._generate_name('path', context))
|
||||
|
||||
continue
|
||||
|
||||
for filter in context.get_filters(search_global=False):
|
||||
names += filter.values()
|
||||
else:
|
||||
# Empty import path=completion after import
|
||||
if not self.level:
|
||||
names += self._get_module_names()
|
||||
|
||||
if self.file_path is not None:
|
||||
path = os.path.abspath(self.file_path)
|
||||
for i in range(self.level - 1):
|
||||
path = os.path.dirname(path)
|
||||
names += self._get_module_names([path])
|
||||
|
||||
return names
|
||||
|
||||
|
||||
def _load_module(evaluator, path=None, code=None, sys_path=None, parent_module=None):
|
||||
if sys_path is None:
|
||||
sys_path = evaluator.sys_path
|
||||
|
||||
dotted_path = path and compiled.dotted_from_fs_path(path, sys_path)
|
||||
if path is not None and path.endswith(('.py', '.zip', '.egg')) \
|
||||
and dotted_path not in settings.auto_import_modules:
|
||||
|
||||
module_node = evaluator.grammar.parse(
|
||||
code=code, path=path, cache=True, diff_cache=True,
|
||||
cache_path=settings.cache_directory)
|
||||
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
return ModuleContext(evaluator, module_node, path=path)
|
||||
else:
|
||||
return compiled.load_module(evaluator, path)
|
||||
|
||||
|
||||
def add_module(evaluator, module_name, module):
|
||||
if '.' not in module_name:
|
||||
# We cannot add paths with dots, because that would collide with
|
||||
# the sepatator dots for nested packages. Therefore we return
|
||||
# `__main__` in ModuleWrapper.py__name__(), which is similar to
|
||||
# Python behavior.
|
||||
evaluator.modules[module_name] = module
|
||||
|
||||
|
||||
def get_modules_containing_name(evaluator, modules, name):
|
||||
"""
|
||||
Search a name in the directories of modules.
|
||||
"""
|
||||
from jedi.evaluate import representation as er
|
||||
|
||||
def check_python_file(path):
|
||||
try:
|
||||
# TODO I don't think we should use the cache here?!
|
||||
node_cache_item = parser_cache[evaluator.grammar._hashed][path]
|
||||
except KeyError:
|
||||
try:
|
||||
return check_fs(path)
|
||||
except IOError:
|
||||
return None
|
||||
else:
|
||||
module_node = node_cache_item.node
|
||||
return er.ModuleContext(evaluator, module_node, path=path)
|
||||
|
||||
def check_fs(path):
|
||||
with open(path, 'rb') as f:
|
||||
code = python_bytes_to_unicode(f.read(), errors='replace')
|
||||
if name in code:
|
||||
module = _load_module(evaluator, path, code)
|
||||
|
||||
module_name = sys_path.dotted_path_in_sys_path(evaluator.sys_path, path)
|
||||
if module_name is not None:
|
||||
add_module(evaluator, module_name, module)
|
||||
return module
|
||||
|
||||
# skip non python modules
|
||||
used_mod_paths = set()
|
||||
for m in modules:
|
||||
try:
|
||||
path = m.py__file__()
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
used_mod_paths.add(path)
|
||||
yield m
|
||||
|
||||
if not settings.dynamic_params_for_other_modules:
|
||||
return
|
||||
|
||||
paths = set(settings.additional_dynamic_modules)
|
||||
for p in used_mod_paths:
|
||||
if p is not None:
|
||||
# We need abspath, because the seetings paths might not already
|
||||
# have been converted to absolute paths.
|
||||
d = os.path.dirname(os.path.abspath(p))
|
||||
for file_name in os.listdir(d):
|
||||
path = os.path.join(d, file_name)
|
||||
if path not in used_mod_paths and path not in paths:
|
||||
if file_name.endswith('.py'):
|
||||
paths.add(path)
|
||||
|
||||
# Sort here to make issues less random.
|
||||
for p in sorted(paths):
|
||||
# make testing easier, sort it - same results on every interpreter
|
||||
m = check_python_file(p)
|
||||
if m is not None and not isinstance(m, compiled.CompiledObject):
|
||||
yield m
|
@ -0,0 +1,433 @@
|
||||
from abc import abstractproperty
|
||||
|
||||
from jedi._compatibility import is_py3
|
||||
from jedi.common import unite
|
||||
from jedi import debug
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import filters
|
||||
from jedi.evaluate.context import Context, LazyKnownContext, LazyKnownContexts
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.param import AbstractArguments, AnonymousArguments
|
||||
from jedi.cache import memoize_method
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate import iterable
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
|
||||
class InstanceFunctionExecution(er.FunctionExecutionContext):
|
||||
def __init__(self, instance, parent_context, function_context, var_args):
|
||||
self.instance = instance
|
||||
var_args = InstanceVarArgs(self, var_args)
|
||||
|
||||
super(InstanceFunctionExecution, self).__init__(
|
||||
instance.evaluator, parent_context, function_context, var_args)
|
||||
|
||||
|
||||
class AnonymousInstanceFunctionExecution(er.FunctionExecutionContext):
|
||||
function_execution_filter = filters.AnonymousInstanceFunctionExecutionFilter
|
||||
|
||||
def __init__(self, instance, parent_context, function_context, var_args):
|
||||
self.instance = instance
|
||||
super(AnonymousInstanceFunctionExecution, self).__init__(
|
||||
instance.evaluator, parent_context, function_context, var_args)
|
||||
|
||||
|
||||
class AbstractInstanceContext(Context):
|
||||
"""
|
||||
This class is used to evaluate instances.
|
||||
"""
|
||||
api_type = 'instance'
|
||||
function_execution_cls = InstanceFunctionExecution
|
||||
|
||||
def __init__(self, evaluator, parent_context, class_context, var_args):
|
||||
super(AbstractInstanceContext, self).__init__(evaluator, parent_context)
|
||||
# Generated instances are classes that are just generated by self
|
||||
# (No var_args) used.
|
||||
self.class_context = class_context
|
||||
self.var_args = var_args
|
||||
|
||||
def is_class(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def py__call__(self):
|
||||
names = self.get_function_slot_names('__call__')
|
||||
if not names:
|
||||
# Means the Instance is not callable.
|
||||
raise AttributeError
|
||||
|
||||
def execute(arguments):
|
||||
return unite(name.execute(arguments) for name in names)
|
||||
|
||||
return execute
|
||||
|
||||
def py__class__(self):
|
||||
return self.class_context
|
||||
|
||||
def py__bool__(self):
|
||||
# Signalize that we don't know about the bool type.
|
||||
return None
|
||||
|
||||
def get_function_slot_names(self, name):
|
||||
# Python classes don't look at the dictionary of the instance when
|
||||
# looking up `__call__`. This is something that has to do with Python's
|
||||
# internal slot system (note: not __slots__, but C slots).
|
||||
for filter in self.get_filters(include_self_names=False):
|
||||
names = filter.get(name)
|
||||
if names:
|
||||
return names
|
||||
return []
|
||||
|
||||
def execute_function_slots(self, names, *evaluated_args):
|
||||
return unite(
|
||||
name.execute_evaluated(*evaluated_args)
|
||||
for name in names
|
||||
)
|
||||
|
||||
def py__get__(self, obj):
|
||||
# Arguments in __get__ descriptors are obj, class.
|
||||
# `method` is the new parent of the array, don't know if that's good.
|
||||
names = self.get_function_slot_names('__get__')
|
||||
if names:
|
||||
if isinstance(obj, AbstractInstanceContext):
|
||||
return self.execute_function_slots(names, obj, obj.class_context)
|
||||
else:
|
||||
none_obj = compiled.create(self.evaluator, None)
|
||||
return self.execute_function_slots(names, none_obj, obj)
|
||||
else:
|
||||
return set([self])
|
||||
|
||||
def get_filters(self, search_global=None, until_position=None,
|
||||
origin_scope=None, include_self_names=True):
|
||||
if include_self_names:
|
||||
for cls in self.class_context.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
if cls.tree_node is not None:
|
||||
# In this case we're talking about a fake object, it
|
||||
# doesn't make sense for normal compiled objects to
|
||||
# search for self variables.
|
||||
yield SelfNameFilter(self.evaluator, self, cls, origin_scope)
|
||||
else:
|
||||
yield SelfNameFilter(self.evaluator, self, cls, origin_scope)
|
||||
|
||||
for cls in self.class_context.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
|
||||
else:
|
||||
yield InstanceClassFilter(self.evaluator, self, cls, origin_scope)
|
||||
|
||||
def py__getitem__(self, index):
|
||||
try:
|
||||
names = self.get_function_slot_names('__getitem__')
|
||||
except KeyError:
|
||||
debug.warning('No __getitem__, cannot access the array.')
|
||||
return set()
|
||||
else:
|
||||
index_obj = compiled.create(self.evaluator, index)
|
||||
return self.execute_function_slots(names, index_obj)
|
||||
|
||||
def py__iter__(self):
|
||||
iter_slot_names = self.get_function_slot_names('__iter__')
|
||||
if not iter_slot_names:
|
||||
debug.warning('No __iter__ on %s.' % self)
|
||||
return
|
||||
|
||||
for generator in self.execute_function_slots(iter_slot_names):
|
||||
if isinstance(generator, AbstractInstanceContext):
|
||||
# `__next__` logic.
|
||||
name = '__next__' if is_py3 else 'next'
|
||||
iter_slot_names = generator.get_function_slot_names(name)
|
||||
if iter_slot_names:
|
||||
yield LazyKnownContexts(
|
||||
generator.execute_function_slots(iter_slot_names)
|
||||
)
|
||||
else:
|
||||
debug.warning('Instance has no __next__ function in %s.', generator)
|
||||
else:
|
||||
for lazy_context in generator.py__iter__():
|
||||
yield lazy_context
|
||||
|
||||
@abstractproperty
|
||||
def name(self):
|
||||
pass
|
||||
|
||||
def _create_init_execution(self, class_context, func_node):
|
||||
bound_method = BoundMethod(
|
||||
self.evaluator, self, class_context, self.parent_context, func_node
|
||||
)
|
||||
return self.function_execution_cls(
|
||||
self,
|
||||
class_context.parent_context,
|
||||
bound_method,
|
||||
self.var_args
|
||||
)
|
||||
|
||||
def create_init_executions(self):
|
||||
for name in self.get_function_slot_names('__init__'):
|
||||
if isinstance(name, LazyInstanceName):
|
||||
yield self._create_init_execution(name.class_context, name.tree_name.parent)
|
||||
|
||||
@evaluator_method_cache()
|
||||
def create_instance_context(self, class_context, node):
|
||||
if node.parent.type in ('funcdef', 'classdef'):
|
||||
node = node.parent
|
||||
scope = get_parent_scope(node)
|
||||
if scope == class_context.tree_node:
|
||||
return class_context
|
||||
else:
|
||||
parent_context = self.create_instance_context(class_context, scope)
|
||||
if scope.type == 'funcdef':
|
||||
if scope.name.value == '__init__' and parent_context == class_context:
|
||||
return self._create_init_execution(class_context, scope)
|
||||
else:
|
||||
bound_method = BoundMethod(
|
||||
self.evaluator, self, class_context,
|
||||
parent_context, scope
|
||||
)
|
||||
return bound_method.get_function_execution()
|
||||
elif scope.type == 'classdef':
|
||||
class_context = er.ClassContext(self.evaluator, scope, parent_context)
|
||||
return class_context
|
||||
elif scope.type == 'comp_for':
|
||||
# Comprehensions currently don't have a special scope in Jedi.
|
||||
return self.create_instance_context(class_context, scope)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return class_context
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
|
||||
self.var_args)
|
||||
|
||||
|
||||
class CompiledInstance(AbstractInstanceContext):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(CompiledInstance, self).__init__(*args, **kwargs)
|
||||
# I don't think that dynamic append lookups should happen here. That
|
||||
# sounds more like something that should go to py__iter__.
|
||||
if self.class_context.name.string_name in ['list', 'set'] \
|
||||
and self.parent_context.get_root_context() == self.evaluator.BUILTINS:
|
||||
# compare the module path with the builtin name.
|
||||
self.var_args = iterable.get_dynamic_array_instance(self)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return compiled.CompiledContextName(self, self.class_context.name.string_name)
|
||||
|
||||
def create_instance_context(self, class_context, node):
|
||||
if get_parent_scope(node).type == 'classdef':
|
||||
return class_context
|
||||
else:
|
||||
return super(CompiledInstance, self).create_instance_context(class_context, node)
|
||||
|
||||
|
||||
class TreeInstance(AbstractInstanceContext):
|
||||
def __init__(self, evaluator, parent_context, class_context, var_args):
|
||||
super(TreeInstance, self).__init__(evaluator, parent_context,
|
||||
class_context, var_args)
|
||||
self.tree_node = class_context.tree_node
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return filters.ContextName(self, self.class_context.name.tree_name)
|
||||
|
||||
|
||||
class AnonymousInstance(TreeInstance):
|
||||
function_execution_cls = AnonymousInstanceFunctionExecution
|
||||
|
||||
def __init__(self, evaluator, parent_context, class_context):
|
||||
super(AnonymousInstance, self).__init__(
|
||||
evaluator,
|
||||
parent_context,
|
||||
class_context,
|
||||
var_args=AnonymousArguments(),
|
||||
)
|
||||
|
||||
|
||||
class CompiledInstanceName(compiled.CompiledName):
|
||||
def __init__(self, evaluator, instance, parent_context, name):
|
||||
super(CompiledInstanceName, self).__init__(evaluator, parent_context, name)
|
||||
self._instance = instance
|
||||
|
||||
def infer(self):
|
||||
for result_context in super(CompiledInstanceName, self).infer():
|
||||
if isinstance(result_context, er.FunctionContext):
|
||||
parent_context = result_context.parent_context
|
||||
while parent_context.is_class():
|
||||
parent_context = parent_context.parent_context
|
||||
|
||||
yield BoundMethod(
|
||||
result_context.evaluator, self._instance, self.parent_context,
|
||||
parent_context, result_context.tree_node
|
||||
)
|
||||
else:
|
||||
if result_context.api_type == 'function':
|
||||
yield CompiledBoundMethod(result_context)
|
||||
else:
|
||||
yield result_context
|
||||
|
||||
|
||||
class CompiledInstanceClassFilter(compiled.CompiledObjectFilter):
|
||||
name_class = CompiledInstanceName
|
||||
|
||||
def __init__(self, evaluator, instance, compiled_object):
|
||||
super(CompiledInstanceClassFilter, self).__init__(
|
||||
evaluator,
|
||||
compiled_object,
|
||||
is_instance=True,
|
||||
)
|
||||
self._instance = instance
|
||||
|
||||
def _create_name(self, name):
|
||||
return self.name_class(
|
||||
self._evaluator, self._instance, self._compiled_object, name)
|
||||
|
||||
|
||||
class BoundMethod(er.FunctionContext):
|
||||
def __init__(self, evaluator, instance, class_context, *args, **kwargs):
|
||||
super(BoundMethod, self).__init__(evaluator, *args, **kwargs)
|
||||
self._instance = instance
|
||||
self._class_context = class_context
|
||||
|
||||
def get_function_execution(self, arguments=None):
|
||||
if arguments is None:
|
||||
arguments = AnonymousArguments()
|
||||
return AnonymousInstanceFunctionExecution(
|
||||
self._instance, self.parent_context, self, arguments)
|
||||
else:
|
||||
return InstanceFunctionExecution(
|
||||
self._instance, self.parent_context, self, arguments)
|
||||
|
||||
|
||||
class CompiledBoundMethod(compiled.CompiledObject):
|
||||
def __init__(self, func):
|
||||
super(CompiledBoundMethod, self).__init__(
|
||||
func.evaluator, func.obj, func.parent_context, func.tree_node)
|
||||
|
||||
def get_param_names(self):
|
||||
return list(super(CompiledBoundMethod, self).get_param_names())[1:]
|
||||
|
||||
|
||||
class InstanceNameDefinition(filters.TreeNameDefinition):
|
||||
def infer(self):
|
||||
contexts = super(InstanceNameDefinition, self).infer()
|
||||
for context in contexts:
|
||||
yield context
|
||||
|
||||
|
||||
class LazyInstanceName(filters.TreeNameDefinition):
|
||||
"""
|
||||
This name calculates the parent_context lazily.
|
||||
"""
|
||||
def __init__(self, instance, class_context, tree_name):
|
||||
self._instance = instance
|
||||
self.class_context = class_context
|
||||
self.tree_name = tree_name
|
||||
|
||||
@property
|
||||
def parent_context(self):
|
||||
return self._instance.create_instance_context(self.class_context, self.tree_name)
|
||||
|
||||
|
||||
class LazyInstanceClassName(LazyInstanceName):
|
||||
def infer(self):
|
||||
for result_context in super(LazyInstanceClassName, self).infer():
|
||||
if isinstance(result_context, er.FunctionContext):
|
||||
# Classes are never used to resolve anything within the
|
||||
# functions. Only other functions and modules will resolve
|
||||
# those things.
|
||||
parent_context = result_context.parent_context
|
||||
while parent_context.is_class():
|
||||
parent_context = parent_context.parent_context
|
||||
|
||||
yield BoundMethod(
|
||||
result_context.evaluator, self._instance, self.class_context,
|
||||
parent_context, result_context.tree_node
|
||||
)
|
||||
else:
|
||||
for c in er.apply_py__get__(result_context, self._instance):
|
||||
yield c
|
||||
|
||||
|
||||
class InstanceClassFilter(filters.ParserTreeFilter):
|
||||
name_class = LazyInstanceClassName
|
||||
|
||||
def __init__(self, evaluator, context, class_context, origin_scope):
|
||||
super(InstanceClassFilter, self).__init__(
|
||||
evaluator=evaluator,
|
||||
context=context,
|
||||
node_context=class_context,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
self._class_context = class_context
|
||||
|
||||
def _equals_origin_scope(self):
|
||||
node = self._origin_scope
|
||||
while node is not None:
|
||||
if node == self._parser_scope or node == self.context:
|
||||
return True
|
||||
node = get_parent_scope(node)
|
||||
return False
|
||||
|
||||
def _access_possible(self, name):
|
||||
return not name.value.startswith('__') or name.value.endswith('__') \
|
||||
or self._equals_origin_scope()
|
||||
|
||||
def _filter(self, names):
|
||||
names = super(InstanceClassFilter, self)._filter(names)
|
||||
return [name for name in names if self._access_possible(name)]
|
||||
|
||||
def _convert_names(self, names):
|
||||
return [self.name_class(self.context, self._class_context, name) for name in names]
|
||||
|
||||
|
||||
class SelfNameFilter(InstanceClassFilter):
|
||||
name_class = LazyInstanceName
|
||||
|
||||
def _filter(self, names):
|
||||
names = self._filter_self_names(names)
|
||||
if isinstance(self._parser_scope, compiled.CompiledObject) and False:
|
||||
# This would be for builtin skeletons, which are not yet supported.
|
||||
return list(names)
|
||||
else:
|
||||
start, end = self._parser_scope.start_pos, self._parser_scope.end_pos
|
||||
return [n for n in names if start < n.start_pos < end]
|
||||
|
||||
def _filter_self_names(self, names):
|
||||
for name in names:
|
||||
trailer = name.parent
|
||||
if trailer.type == 'trailer' \
|
||||
and len(trailer.children) == 2 \
|
||||
and trailer.children[0] == '.':
|
||||
if name.is_definition() and self._access_possible(name):
|
||||
yield name
|
||||
|
||||
def _check_flows(self, names):
|
||||
return names
|
||||
|
||||
|
||||
class InstanceVarArgs(AbstractArguments):
|
||||
def __init__(self, execution_context, var_args):
|
||||
self._execution_context = execution_context
|
||||
self._var_args = var_args
|
||||
|
||||
@memoize_method
|
||||
def _get_var_args(self):
|
||||
return self._var_args
|
||||
|
||||
@property
|
||||
def argument_node(self):
|
||||
return self._var_args.argument_node
|
||||
|
||||
@property
|
||||
def trailer(self):
|
||||
return self._var_args.trailer
|
||||
|
||||
def unpack(self, func=None):
|
||||
yield None, LazyKnownContext(self._execution_context.instance)
|
||||
for values in self._get_var_args().unpack(func):
|
||||
yield values
|
||||
|
||||
def get_calling_nodes(self):
|
||||
return self._get_var_args().get_calling_nodes()
|
@ -0,0 +1,884 @@
|
||||
"""
|
||||
Contains all classes and functions to deal with lists, dicts, generators and
|
||||
iterators in general.
|
||||
|
||||
Array modifications
|
||||
*******************
|
||||
|
||||
If the content of an array (``set``/``list``) is requested somewhere, the
|
||||
current module will be checked for appearances of ``arr.append``,
|
||||
``arr.insert``, etc. If the ``arr`` name points to an actual array, the
|
||||
content will be added
|
||||
|
||||
This can be really cpu intensive, as you can imagine. Because |jedi| has to
|
||||
follow **every** ``append`` and check wheter it's the right array. However this
|
||||
works pretty good, because in *slow* cases, the recursion detector and other
|
||||
settings will stop this process.
|
||||
|
||||
It is important to note that:
|
||||
|
||||
1. Array modfications work only in the current module.
|
||||
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
|
||||
"""
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi import common
|
||||
from jedi.common import unite, safe_property
|
||||
from jedi._compatibility import unicode, zip_longest, is_py3
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate import pep0484
|
||||
from jedi.evaluate import context
|
||||
from jedi.evaluate import precedence
|
||||
from jedi.evaluate import recursion
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, \
|
||||
ParserTreeFilter
|
||||
from jedi.parser_utils import get_comp_fors
|
||||
|
||||
|
||||
class AbstractSequence(context.Context):
|
||||
builtin_methods = {}
|
||||
api_type = 'instance'
|
||||
|
||||
def __init__(self, evaluator):
|
||||
super(AbstractSequence, self).__init__(evaluator, evaluator.BUILTINS)
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
raise NotImplementedError
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return compiled.CompiledContextName(self, self.array_type)
|
||||
|
||||
|
||||
class BuiltinMethod(object):
|
||||
"""``Generator.__next__`` ``dict.values`` methods and so on."""
|
||||
def __init__(self, builtin_context, method, builtin_func):
|
||||
self._builtin_context = builtin_context
|
||||
self._method = method
|
||||
self._builtin_func = builtin_func
|
||||
|
||||
def py__call__(self, params):
|
||||
return self._method(self._builtin_context)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._builtin_func, name)
|
||||
|
||||
|
||||
class SpecialMethodFilter(DictFilter):
|
||||
"""
|
||||
A filter for methods that are defined in this module on the corresponding
|
||||
classes like Generator (for __next__, etc).
|
||||
"""
|
||||
class SpecialMethodName(AbstractNameDefinition):
|
||||
api_type = 'function'
|
||||
|
||||
def __init__(self, parent_context, string_name, callable_, builtin_context):
|
||||
self.parent_context = parent_context
|
||||
self.string_name = string_name
|
||||
self._callable = callable_
|
||||
self._builtin_context = builtin_context
|
||||
|
||||
def infer(self):
|
||||
filter = next(self._builtin_context.get_filters())
|
||||
# We can take the first index, because on builtin methods there's
|
||||
# always only going to be one name. The same is true for the
|
||||
# inferred values.
|
||||
builtin_func = next(iter(filter.get(self.string_name)[0].infer()))
|
||||
return set([BuiltinMethod(self.parent_context, self._callable, builtin_func)])
|
||||
|
||||
def __init__(self, context, dct, builtin_context):
|
||||
super(SpecialMethodFilter, self).__init__(dct)
|
||||
self.context = context
|
||||
self._builtin_context = builtin_context
|
||||
"""
|
||||
This context is what will be used to introspect the name, where as the
|
||||
other context will be used to execute the function.
|
||||
|
||||
We distinguish, because we have to.
|
||||
"""
|
||||
|
||||
def _convert(self, name, value):
|
||||
return self.SpecialMethodName(self.context, name, value, self._builtin_context)
|
||||
|
||||
|
||||
def has_builtin_methods(cls):
|
||||
base_dct = {}
|
||||
# Need to care properly about inheritance. Builtin Methods should not get
|
||||
# lost, just because they are not mentioned in a class.
|
||||
for base_cls in reversed(cls.__bases__):
|
||||
try:
|
||||
base_dct.update(base_cls.builtin_methods)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
cls.builtin_methods = base_dct
|
||||
for func in cls.__dict__.values():
|
||||
try:
|
||||
cls.builtin_methods.update(func.registered_builtin_methods)
|
||||
except AttributeError:
|
||||
pass
|
||||
return cls
|
||||
|
||||
|
||||
def register_builtin_method(method_name, python_version_match=None):
|
||||
def wrapper(func):
|
||||
if python_version_match and python_version_match != 2 + int(is_py3):
|
||||
# Some functions do only apply to certain versions.
|
||||
return func
|
||||
dct = func.__dict__.setdefault('registered_builtin_methods', {})
|
||||
dct[method_name] = func
|
||||
return func
|
||||
return wrapper
|
||||
|
||||
|
||||
@has_builtin_methods
|
||||
class GeneratorMixin(object):
|
||||
array_type = None
|
||||
|
||||
@register_builtin_method('send')
|
||||
@register_builtin_method('next', python_version_match=2)
|
||||
@register_builtin_method('__next__', python_version_match=3)
|
||||
def py__next__(self):
|
||||
# TODO add TypeError if params are given.
|
||||
return unite(lazy_context.infer() for lazy_context in self.py__iter__())
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
gen_obj = compiled.get_special_object(self.evaluator, 'GENERATOR_OBJECT')
|
||||
yield SpecialMethodFilter(self, self.builtin_methods, gen_obj)
|
||||
for filter in gen_obj.get_filters(search_global):
|
||||
yield filter
|
||||
|
||||
def py__bool__(self):
|
||||
return True
|
||||
|
||||
def py__class__(self):
|
||||
gen_obj = compiled.get_special_object(self.evaluator, 'GENERATOR_OBJECT')
|
||||
return gen_obj.py__class__()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return compiled.CompiledContextName(self, 'generator')
|
||||
|
||||
|
||||
class Generator(GeneratorMixin, context.Context):
|
||||
"""Handling of `yield` functions."""
|
||||
def __init__(self, evaluator, func_execution_context):
|
||||
super(Generator, self).__init__(evaluator, parent_context=evaluator.BUILTINS)
|
||||
self._func_execution_context = func_execution_context
|
||||
|
||||
def py__iter__(self):
|
||||
return self._func_execution_context.get_yield_values()
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (type(self).__name__, self._func_execution_context)
|
||||
|
||||
|
||||
class CompForContext(context.TreeContext):
|
||||
@classmethod
|
||||
def from_comp_for(cls, parent_context, comp_for):
|
||||
return cls(parent_context.evaluator, parent_context, comp_for)
|
||||
|
||||
def __init__(self, evaluator, parent_context, comp_for):
|
||||
super(CompForContext, self).__init__(evaluator, parent_context)
|
||||
self.tree_node = comp_for
|
||||
|
||||
def get_node(self):
|
||||
return self.tree_node
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield ParserTreeFilter(self.evaluator, self)
|
||||
|
||||
|
||||
class Comprehension(AbstractSequence):
|
||||
@staticmethod
|
||||
def from_atom(evaluator, context, atom):
|
||||
bracket = atom.children[0]
|
||||
if bracket == '{':
|
||||
if atom.children[1].children[1] == ':':
|
||||
cls = DictComprehension
|
||||
else:
|
||||
cls = SetComprehension
|
||||
elif bracket == '(':
|
||||
cls = GeneratorComprehension
|
||||
elif bracket == '[':
|
||||
cls = ListComprehension
|
||||
return cls(evaluator, context, atom)
|
||||
|
||||
def __init__(self, evaluator, defining_context, atom):
|
||||
super(Comprehension, self).__init__(evaluator)
|
||||
self._defining_context = defining_context
|
||||
self._atom = atom
|
||||
|
||||
def _get_comprehension(self):
|
||||
# The atom contains a testlist_comp
|
||||
return self._atom.children[1]
|
||||
|
||||
def _get_comp_for(self):
|
||||
# The atom contains a testlist_comp
|
||||
return self._get_comprehension().children[1]
|
||||
|
||||
def _eval_node(self, index=0):
|
||||
"""
|
||||
The first part `x + 1` of the list comprehension:
|
||||
|
||||
[x + 1 for x in foo]
|
||||
"""
|
||||
return self._get_comprehension().children[index]
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _get_comp_for_context(self, parent_context, comp_for):
|
||||
# TODO shouldn't this be part of create_context?
|
||||
return CompForContext.from_comp_for(parent_context, comp_for)
|
||||
|
||||
def _nested(self, comp_fors, parent_context=None):
|
||||
evaluator = self.evaluator
|
||||
comp_for = comp_fors[0]
|
||||
input_node = comp_for.children[3]
|
||||
parent_context = parent_context or self._defining_context
|
||||
input_types = parent_context.eval_node(input_node)
|
||||
|
||||
cn = context.ContextualizedNode(parent_context, input_node)
|
||||
iterated = py__iter__(evaluator, input_types, cn)
|
||||
exprlist = comp_for.children[1]
|
||||
for i, lazy_context in enumerate(iterated):
|
||||
types = lazy_context.infer()
|
||||
dct = unpack_tuple_to_dict(parent_context, types, exprlist)
|
||||
context_ = self._get_comp_for_context(
|
||||
parent_context,
|
||||
comp_for,
|
||||
)
|
||||
with helpers.predefine_names(context_, comp_for, dct):
|
||||
try:
|
||||
for result in self._nested(comp_fors[1:], context_):
|
||||
yield result
|
||||
except IndexError:
|
||||
iterated = context_.eval_node(self._eval_node())
|
||||
if self.array_type == 'dict':
|
||||
yield iterated, context_.eval_node(self._eval_node(2))
|
||||
else:
|
||||
yield iterated
|
||||
|
||||
@evaluator_method_cache(default=[])
|
||||
@common.to_list
|
||||
def _iterate(self):
|
||||
comp_fors = tuple(get_comp_fors(self._get_comp_for()))
|
||||
for result in self._nested(comp_fors):
|
||||
yield result
|
||||
|
||||
def py__iter__(self):
|
||||
for set_ in self._iterate():
|
||||
yield context.LazyKnownContexts(set_)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (type(self).__name__, self._atom)
|
||||
|
||||
|
||||
class ArrayMixin(object):
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
# `array.type` is a string with the type, e.g. 'list'.
|
||||
compiled_obj = compiled.builtin_from_name(self.evaluator, self.array_type)
|
||||
yield SpecialMethodFilter(self, self.builtin_methods, compiled_obj)
|
||||
for typ in compiled_obj.execute_evaluated(self):
|
||||
for filter in typ.get_filters():
|
||||
yield filter
|
||||
|
||||
def py__bool__(self):
|
||||
return None # We don't know the length, because of appends.
|
||||
|
||||
def py__class__(self):
|
||||
return compiled.builtin_from_name(self.evaluator, self.array_type)
|
||||
|
||||
@safe_property
|
||||
def parent(self):
|
||||
return self.evaluator.BUILTINS
|
||||
|
||||
def dict_values(self):
|
||||
return unite(self._defining_context.eval_node(v) for k, v in self._items())
|
||||
|
||||
|
||||
class ListComprehension(ArrayMixin, Comprehension):
|
||||
array_type = 'list'
|
||||
|
||||
def py__getitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
return set([self])
|
||||
|
||||
all_types = list(self.py__iter__())
|
||||
return all_types[index].infer()
|
||||
|
||||
|
||||
class SetComprehension(ArrayMixin, Comprehension):
|
||||
array_type = 'set'
|
||||
|
||||
|
||||
@has_builtin_methods
|
||||
class DictComprehension(ArrayMixin, Comprehension):
|
||||
array_type = 'dict'
|
||||
|
||||
def _get_comp_for(self):
|
||||
return self._get_comprehension().children[3]
|
||||
|
||||
def py__iter__(self):
|
||||
for keys, values in self._iterate():
|
||||
yield context.LazyKnownContexts(keys)
|
||||
|
||||
def py__getitem__(self, index):
|
||||
for keys, values in self._iterate():
|
||||
for k in keys:
|
||||
if isinstance(k, compiled.CompiledObject):
|
||||
if k.obj == index:
|
||||
return values
|
||||
return self.dict_values()
|
||||
|
||||
def dict_values(self):
|
||||
return unite(values for keys, values in self._iterate())
|
||||
|
||||
@register_builtin_method('values')
|
||||
def _imitate_values(self):
|
||||
lazy_context = context.LazyKnownContexts(self.dict_values())
|
||||
return set([FakeSequence(self.evaluator, 'list', [lazy_context])])
|
||||
|
||||
@register_builtin_method('items')
|
||||
def _imitate_items(self):
|
||||
items = set(
|
||||
FakeSequence(
|
||||
self.evaluator, 'tuple'
|
||||
(context.LazyKnownContexts(keys), context.LazyKnownContexts(values))
|
||||
) for keys, values in self._iterate()
|
||||
)
|
||||
|
||||
return create_evaluated_sequence_set(self.evaluator, items, sequence_type='list')
|
||||
|
||||
|
||||
class GeneratorComprehension(GeneratorMixin, Comprehension):
|
||||
pass
|
||||
|
||||
|
||||
class SequenceLiteralContext(ArrayMixin, AbstractSequence):
|
||||
mapping = {'(': 'tuple',
|
||||
'[': 'list',
|
||||
'{': 'set'}
|
||||
|
||||
def __init__(self, evaluator, defining_context, atom):
|
||||
super(SequenceLiteralContext, self).__init__(evaluator)
|
||||
self.atom = atom
|
||||
self._defining_context = defining_context
|
||||
|
||||
if self.atom.type in ('testlist_star_expr', 'testlist'):
|
||||
self.array_type = 'tuple'
|
||||
else:
|
||||
self.array_type = SequenceLiteralContext.mapping[atom.children[0]]
|
||||
"""The builtin name of the array (list, set, tuple or dict)."""
|
||||
|
||||
def py__getitem__(self, index):
|
||||
"""Here the index is an int/str. Raises IndexError/KeyError."""
|
||||
if self.array_type == 'dict':
|
||||
for key, value in self._items():
|
||||
for k in self._defining_context.eval_node(key):
|
||||
if isinstance(k, compiled.CompiledObject) \
|
||||
and index == k.obj:
|
||||
return self._defining_context.eval_node(value)
|
||||
raise KeyError('No key found in dictionary %s.' % self)
|
||||
|
||||
# Can raise an IndexError
|
||||
if isinstance(index, slice):
|
||||
return set([self])
|
||||
else:
|
||||
return self._defining_context.eval_node(self._items()[index])
|
||||
|
||||
def py__iter__(self):
|
||||
"""
|
||||
While values returns the possible values for any array field, this
|
||||
function returns the value for a certain index.
|
||||
"""
|
||||
if self.array_type == 'dict':
|
||||
# Get keys.
|
||||
types = set()
|
||||
for k, _ in self._items():
|
||||
types |= self._defining_context.eval_node(k)
|
||||
# We don't know which dict index comes first, therefore always
|
||||
# yield all the types.
|
||||
for _ in types:
|
||||
yield context.LazyKnownContexts(types)
|
||||
else:
|
||||
for node in self._items():
|
||||
yield context.LazyTreeContext(self._defining_context, node)
|
||||
|
||||
for addition in check_array_additions(self._defining_context, self):
|
||||
yield addition
|
||||
|
||||
def _values(self):
|
||||
"""Returns a list of a list of node."""
|
||||
if self.array_type == 'dict':
|
||||
return unite(v for k, v in self._items())
|
||||
else:
|
||||
return self._items()
|
||||
|
||||
def _items(self):
|
||||
c = self.atom.children
|
||||
|
||||
if self.atom.type in ('testlist_star_expr', 'testlist'):
|
||||
return c[::2]
|
||||
|
||||
array_node = c[1]
|
||||
if array_node in (']', '}', ')'):
|
||||
return [] # Direct closing bracket, doesn't contain items.
|
||||
|
||||
if array_node.type == 'testlist_comp':
|
||||
return array_node.children[::2]
|
||||
elif array_node.type == 'dictorsetmaker':
|
||||
kv = []
|
||||
iterator = iter(array_node.children)
|
||||
for key in iterator:
|
||||
op = next(iterator, None)
|
||||
if op is None or op == ',':
|
||||
kv.append(key) # A set.
|
||||
else:
|
||||
assert op == ':' # A dict.
|
||||
kv.append((key, next(iterator)))
|
||||
next(iterator, None) # Possible comma.
|
||||
return kv
|
||||
else:
|
||||
return [array_node]
|
||||
|
||||
def exact_key_items(self):
|
||||
"""
|
||||
Returns a generator of tuples like dict.items(), where the key is
|
||||
resolved (as a string) and the values are still lazy contexts.
|
||||
"""
|
||||
for key_node, value in self._items():
|
||||
for key in self._defining_context.eval_node(key_node):
|
||||
if precedence.is_string(key):
|
||||
yield key.obj, context.LazyTreeContext(self._defining_context, value)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (self.__class__.__name__, self.atom)
|
||||
|
||||
|
||||
@has_builtin_methods
|
||||
class DictLiteralContext(SequenceLiteralContext):
|
||||
array_type = 'dict'
|
||||
|
||||
def __init__(self, evaluator, defining_context, atom):
|
||||
super(SequenceLiteralContext, self).__init__(evaluator)
|
||||
self._defining_context = defining_context
|
||||
self.atom = atom
|
||||
|
||||
@register_builtin_method('values')
|
||||
def _imitate_values(self):
|
||||
lazy_context = context.LazyKnownContexts(self.dict_values())
|
||||
return set([FakeSequence(self.evaluator, 'list', [lazy_context])])
|
||||
|
||||
@register_builtin_method('items')
|
||||
def _imitate_items(self):
|
||||
lazy_contexts = [
|
||||
context.LazyKnownContext(FakeSequence(
|
||||
self.evaluator, 'tuple',
|
||||
(context.LazyTreeContext(self._defining_context, key_node),
|
||||
context.LazyTreeContext(self._defining_context, value_node))
|
||||
)) for key_node, value_node in self._items()
|
||||
]
|
||||
|
||||
return set([FakeSequence(self.evaluator, 'list', lazy_contexts)])
|
||||
|
||||
|
||||
class _FakeArray(SequenceLiteralContext):
|
||||
def __init__(self, evaluator, container, type):
|
||||
super(SequenceLiteralContext, self).__init__(evaluator)
|
||||
self.array_type = type
|
||||
self.atom = container
|
||||
# TODO is this class really needed?
|
||||
|
||||
|
||||
class FakeSequence(_FakeArray):
|
||||
def __init__(self, evaluator, array_type, lazy_context_list):
|
||||
"""
|
||||
type should be one of "tuple", "list"
|
||||
"""
|
||||
super(FakeSequence, self).__init__(evaluator, None, array_type)
|
||||
self._lazy_context_list = lazy_context_list
|
||||
|
||||
def py__getitem__(self, index):
|
||||
return set(self._lazy_context_list[index].infer())
|
||||
|
||||
def py__iter__(self):
|
||||
return self._lazy_context_list
|
||||
|
||||
def py__bool__(self):
|
||||
return bool(len(self._lazy_context_list))
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (type(self).__name__, self._lazy_context_list)
|
||||
|
||||
|
||||
class FakeDict(_FakeArray):
|
||||
def __init__(self, evaluator, dct):
|
||||
super(FakeDict, self).__init__(evaluator, dct, 'dict')
|
||||
self._dct = dct
|
||||
|
||||
def py__iter__(self):
|
||||
for key in self._dct:
|
||||
yield context.LazyKnownContext(compiled.create(self.evaluator, key))
|
||||
|
||||
def py__getitem__(self, index):
|
||||
return self._dct[index].infer()
|
||||
|
||||
def dict_values(self):
|
||||
return unite(lazy_context.infer() for lazy_context in self._dct.values())
|
||||
|
||||
def exact_key_items(self):
|
||||
return self._dct.items()
|
||||
|
||||
|
||||
class MergedArray(_FakeArray):
|
||||
def __init__(self, evaluator, arrays):
|
||||
super(MergedArray, self).__init__(evaluator, arrays, arrays[-1].array_type)
|
||||
self._arrays = arrays
|
||||
|
||||
def py__iter__(self):
|
||||
for array in self._arrays:
|
||||
for lazy_context in array.py__iter__():
|
||||
yield lazy_context
|
||||
|
||||
def py__getitem__(self, index):
|
||||
return unite(lazy_context.infer() for lazy_context in self.py__iter__())
|
||||
|
||||
def _items(self):
|
||||
for array in self._arrays:
|
||||
for a in array._items():
|
||||
yield a
|
||||
|
||||
def __len__(self):
|
||||
return sum(len(a) for a in self._arrays)
|
||||
|
||||
|
||||
def unpack_tuple_to_dict(context, types, exprlist):
|
||||
"""
|
||||
Unpacking tuple assignments in for statements and expr_stmts.
|
||||
"""
|
||||
if exprlist.type == 'name':
|
||||
return {exprlist.value: types}
|
||||
elif exprlist.type == 'atom' and exprlist.children[0] in '([':
|
||||
return unpack_tuple_to_dict(context, types, exprlist.children[1])
|
||||
elif exprlist.type in ('testlist', 'testlist_comp', 'exprlist',
|
||||
'testlist_star_expr'):
|
||||
dct = {}
|
||||
parts = iter(exprlist.children[::2])
|
||||
n = 0
|
||||
for lazy_context in py__iter__(context.evaluator, types, exprlist):
|
||||
n += 1
|
||||
try:
|
||||
part = next(parts)
|
||||
except StopIteration:
|
||||
# TODO this context is probably not right.
|
||||
analysis.add(context, 'value-error-too-many-values', part,
|
||||
message="ValueError: too many values to unpack (expected %s)" % n)
|
||||
else:
|
||||
dct.update(unpack_tuple_to_dict(context, lazy_context.infer(), part))
|
||||
has_parts = next(parts, None)
|
||||
if types and has_parts is not None:
|
||||
# TODO this context is probably not right.
|
||||
analysis.add(context, 'value-error-too-few-values', has_parts,
|
||||
message="ValueError: need more than %s values to unpack" % n)
|
||||
return dct
|
||||
elif exprlist.type == 'power' or exprlist.type == 'atom_expr':
|
||||
# Something like ``arr[x], var = ...``.
|
||||
# This is something that is not yet supported, would also be difficult
|
||||
# to write into a dict.
|
||||
return {}
|
||||
elif exprlist.type == 'star_expr': # `a, *b, c = x` type unpackings
|
||||
# Currently we're not supporting them.
|
||||
return {}
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def py__iter__(evaluator, types, contextualized_node=None):
|
||||
debug.dbg('py__iter__')
|
||||
type_iters = []
|
||||
for typ in types:
|
||||
try:
|
||||
iter_method = typ.py__iter__
|
||||
except AttributeError:
|
||||
if contextualized_node is not None:
|
||||
analysis.add(
|
||||
contextualized_node.context,
|
||||
'type-error-not-iterable',
|
||||
contextualized_node._node,
|
||||
message="TypeError: '%s' object is not iterable" % typ)
|
||||
else:
|
||||
type_iters.append(iter_method())
|
||||
|
||||
for lazy_contexts in zip_longest(*type_iters):
|
||||
yield context.get_merged_lazy_context(
|
||||
[l for l in lazy_contexts if l is not None]
|
||||
)
|
||||
|
||||
|
||||
def py__iter__types(evaluator, types, contextualized_node=None):
|
||||
"""
|
||||
Calls `py__iter__`, but ignores the ordering in the end and just returns
|
||||
all types that it contains.
|
||||
"""
|
||||
return unite(
|
||||
lazy_context.infer()
|
||||
for lazy_context in py__iter__(evaluator, types, contextualized_node)
|
||||
)
|
||||
|
||||
|
||||
def py__getitem__(evaluator, context, types, trailer):
|
||||
from jedi.evaluate.representation import ClassContext
|
||||
from jedi.evaluate.instance import TreeInstance
|
||||
result = set()
|
||||
|
||||
trailer_op, node, trailer_cl = trailer.children
|
||||
assert trailer_op == "["
|
||||
assert trailer_cl == "]"
|
||||
|
||||
# special case: PEP0484 typing module, see
|
||||
# https://github.com/davidhalter/jedi/issues/663
|
||||
for typ in list(types):
|
||||
if isinstance(typ, (ClassContext, TreeInstance)):
|
||||
typing_module_types = pep0484.py__getitem__(context, typ, node)
|
||||
if typing_module_types is not None:
|
||||
types.remove(typ)
|
||||
result |= typing_module_types
|
||||
|
||||
if not types:
|
||||
# all consumed by special cases
|
||||
return result
|
||||
|
||||
for index in create_index_types(evaluator, context, node):
|
||||
if isinstance(index, (compiled.CompiledObject, Slice)):
|
||||
index = index.obj
|
||||
|
||||
if type(index) not in (float, int, str, unicode, slice, type(Ellipsis)):
|
||||
# If the index is not clearly defined, we have to get all the
|
||||
# possiblities.
|
||||
for typ in list(types):
|
||||
if isinstance(typ, AbstractSequence) and typ.array_type == 'dict':
|
||||
types.remove(typ)
|
||||
result |= typ.dict_values()
|
||||
return result | py__iter__types(evaluator, types)
|
||||
|
||||
for typ in types:
|
||||
# The actual getitem call.
|
||||
try:
|
||||
getitem = typ.py__getitem__
|
||||
except AttributeError:
|
||||
# TODO this context is probably not right.
|
||||
analysis.add(context, 'type-error-not-subscriptable', trailer_op,
|
||||
message="TypeError: '%s' object is not subscriptable" % typ)
|
||||
else:
|
||||
try:
|
||||
result |= getitem(index)
|
||||
except IndexError:
|
||||
result |= py__iter__types(evaluator, set([typ]))
|
||||
except KeyError:
|
||||
# Must be a dict. Lists don't raise KeyErrors.
|
||||
result |= typ.dict_values()
|
||||
return result
|
||||
|
||||
|
||||
def check_array_additions(context, sequence):
|
||||
""" Just a mapper function for the internal _check_array_additions """
|
||||
if sequence.array_type not in ('list', 'set'):
|
||||
# TODO also check for dict updates
|
||||
return set()
|
||||
|
||||
return _check_array_additions(context, sequence)
|
||||
|
||||
|
||||
@evaluator_method_cache(default=set())
|
||||
@debug.increase_indent
|
||||
def _check_array_additions(context, sequence):
|
||||
"""
|
||||
Checks if a `Array` has "add" (append, insert, extend) statements:
|
||||
|
||||
>>> a = [""]
|
||||
>>> a.append(1)
|
||||
"""
|
||||
from jedi.evaluate import param
|
||||
|
||||
debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
|
||||
module_context = context.get_root_context()
|
||||
if not settings.dynamic_array_additions or isinstance(module_context, compiled.CompiledObject):
|
||||
debug.dbg('Dynamic array search aborted.', color='MAGENTA')
|
||||
return set()
|
||||
|
||||
def find_additions(context, arglist, add_name):
|
||||
params = list(param.TreeArguments(context.evaluator, context, arglist).unpack())
|
||||
result = set()
|
||||
if add_name in ['insert']:
|
||||
params = params[1:]
|
||||
if add_name in ['append', 'add', 'insert']:
|
||||
for key, lazy_context in params:
|
||||
result.add(lazy_context)
|
||||
elif add_name in ['extend', 'update']:
|
||||
for key, lazy_context in params:
|
||||
result |= set(py__iter__(context.evaluator, lazy_context.infer()))
|
||||
return result
|
||||
|
||||
temp_param_add, settings.dynamic_params_for_other_modules = \
|
||||
settings.dynamic_params_for_other_modules, False
|
||||
|
||||
is_list = sequence.name.string_name == 'list'
|
||||
search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
|
||||
|
||||
added_types = set()
|
||||
for add_name in search_names:
|
||||
try:
|
||||
possible_names = module_context.tree_node.get_used_names()[add_name]
|
||||
except KeyError:
|
||||
continue
|
||||
else:
|
||||
for name in possible_names:
|
||||
context_node = context.tree_node
|
||||
if not (context_node.start_pos < name.start_pos < context_node.end_pos):
|
||||
continue
|
||||
trailer = name.parent
|
||||
power = trailer.parent
|
||||
trailer_pos = power.children.index(trailer)
|
||||
try:
|
||||
execution_trailer = power.children[trailer_pos + 1]
|
||||
except IndexError:
|
||||
continue
|
||||
else:
|
||||
if execution_trailer.type != 'trailer' \
|
||||
or execution_trailer.children[0] != '(' \
|
||||
or execution_trailer.children[1] == ')':
|
||||
continue
|
||||
|
||||
random_context = context.create_context(name)
|
||||
|
||||
with recursion.execution_allowed(context.evaluator, power) as allowed:
|
||||
if allowed:
|
||||
found = helpers.evaluate_call_of_leaf(
|
||||
random_context,
|
||||
name,
|
||||
cut_own_trailer=True
|
||||
)
|
||||
if sequence in found:
|
||||
# The arrays match. Now add the results
|
||||
added_types |= find_additions(
|
||||
random_context,
|
||||
execution_trailer.children[1],
|
||||
add_name
|
||||
)
|
||||
|
||||
# reset settings
|
||||
settings.dynamic_params_for_other_modules = temp_param_add
|
||||
debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA')
|
||||
return added_types
|
||||
|
||||
|
||||
def get_dynamic_array_instance(instance):
|
||||
"""Used for set() and list() instances."""
|
||||
if not settings.dynamic_array_additions:
|
||||
return instance.var_args
|
||||
|
||||
ai = _ArrayInstance(instance)
|
||||
from jedi.evaluate import param
|
||||
return param.ValuesArguments([[ai]])
|
||||
|
||||
|
||||
class _ArrayInstance(object):
|
||||
"""
|
||||
Used for the usage of set() and list().
|
||||
This is definitely a hack, but a good one :-)
|
||||
It makes it possible to use set/list conversions.
|
||||
|
||||
In contrast to Array, ListComprehension and all other iterable types, this
|
||||
is something that is only used inside `evaluate/compiled/fake/builtins.py`
|
||||
and therefore doesn't need filters, `py__bool__` and so on, because
|
||||
we don't use these operations in `builtins.py`.
|
||||
"""
|
||||
def __init__(self, instance):
|
||||
self.instance = instance
|
||||
self.var_args = instance.var_args
|
||||
|
||||
def py__iter__(self):
|
||||
var_args = self.var_args
|
||||
try:
|
||||
_, lazy_context = next(var_args.unpack())
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
for lazy in py__iter__(self.instance.evaluator, lazy_context.infer()):
|
||||
yield lazy
|
||||
|
||||
from jedi.evaluate import param
|
||||
if isinstance(var_args, param.TreeArguments):
|
||||
additions = _check_array_additions(var_args.context, self.instance)
|
||||
for addition in additions:
|
||||
yield addition
|
||||
|
||||
|
||||
class Slice(context.Context):
|
||||
def __init__(self, context, start, stop, step):
|
||||
super(Slice, self).__init__(
|
||||
context.evaluator,
|
||||
parent_context=context.evaluator.BUILTINS
|
||||
)
|
||||
self._context = context
|
||||
# all of them are either a Precedence or None.
|
||||
self._start = start
|
||||
self._stop = stop
|
||||
self._step = step
|
||||
|
||||
@property
|
||||
def obj(self):
|
||||
"""
|
||||
Imitate CompiledObject.obj behavior and return a ``builtin.slice()``
|
||||
object.
|
||||
"""
|
||||
def get(element):
|
||||
if element is None:
|
||||
return None
|
||||
|
||||
result = self._context.eval_node(element)
|
||||
if len(result) != 1:
|
||||
# For simplicity, we want slices to be clear defined with just
|
||||
# one type. Otherwise we will return an empty slice object.
|
||||
raise IndexError
|
||||
try:
|
||||
return list(result)[0].obj
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
try:
|
||||
return slice(get(self._start), get(self._stop), get(self._step))
|
||||
except IndexError:
|
||||
return slice(None, None, None)
|
||||
|
||||
|
||||
def create_index_types(evaluator, context, index):
|
||||
"""
|
||||
Handles slices in subscript nodes.
|
||||
"""
|
||||
if index == ':':
|
||||
# Like array[:]
|
||||
return set([Slice(context, None, None, None)])
|
||||
|
||||
elif index.type == 'subscript' and not index.children[0] == '.':
|
||||
# subscript basically implies a slice operation, except for Python 2's
|
||||
# Ellipsis.
|
||||
# e.g. array[:3]
|
||||
result = []
|
||||
for el in index.children:
|
||||
if el == ':':
|
||||
if not result:
|
||||
result.append(None)
|
||||
elif el.type == 'sliceop':
|
||||
if len(el.children) == 2:
|
||||
result.append(el.children[1])
|
||||
else:
|
||||
result.append(el)
|
||||
result += [None] * (3 - len(result))
|
||||
|
||||
return set([Slice(context, *result)])
|
||||
|
||||
# No slices
|
||||
return context.eval_node(index)
|
@ -0,0 +1,100 @@
|
||||
"""
|
||||
This module is not intended to be used in jedi, rather it will be fed to the
|
||||
jedi-parser to replace classes in the typing module
|
||||
"""
|
||||
|
||||
try:
|
||||
from collections import abc
|
||||
except ImportError:
|
||||
# python 2
|
||||
import collections as abc
|
||||
|
||||
|
||||
def factory(typing_name, indextypes):
|
||||
class Iterable(abc.Iterable):
|
||||
def __iter__(self):
|
||||
while True:
|
||||
yield indextypes[0]()
|
||||
|
||||
class Iterator(Iterable, abc.Iterator):
|
||||
def next(self):
|
||||
""" needed for python 2 """
|
||||
return self.__next__()
|
||||
|
||||
def __next__(self):
|
||||
return indextypes[0]()
|
||||
|
||||
class Sequence(abc.Sequence):
|
||||
def __getitem__(self, index):
|
||||
return indextypes[0]()
|
||||
|
||||
class MutableSequence(Sequence, abc.MutableSequence):
|
||||
pass
|
||||
|
||||
class List(MutableSequence, list):
|
||||
pass
|
||||
|
||||
class Tuple(Sequence, tuple):
|
||||
def __getitem__(self, index):
|
||||
if indextypes[1] == Ellipsis:
|
||||
# https://www.python.org/dev/peps/pep-0484/#the-typing-module
|
||||
# Tuple[int, ...] means a tuple of ints of indetermined length
|
||||
return indextypes[0]()
|
||||
else:
|
||||
return indextypes[index]()
|
||||
|
||||
class AbstractSet(Iterable, abc.Set):
|
||||
pass
|
||||
|
||||
class MutableSet(AbstractSet, abc.MutableSet):
|
||||
pass
|
||||
|
||||
class KeysView(Iterable, abc.KeysView):
|
||||
pass
|
||||
|
||||
class ValuesView(abc.ValuesView):
|
||||
def __iter__(self):
|
||||
while True:
|
||||
yield indextypes[1]()
|
||||
|
||||
class ItemsView(abc.ItemsView):
|
||||
def __iter__(self):
|
||||
while True:
|
||||
yield (indextypes[0](), indextypes[1]())
|
||||
|
||||
class Mapping(Iterable, abc.Mapping):
|
||||
def __getitem__(self, item):
|
||||
return indextypes[1]()
|
||||
|
||||
def keys(self):
|
||||
return KeysView()
|
||||
|
||||
def values(self):
|
||||
return ValuesView()
|
||||
|
||||
def items(self):
|
||||
return ItemsView()
|
||||
|
||||
class MutableMapping(Mapping, abc.MutableMapping):
|
||||
pass
|
||||
|
||||
class Dict(MutableMapping, dict):
|
||||
pass
|
||||
|
||||
dct = {
|
||||
"Sequence": Sequence,
|
||||
"MutableSequence": MutableSequence,
|
||||
"List": List,
|
||||
"Iterable": Iterable,
|
||||
"Iterator": Iterator,
|
||||
"AbstractSet": AbstractSet,
|
||||
"MutableSet": MutableSet,
|
||||
"Mapping": Mapping,
|
||||
"MutableMapping": MutableMapping,
|
||||
"Tuple": Tuple,
|
||||
"KeysView": KeysView,
|
||||
"ItemsView": ItemsView,
|
||||
"ValuesView": ValuesView,
|
||||
"Dict": Dict,
|
||||
}
|
||||
return dct[typing_name]
|
@ -0,0 +1,433 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from jedi._compatibility import zip_longest
|
||||
from jedi import debug
|
||||
from jedi import common
|
||||
from parso.python import tree
|
||||
from jedi.evaluate import iterable
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate import context
|
||||
from jedi.evaluate import docstrings
|
||||
from jedi.evaluate import pep0484
|
||||
from jedi.evaluate.filters import ParamName
|
||||
|
||||
|
||||
def add_argument_issue(parent_context, error_name, lazy_context, message):
|
||||
if isinstance(lazy_context, context.LazyTreeContext):
|
||||
node = lazy_context.data
|
||||
if node.parent.type == 'argument':
|
||||
node = node.parent
|
||||
analysis.add(parent_context, error_name, node, message)
|
||||
|
||||
|
||||
def try_iter_content(types, depth=0):
|
||||
"""Helper method for static analysis."""
|
||||
if depth > 10:
|
||||
# It's possible that a loop has references on itself (especially with
|
||||
# CompiledObject). Therefore don't loop infinitely.
|
||||
return
|
||||
|
||||
for typ in types:
|
||||
try:
|
||||
f = typ.py__iter__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for lazy_context in f():
|
||||
try_iter_content(lazy_context.infer(), depth + 1)
|
||||
|
||||
|
||||
class AbstractArguments():
|
||||
context = None
|
||||
|
||||
def eval_argument_clinic(self, parameters):
|
||||
"""Uses a list with argument clinic information (see PEP 436)."""
|
||||
iterator = self.unpack()
|
||||
for i, (name, optional, allow_kwargs) in enumerate(parameters):
|
||||
key, argument = next(iterator, (None, None))
|
||||
if key is not None:
|
||||
raise NotImplementedError
|
||||
if argument is None and not optional:
|
||||
debug.warning('TypeError: %s expected at least %s arguments, got %s',
|
||||
name, len(parameters), i)
|
||||
raise ValueError
|
||||
values = set() if argument is None else argument.infer()
|
||||
|
||||
if not values and not optional:
|
||||
# For the stdlib we always want values. If we don't get them,
|
||||
# that's ok, maybe something is too hard to resolve, however,
|
||||
# we will not proceed with the evaluation of that function.
|
||||
debug.warning('argument_clinic "%s" not resolvable.', name)
|
||||
raise ValueError
|
||||
yield values
|
||||
|
||||
def eval_all(self, funcdef=None):
|
||||
"""
|
||||
Evaluates all arguments as a support for static analysis
|
||||
(normally Jedi).
|
||||
"""
|
||||
for key, lazy_context in self.unpack():
|
||||
types = lazy_context.infer()
|
||||
try_iter_content(types)
|
||||
|
||||
def get_calling_nodes(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_params(self, execution_context):
|
||||
return get_params(execution_context, self)
|
||||
|
||||
|
||||
class AnonymousArguments(AbstractArguments):
|
||||
def get_params(self, execution_context):
|
||||
from jedi.evaluate.dynamic import search_params
|
||||
return search_params(
|
||||
execution_context.evaluator,
|
||||
execution_context,
|
||||
execution_context.tree_node
|
||||
)
|
||||
|
||||
|
||||
class TreeArguments(AbstractArguments):
|
||||
def __init__(self, evaluator, context, argument_node, trailer=None):
|
||||
"""
|
||||
The argument_node is either a parser node or a list of evaluated
|
||||
objects. Those evaluated objects may be lists of evaluated objects
|
||||
themselves (one list for the first argument, one for the second, etc).
|
||||
|
||||
:param argument_node: May be an argument_node or a list of nodes.
|
||||
"""
|
||||
self.argument_node = argument_node
|
||||
self.context = context
|
||||
self._evaluator = evaluator
|
||||
self.trailer = trailer # Can be None, e.g. in a class definition.
|
||||
|
||||
def _split(self):
|
||||
if isinstance(self.argument_node, (tuple, list)):
|
||||
for el in self.argument_node:
|
||||
yield 0, el
|
||||
else:
|
||||
if not (self.argument_node.type == 'arglist' or (
|
||||
# in python 3.5 **arg is an argument, not arglist
|
||||
(self.argument_node.type == 'argument') and
|
||||
self.argument_node.children[0] in ('*', '**'))):
|
||||
yield 0, self.argument_node
|
||||
return
|
||||
|
||||
iterator = iter(self.argument_node.children)
|
||||
for child in iterator:
|
||||
if child == ',':
|
||||
continue
|
||||
elif child in ('*', '**'):
|
||||
yield len(child.value), next(iterator)
|
||||
elif child.type == 'argument' and \
|
||||
child.children[0] in ('*', '**'):
|
||||
assert len(child.children) == 2
|
||||
yield len(child.children[0].value), child.children[1]
|
||||
else:
|
||||
yield 0, child
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
named_args = []
|
||||
for star_count, el in self._split():
|
||||
if star_count == 1:
|
||||
arrays = self.context.eval_node(el)
|
||||
iterators = [_iterate_star_args(self.context, a, el, funcdef)
|
||||
for a in arrays]
|
||||
iterators = list(iterators)
|
||||
for values in list(zip_longest(*iterators)):
|
||||
# TODO zip_longest yields None, that means this would raise
|
||||
# an exception?
|
||||
yield None, context.get_merged_lazy_context(
|
||||
[v for v in values if v is not None]
|
||||
)
|
||||
elif star_count == 2:
|
||||
arrays = self._evaluator.eval_element(self.context, el)
|
||||
for dct in arrays:
|
||||
for key, values in _star_star_dict(self.context, dct, el, funcdef):
|
||||
yield key, values
|
||||
else:
|
||||
if el.type == 'argument':
|
||||
c = el.children
|
||||
if len(c) == 3: # Keyword argument.
|
||||
named_args.append((c[0].value, context.LazyTreeContext(self.context, c[2]),))
|
||||
else: # Generator comprehension.
|
||||
# Include the brackets with the parent.
|
||||
comp = iterable.GeneratorComprehension(
|
||||
self._evaluator, self.context, self.argument_node.parent)
|
||||
yield None, context.LazyKnownContext(comp)
|
||||
else:
|
||||
yield None, context.LazyTreeContext(self.context, el)
|
||||
|
||||
# Reordering var_args is necessary, because star args sometimes appear
|
||||
# after named argument, but in the actual order it's prepended.
|
||||
for named_arg in named_args:
|
||||
yield named_arg
|
||||
|
||||
def as_tree_tuple_objects(self):
|
||||
for star_count, argument in self._split():
|
||||
if argument.type == 'argument':
|
||||
argument, default = argument.children[::2]
|
||||
else:
|
||||
default = None
|
||||
yield argument, default, star_count
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
|
||||
|
||||
def get_calling_nodes(self):
|
||||
from jedi.evaluate.dynamic import MergedExecutedParams
|
||||
old_arguments_list = []
|
||||
arguments = self
|
||||
|
||||
while arguments not in old_arguments_list:
|
||||
if not isinstance(arguments, TreeArguments):
|
||||
break
|
||||
|
||||
old_arguments_list.append(arguments)
|
||||
for name, default, star_count in reversed(list(arguments.as_tree_tuple_objects())):
|
||||
if not star_count or not isinstance(name, tree.Name):
|
||||
continue
|
||||
|
||||
names = self._evaluator.goto(arguments.context, name)
|
||||
if len(names) != 1:
|
||||
break
|
||||
if not isinstance(names[0], ParamName):
|
||||
break
|
||||
param = names[0].get_param()
|
||||
if isinstance(param, MergedExecutedParams):
|
||||
# For dynamic searches we don't even want to see errors.
|
||||
return []
|
||||
if not isinstance(param, ExecutedParam):
|
||||
break
|
||||
if param.var_args is None:
|
||||
break
|
||||
arguments = param.var_args
|
||||
break
|
||||
|
||||
return [arguments.argument_node or arguments.trailer]
|
||||
|
||||
|
||||
class ValuesArguments(AbstractArguments):
|
||||
def __init__(self, values_list):
|
||||
self._values_list = values_list
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
for values in self._values_list:
|
||||
yield None, context.LazyKnownContexts(values)
|
||||
|
||||
def get_calling_nodes(self):
|
||||
return []
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._values_list)
|
||||
|
||||
|
||||
class ExecutedParam(object):
|
||||
"""Fake a param and give it values."""
|
||||
def __init__(self, execution_context, param_node, lazy_context):
|
||||
self._execution_context = execution_context
|
||||
self._param_node = param_node
|
||||
self._lazy_context = lazy_context
|
||||
self.string_name = param_node.name.value
|
||||
|
||||
def infer(self):
|
||||
pep0484_hints = pep0484.infer_param(self._execution_context, self._param_node)
|
||||
doc_params = docstrings.infer_param(self._execution_context, self._param_node)
|
||||
if pep0484_hints or doc_params:
|
||||
return list(set(pep0484_hints) | set(doc_params))
|
||||
|
||||
return self._lazy_context.infer()
|
||||
|
||||
@property
|
||||
def var_args(self):
|
||||
return self._execution_context.var_args
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.string_name)
|
||||
|
||||
|
||||
def get_params(execution_context, var_args):
|
||||
result_params = []
|
||||
param_dict = {}
|
||||
funcdef = execution_context.tree_node
|
||||
parent_context = execution_context.parent_context
|
||||
|
||||
for param in funcdef.get_params():
|
||||
param_dict[param.name.value] = param
|
||||
unpacked_va = list(var_args.unpack(funcdef))
|
||||
var_arg_iterator = common.PushBackIterator(iter(unpacked_va))
|
||||
|
||||
non_matching_keys = defaultdict(lambda: [])
|
||||
keys_used = {}
|
||||
keys_only = False
|
||||
had_multiple_value_error = False
|
||||
for param in funcdef.get_params():
|
||||
# The value and key can both be null. There, the defaults apply.
|
||||
# args / kwargs will just be empty arrays / dicts, respectively.
|
||||
# Wrong value count is just ignored. If you try to test cases that are
|
||||
# not allowed in Python, Jedi will maybe not show any completions.
|
||||
key, argument = next(var_arg_iterator, (None, None))
|
||||
while key is not None:
|
||||
keys_only = True
|
||||
try:
|
||||
key_param = param_dict[key]
|
||||
except KeyError:
|
||||
non_matching_keys[key] = argument
|
||||
else:
|
||||
if key in keys_used:
|
||||
had_multiple_value_error = True
|
||||
m = ("TypeError: %s() got multiple values for keyword argument '%s'."
|
||||
% (funcdef.name, key))
|
||||
for node in var_args.get_calling_nodes():
|
||||
analysis.add(parent_context, 'type-error-multiple-values',
|
||||
node, message=m)
|
||||
else:
|
||||
keys_used[key] = ExecutedParam(execution_context, key_param, argument)
|
||||
key, argument = next(var_arg_iterator, (None, None))
|
||||
|
||||
try:
|
||||
result_params.append(keys_used[param.name.value])
|
||||
continue
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if param.star_count == 1:
|
||||
# *args param
|
||||
lazy_context_list = []
|
||||
if argument is not None:
|
||||
lazy_context_list.append(argument)
|
||||
for key, argument in var_arg_iterator:
|
||||
# Iterate until a key argument is found.
|
||||
if key:
|
||||
var_arg_iterator.push_back((key, argument))
|
||||
break
|
||||
lazy_context_list.append(argument)
|
||||
seq = iterable.FakeSequence(execution_context.evaluator, 'tuple', lazy_context_list)
|
||||
result_arg = context.LazyKnownContext(seq)
|
||||
elif param.star_count == 2:
|
||||
# **kwargs param
|
||||
dct = iterable.FakeDict(execution_context.evaluator, dict(non_matching_keys))
|
||||
result_arg = context.LazyKnownContext(dct)
|
||||
non_matching_keys = {}
|
||||
else:
|
||||
# normal param
|
||||
if argument is None:
|
||||
# No value: Return an empty container
|
||||
if param.default is None:
|
||||
result_arg = context.LazyUnknownContext()
|
||||
if not keys_only:
|
||||
for node in var_args.get_calling_nodes():
|
||||
m = _error_argument_count(funcdef, len(unpacked_va))
|
||||
analysis.add(parent_context, 'type-error-too-few-arguments',
|
||||
node, message=m)
|
||||
else:
|
||||
result_arg = context.LazyTreeContext(parent_context, param.default)
|
||||
else:
|
||||
result_arg = argument
|
||||
|
||||
result_params.append(ExecutedParam(execution_context, param, result_arg))
|
||||
if not isinstance(result_arg, context.LazyUnknownContext):
|
||||
keys_used[param.name.value] = result_params[-1]
|
||||
|
||||
if keys_only:
|
||||
# All arguments should be handed over to the next function. It's not
|
||||
# about the values inside, it's about the names. Jedi needs to now that
|
||||
# there's nothing to find for certain names.
|
||||
for k in set(param_dict) - set(keys_used):
|
||||
param = param_dict[k]
|
||||
|
||||
if not (non_matching_keys or had_multiple_value_error or
|
||||
param.star_count or param.default):
|
||||
# add a warning only if there's not another one.
|
||||
for node in var_args.get_calling_nodes():
|
||||
m = _error_argument_count(funcdef, len(unpacked_va))
|
||||
analysis.add(parent_context, 'type-error-too-few-arguments',
|
||||
node, message=m)
|
||||
|
||||
for key, lazy_context in non_matching_keys.items():
|
||||
m = "TypeError: %s() got an unexpected keyword argument '%s'." \
|
||||
% (funcdef.name, key)
|
||||
add_argument_issue(
|
||||
parent_context,
|
||||
'type-error-keyword-argument',
|
||||
lazy_context,
|
||||
message=m
|
||||
)
|
||||
|
||||
remaining_arguments = list(var_arg_iterator)
|
||||
if remaining_arguments:
|
||||
m = _error_argument_count(funcdef, len(unpacked_va))
|
||||
# Just report an error for the first param that is not needed (like
|
||||
# cPython).
|
||||
first_key, lazy_context = remaining_arguments[0]
|
||||
if var_args.get_calling_nodes():
|
||||
# There might not be a valid calling node so check for that first.
|
||||
add_argument_issue(parent_context, 'type-error-too-many-arguments', lazy_context, message=m)
|
||||
return result_params
|
||||
|
||||
|
||||
def _iterate_star_args(context, array, input_node, funcdef=None):
|
||||
try:
|
||||
iter_ = array.py__iter__
|
||||
except AttributeError:
|
||||
if funcdef is not None:
|
||||
# TODO this funcdef should not be needed.
|
||||
m = "TypeError: %s() argument after * must be a sequence, not %s" \
|
||||
% (funcdef.name.value, array)
|
||||
analysis.add(context, 'type-error-star', input_node, message=m)
|
||||
else:
|
||||
for lazy_context in iter_():
|
||||
yield lazy_context
|
||||
|
||||
|
||||
def _star_star_dict(context, array, input_node, funcdef):
|
||||
from jedi.evaluate.instance import CompiledInstance
|
||||
if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
|
||||
# For now ignore this case. In the future add proper iterators and just
|
||||
# make one call without crazy isinstance checks.
|
||||
return {}
|
||||
elif isinstance(array, iterable.AbstractSequence) and array.array_type == 'dict':
|
||||
return array.exact_key_items()
|
||||
else:
|
||||
if funcdef is not None:
|
||||
m = "TypeError: %s argument after ** must be a mapping, not %s" \
|
||||
% (funcdef.name.value, array)
|
||||
analysis.add(context, 'type-error-star-star', input_node, message=m)
|
||||
return {}
|
||||
|
||||
|
||||
def _error_argument_count(funcdef, actual_count):
|
||||
params = funcdef.get_params()
|
||||
default_arguments = sum(1 for p in params if p.default or p.star_count)
|
||||
|
||||
if default_arguments == 0:
|
||||
before = 'exactly '
|
||||
else:
|
||||
before = 'from %s to ' % (len(params) - default_arguments)
|
||||
return ('TypeError: %s() takes %s%s arguments (%s given).'
|
||||
% (funcdef.name, before, len(params), actual_count))
|
||||
|
||||
|
||||
def _create_default_param(execution_context, param):
|
||||
if param.star_count == 1:
|
||||
result_arg = context.LazyKnownContext(
|
||||
iterable.FakeSequence(execution_context.evaluator, 'tuple', [])
|
||||
)
|
||||
elif param.star_count == 2:
|
||||
result_arg = context.LazyKnownContext(
|
||||
iterable.FakeDict(execution_context.evaluator, {})
|
||||
)
|
||||
elif param.default is None:
|
||||
result_arg = context.LazyUnknownContext()
|
||||
else:
|
||||
result_arg = context.LazyTreeContext(execution_context.parent_context, param.default)
|
||||
return ExecutedParam(execution_context, param, result_arg)
|
||||
|
||||
|
||||
def create_default_params(execution_context, funcdef):
|
||||
return [_create_default_param(execution_context, p)
|
||||
for p in funcdef.get_params()]
|
||||
|
@ -0,0 +1,6 @@
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
|
||||
|
||||
@evaluator_function_cache()
|
||||
def get_yield_exprs(evaluator, funcdef):
|
||||
return list(funcdef.iter_yield_exprs())
|
@ -0,0 +1,224 @@
|
||||
"""
|
||||
PEP 0484 ( https://www.python.org/dev/peps/pep-0484/ ) describes type hints
|
||||
through function annotations. There is a strong suggestion in this document
|
||||
that only the type of type hinting defined in PEP0484 should be allowed
|
||||
as annotations in future python versions.
|
||||
|
||||
The (initial / probably incomplete) implementation todo list for pep-0484:
|
||||
v Function parameter annotations with builtin/custom type classes
|
||||
v Function returntype annotations with builtin/custom type classes
|
||||
v Function parameter annotations with strings (forward reference)
|
||||
v Function return type annotations with strings (forward reference)
|
||||
v Local variable type hints
|
||||
v Assigned types: `Url = str\ndef get(url:Url) -> str:`
|
||||
v Type hints in `with` statements
|
||||
x Stub files support
|
||||
x support `@no_type_check` and `@no_type_check_decorator`
|
||||
x support for typing.cast() operator
|
||||
x support for type hint comments for functions, `# type: (int, str) -> int`.
|
||||
See comment from Guido https://github.com/davidhalter/jedi/issues/662
|
||||
"""
|
||||
|
||||
import itertools
|
||||
import os
|
||||
import re
|
||||
|
||||
from parso import ParserSyntaxError
|
||||
from parso.python import tree
|
||||
|
||||
from jedi.common import unite
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.context import LazyTreeContext
|
||||
from jedi import debug
|
||||
from jedi import _compatibility
|
||||
from jedi import parser_utils
|
||||
|
||||
|
||||
def _evaluate_for_annotation(context, annotation, index=None):
|
||||
"""
|
||||
Evaluates a string-node, looking for an annotation
|
||||
If index is not None, the annotation is expected to be a tuple
|
||||
and we're interested in that index
|
||||
"""
|
||||
if annotation is not None:
|
||||
definitions = context.eval_node(
|
||||
_fix_forward_reference(context, annotation))
|
||||
if index is not None:
|
||||
definitions = list(itertools.chain.from_iterable(
|
||||
definition.py__getitem__(index) for definition in definitions
|
||||
if definition.array_type == 'tuple' and
|
||||
len(list(definition.py__iter__())) >= index))
|
||||
return unite(d.execute_evaluated() for d in definitions)
|
||||
else:
|
||||
return set()
|
||||
|
||||
|
||||
def _fix_forward_reference(context, node):
|
||||
evaled_nodes = context.eval_node(node)
|
||||
if len(evaled_nodes) != 1:
|
||||
debug.warning("Eval'ed typing index %s should lead to 1 object, "
|
||||
" not %s" % (node, evaled_nodes))
|
||||
return node
|
||||
evaled_node = list(evaled_nodes)[0]
|
||||
if isinstance(evaled_node, compiled.CompiledObject) and \
|
||||
isinstance(evaled_node.obj, str):
|
||||
try:
|
||||
new_node = context.evaluator.grammar.parse(
|
||||
_compatibility.unicode(evaled_node.obj),
|
||||
start_symbol='eval_input',
|
||||
error_recovery=False
|
||||
)
|
||||
except ParserSyntaxError:
|
||||
debug.warning('Annotation not parsed: %s' % evaled_node.obj)
|
||||
return node
|
||||
else:
|
||||
module = node.get_root_node()
|
||||
parser_utils.move(new_node, module.end_pos[0])
|
||||
new_node.parent = context.tree_node
|
||||
return new_node
|
||||
else:
|
||||
return node
|
||||
|
||||
|
||||
@evaluator_method_cache()
|
||||
def infer_param(execution_context, param):
|
||||
annotation = param.annotation
|
||||
module_context = execution_context.get_root_context()
|
||||
return _evaluate_for_annotation(module_context, annotation)
|
||||
|
||||
|
||||
def py__annotations__(funcdef):
|
||||
return_annotation = funcdef.annotation
|
||||
if return_annotation:
|
||||
dct = {'return': return_annotation}
|
||||
else:
|
||||
dct = {}
|
||||
for function_param in funcdef.get_params():
|
||||
param_annotation = function_param.annotation
|
||||
if param_annotation is not None:
|
||||
dct[function_param.name.value] = param_annotation
|
||||
return dct
|
||||
|
||||
|
||||
@evaluator_method_cache()
|
||||
def infer_return_types(function_context):
|
||||
annotation = py__annotations__(function_context.tree_node).get("return", None)
|
||||
module_context = function_context.get_root_context()
|
||||
return _evaluate_for_annotation(module_context, annotation)
|
||||
|
||||
|
||||
_typing_module = None
|
||||
|
||||
|
||||
def _get_typing_replacement_module(grammar):
|
||||
"""
|
||||
The idea is to return our jedi replacement for the PEP-0484 typing module
|
||||
as discussed at https://github.com/davidhalter/jedi/issues/663
|
||||
"""
|
||||
global _typing_module
|
||||
if _typing_module is None:
|
||||
typing_path = \
|
||||
os.path.abspath(os.path.join(__file__, "../jedi_typing.py"))
|
||||
with open(typing_path) as f:
|
||||
code = _compatibility.unicode(f.read())
|
||||
_typing_module = grammar.parse(code)
|
||||
return _typing_module
|
||||
|
||||
|
||||
def py__getitem__(context, typ, node):
|
||||
if not typ.get_root_context().name.string_name == "typing":
|
||||
return None
|
||||
# we assume that any class using [] in a module called
|
||||
# "typing" with a name for which we have a replacement
|
||||
# should be replaced by that class. This is not 100%
|
||||
# airtight but I don't have a better idea to check that it's
|
||||
# actually the PEP-0484 typing module and not some other
|
||||
if node.type == "subscriptlist":
|
||||
nodes = node.children[::2] # skip the commas
|
||||
else:
|
||||
nodes = [node]
|
||||
del node
|
||||
|
||||
nodes = [_fix_forward_reference(context, node) for node in nodes]
|
||||
type_name = typ.name.string_name
|
||||
|
||||
# hacked in Union and Optional, since it's hard to do nicely in parsed code
|
||||
if type_name in ("Union", '_Union'):
|
||||
# In Python 3.6 it's still called typing.Union but it's an instance
|
||||
# called _Union.
|
||||
return unite(context.eval_node(node) for node in nodes)
|
||||
if type_name in ("Optional", '_Optional'):
|
||||
# Here we have the same issue like in Union. Therefore we also need to
|
||||
# check for the instance typing._Optional (Python 3.6).
|
||||
return context.eval_node(nodes[0])
|
||||
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
typing = ModuleContext(
|
||||
context.evaluator,
|
||||
module_node=_get_typing_replacement_module(context.evaluator.latest_grammar),
|
||||
path=None
|
||||
)
|
||||
factories = typing.py__getattribute__("factory")
|
||||
assert len(factories) == 1
|
||||
factory = list(factories)[0]
|
||||
assert factory
|
||||
function_body_nodes = factory.tree_node.children[4].children
|
||||
valid_classnames = set(child.name.value
|
||||
for child in function_body_nodes
|
||||
if isinstance(child, tree.Class))
|
||||
if type_name not in valid_classnames:
|
||||
return None
|
||||
compiled_classname = compiled.create(context.evaluator, type_name)
|
||||
|
||||
from jedi.evaluate.iterable import FakeSequence
|
||||
args = FakeSequence(
|
||||
context.evaluator,
|
||||
"tuple",
|
||||
[LazyTreeContext(context, n) for n in nodes]
|
||||
)
|
||||
|
||||
result = factory.execute_evaluated(compiled_classname, args)
|
||||
return result
|
||||
|
||||
|
||||
def find_type_from_comment_hint_for(context, node, name):
|
||||
return _find_type_from_comment_hint(context, node, node.children[1], name)
|
||||
|
||||
|
||||
def find_type_from_comment_hint_with(context, node, name):
|
||||
assert len(node.children[1].children) == 3, \
|
||||
"Can only be here when children[1] is 'foo() as f'"
|
||||
varlist = node.children[1].children[2]
|
||||
return _find_type_from_comment_hint(context, node, varlist, name)
|
||||
|
||||
|
||||
def find_type_from_comment_hint_assign(context, node, name):
|
||||
return _find_type_from_comment_hint(context, node, node.children[0], name)
|
||||
|
||||
|
||||
def _find_type_from_comment_hint(context, node, varlist, name):
|
||||
index = None
|
||||
if varlist.type in ("testlist_star_expr", "exprlist", "testlist"):
|
||||
# something like "a, b = 1, 2"
|
||||
index = 0
|
||||
for child in varlist.children:
|
||||
if child == name:
|
||||
break
|
||||
if child.type == "operator":
|
||||
continue
|
||||
index += 1
|
||||
else:
|
||||
return []
|
||||
|
||||
comment = parser_utils.get_following_comment_same_line(node)
|
||||
if comment is None:
|
||||
return []
|
||||
match = re.match(r"^#\s*type:\s*([^#]*)", comment)
|
||||
if not match:
|
||||
return []
|
||||
annotation = tree.String(
|
||||
repr(str(match.group(1).strip())),
|
||||
node.start_pos)
|
||||
annotation.parent = node.parent
|
||||
return _evaluate_for_annotation(context, annotation, index)
|
@ -0,0 +1,178 @@
|
||||
"""
|
||||
Handles operator precedence.
|
||||
"""
|
||||
import operator as op
|
||||
|
||||
from jedi._compatibility import unicode
|
||||
from jedi import debug
|
||||
from jedi.evaluate.compiled import CompiledObject, create, builtin_from_name
|
||||
from jedi.evaluate import analysis
|
||||
|
||||
# Maps Python syntax to the operator module.
|
||||
COMPARISON_OPERATORS = {
|
||||
'==': op.eq,
|
||||
'!=': op.ne,
|
||||
'is': op.is_,
|
||||
'is not': op.is_not,
|
||||
'<': op.lt,
|
||||
'<=': op.le,
|
||||
'>': op.gt,
|
||||
'>=': op.ge,
|
||||
}
|
||||
|
||||
|
||||
def literals_to_types(evaluator, result):
|
||||
# Changes literals ('a', 1, 1.0, etc) to its type instances (str(),
|
||||
# int(), float(), etc).
|
||||
new_result = set()
|
||||
for typ in result:
|
||||
if is_literal(typ):
|
||||
# Literals are only valid as long as the operations are
|
||||
# correct. Otherwise add a value-free instance.
|
||||
cls = builtin_from_name(evaluator, typ.name.string_name)
|
||||
new_result |= cls.execute_evaluated()
|
||||
else:
|
||||
new_result.add(typ)
|
||||
return new_result
|
||||
|
||||
|
||||
def calculate_children(evaluator, context, children):
|
||||
"""
|
||||
Calculate a list of children with operators.
|
||||
"""
|
||||
iterator = iter(children)
|
||||
types = context.eval_node(next(iterator))
|
||||
for operator in iterator:
|
||||
right = next(iterator)
|
||||
if operator.type == 'comp_op': # not in / is not
|
||||
operator = ' '.join(c.value for c in operator.children)
|
||||
|
||||
# handle lazy evaluation of and/or here.
|
||||
if operator in ('and', 'or'):
|
||||
left_bools = set([left.py__bool__() for left in types])
|
||||
if left_bools == set([True]):
|
||||
if operator == 'and':
|
||||
types = context.eval_node(right)
|
||||
elif left_bools == set([False]):
|
||||
if operator != 'and':
|
||||
types = context.eval_node(right)
|
||||
# Otherwise continue, because of uncertainty.
|
||||
else:
|
||||
types = calculate(evaluator, context, types, operator,
|
||||
context.eval_node(right))
|
||||
debug.dbg('calculate_children types %s', types)
|
||||
return types
|
||||
|
||||
|
||||
def calculate(evaluator, context, left_result, operator, right_result):
|
||||
result = set()
|
||||
if not left_result or not right_result:
|
||||
# illegal slices e.g. cause left/right_result to be None
|
||||
result = (left_result or set()) | (right_result or set())
|
||||
result = literals_to_types(evaluator, result)
|
||||
else:
|
||||
# I don't think there's a reasonable chance that a string
|
||||
# operation is still correct, once we pass something like six
|
||||
# objects.
|
||||
if len(left_result) * len(right_result) > 6:
|
||||
result = literals_to_types(evaluator, left_result | right_result)
|
||||
else:
|
||||
for left in left_result:
|
||||
for right in right_result:
|
||||
result |= _element_calculate(evaluator, context, left, operator, right)
|
||||
return result
|
||||
|
||||
|
||||
def factor_calculate(evaluator, types, operator):
|
||||
"""
|
||||
Calculates `+`, `-`, `~` and `not` prefixes.
|
||||
"""
|
||||
for typ in types:
|
||||
if operator == '-':
|
||||
if _is_number(typ):
|
||||
yield create(evaluator, -typ.obj)
|
||||
elif operator == 'not':
|
||||
value = typ.py__bool__()
|
||||
if value is None: # Uncertainty.
|
||||
return
|
||||
yield create(evaluator, not value)
|
||||
else:
|
||||
yield typ
|
||||
|
||||
|
||||
def _is_number(obj):
|
||||
return isinstance(obj, CompiledObject) \
|
||||
and isinstance(obj.obj, (int, float))
|
||||
|
||||
|
||||
def is_string(obj):
|
||||
return isinstance(obj, CompiledObject) \
|
||||
and isinstance(obj.obj, (str, unicode))
|
||||
|
||||
|
||||
def is_literal(obj):
|
||||
return _is_number(obj) or is_string(obj)
|
||||
|
||||
|
||||
def _is_tuple(obj):
|
||||
from jedi.evaluate import iterable
|
||||
return isinstance(obj, iterable.AbstractSequence) and obj.array_type == 'tuple'
|
||||
|
||||
|
||||
def _is_list(obj):
|
||||
from jedi.evaluate import iterable
|
||||
return isinstance(obj, iterable.AbstractSequence) and obj.array_type == 'list'
|
||||
|
||||
|
||||
def _element_calculate(evaluator, context, left, operator, right):
|
||||
from jedi.evaluate import iterable, instance
|
||||
l_is_num = _is_number(left)
|
||||
r_is_num = _is_number(right)
|
||||
if operator == '*':
|
||||
# for iterables, ignore * operations
|
||||
if isinstance(left, iterable.AbstractSequence) or is_string(left):
|
||||
return set([left])
|
||||
elif isinstance(right, iterable.AbstractSequence) or is_string(right):
|
||||
return set([right])
|
||||
elif operator == '+':
|
||||
if l_is_num and r_is_num or is_string(left) and is_string(right):
|
||||
return set([create(evaluator, left.obj + right.obj)])
|
||||
elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right):
|
||||
return set([iterable.MergedArray(evaluator, (left, right))])
|
||||
elif operator == '-':
|
||||
if l_is_num and r_is_num:
|
||||
return set([create(evaluator, left.obj - right.obj)])
|
||||
elif operator == '%':
|
||||
# With strings and numbers the left type typically remains. Except for
|
||||
# `int() % float()`.
|
||||
return set([left])
|
||||
elif operator in COMPARISON_OPERATORS:
|
||||
operation = COMPARISON_OPERATORS[operator]
|
||||
if isinstance(left, CompiledObject) and isinstance(right, CompiledObject):
|
||||
# Possible, because the return is not an option. Just compare.
|
||||
left = left.obj
|
||||
right = right.obj
|
||||
|
||||
try:
|
||||
result = operation(left, right)
|
||||
except TypeError:
|
||||
# Could be True or False.
|
||||
return set([create(evaluator, True), create(evaluator, False)])
|
||||
else:
|
||||
return set([create(evaluator, result)])
|
||||
elif operator == 'in':
|
||||
return set()
|
||||
|
||||
def check(obj):
|
||||
"""Checks if a Jedi object is either a float or an int."""
|
||||
return isinstance(obj, instance.CompiledInstance) and \
|
||||
obj.name.string_name in ('int', 'float')
|
||||
|
||||
# Static analysis, one is a number, the other one is not.
|
||||
if operator in ('+', '-') and l_is_num != r_is_num \
|
||||
and not (check(left) or check(right)):
|
||||
message = "TypeError: unsupported operand type(s) for +: %s and %s"
|
||||
analysis.add(context, 'type-error-operation', operator,
|
||||
message % (left, right))
|
||||
|
||||
return set([left, right])
|
@ -0,0 +1,134 @@
|
||||
"""
|
||||
Recursions are the recipe of |jedi| to conquer Python code. However, someone
|
||||
must stop recursions going mad. Some settings are here to make |jedi| stop at
|
||||
the right time. You can read more about them :ref:`here <settings-recursion>`.
|
||||
|
||||
Next to :mod:`jedi.evaluate.cache` this module also makes |jedi| not
|
||||
thread-safe. Why? ``execution_recursion_decorator`` uses class variables to
|
||||
count the function calls.
|
||||
|
||||
.. _settings-recursion:
|
||||
|
||||
Settings
|
||||
~~~~~~~~~~
|
||||
|
||||
Recursion settings are important if you don't want extremly
|
||||
recursive python code to go absolutely crazy.
|
||||
|
||||
The default values are based on experiments while completing the |jedi| library
|
||||
itself (inception!). But I don't think there's any other Python library that
|
||||
uses recursion in a similarly extreme way. Completion should also be fast and
|
||||
therefore the quality might not always be maximal.
|
||||
|
||||
.. autodata:: recursion_limit
|
||||
.. autodata:: total_function_execution_limit
|
||||
.. autodata:: per_function_execution_limit
|
||||
.. autodata:: per_function_recursion_limit
|
||||
"""
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
from jedi import debug
|
||||
|
||||
|
||||
recursion_limit = 15
|
||||
"""
|
||||
Like ``sys.getrecursionlimit()``, just for |jedi|.
|
||||
"""
|
||||
total_function_execution_limit = 200
|
||||
"""
|
||||
This is a hard limit of how many non-builtin functions can be executed.
|
||||
"""
|
||||
per_function_execution_limit = 6
|
||||
"""
|
||||
The maximal amount of times a specific function may be executed.
|
||||
"""
|
||||
per_function_recursion_limit = 2
|
||||
"""
|
||||
A function may not be executed more than this number of times recursively.
|
||||
"""
|
||||
|
||||
class RecursionDetector(object):
|
||||
def __init__(self):
|
||||
self.pushed_nodes = []
|
||||
|
||||
|
||||
@contextmanager
|
||||
def execution_allowed(evaluator, node):
|
||||
"""
|
||||
A decorator to detect recursions in statements. In a recursion a statement
|
||||
at the same place, in the same module may not be executed two times.
|
||||
"""
|
||||
pushed_nodes = evaluator.recursion_detector.pushed_nodes
|
||||
|
||||
if node in pushed_nodes:
|
||||
debug.warning('catched stmt recursion: %s @%s', node,
|
||||
node.start_pos)
|
||||
yield False
|
||||
else:
|
||||
pushed_nodes.append(node)
|
||||
yield True
|
||||
pushed_nodes.pop()
|
||||
|
||||
|
||||
def execution_recursion_decorator(default=set()):
|
||||
def decorator(func):
|
||||
def wrapper(execution, **kwargs):
|
||||
detector = execution.evaluator.execution_recursion_detector
|
||||
allowed = detector.push_execution(execution)
|
||||
try:
|
||||
if allowed:
|
||||
result = default
|
||||
else:
|
||||
result = func(execution, **kwargs)
|
||||
finally:
|
||||
detector.pop_execution()
|
||||
return result
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
class ExecutionRecursionDetector(object):
|
||||
"""
|
||||
Catches recursions of executions.
|
||||
"""
|
||||
def __init__(self, evaluator):
|
||||
self._evaluator = evaluator
|
||||
|
||||
self._recursion_level = 0
|
||||
self._parent_execution_funcs = []
|
||||
self._funcdef_execution_counts = {}
|
||||
self._execution_count = 0
|
||||
|
||||
def pop_execution(self):
|
||||
self._parent_execution_funcs.pop()
|
||||
self._recursion_level -= 1
|
||||
|
||||
def push_execution(self, execution):
|
||||
funcdef = execution.tree_node
|
||||
|
||||
# These two will be undone in pop_execution.
|
||||
self._recursion_level += 1
|
||||
self._parent_execution_funcs.append(funcdef)
|
||||
|
||||
module = execution.get_root_context()
|
||||
if module == self._evaluator.BUILTINS:
|
||||
# We have control over builtins so we know they are not recursing
|
||||
# like crazy. Therefore we just let them execute always, because
|
||||
# they usually just help a lot with getting good results.
|
||||
return False
|
||||
|
||||
if self._recursion_level > recursion_limit:
|
||||
return True
|
||||
|
||||
if self._execution_count >= total_function_execution_limit:
|
||||
return True
|
||||
self._execution_count += 1
|
||||
|
||||
if self._funcdef_execution_counts.setdefault(funcdef, 0) >= per_function_execution_limit:
|
||||
return True
|
||||
self._funcdef_execution_counts[funcdef] += 1
|
||||
|
||||
if self._parent_execution_funcs.count(funcdef) > per_function_recursion_limit:
|
||||
return True
|
||||
return False
|
@ -0,0 +1,684 @@
|
||||
"""
|
||||
Like described in the :mod:`parso.python.tree` module,
|
||||
there's a need for an ast like module to represent the states of parsed
|
||||
modules.
|
||||
|
||||
But now there are also structures in Python that need a little bit more than
|
||||
that. An ``Instance`` for example is only a ``Class`` before it is
|
||||
instantiated. This class represents these cases.
|
||||
|
||||
So, why is there also a ``Class`` class here? Well, there are decorators and
|
||||
they change classes in Python 3.
|
||||
|
||||
Representation modules also define "magic methods". Those methods look like
|
||||
``py__foo__`` and are typically mappable to the Python equivalents ``__call__``
|
||||
and others. Here's a list:
|
||||
|
||||
====================================== ========================================
|
||||
**Method** **Description**
|
||||
-------------------------------------- ----------------------------------------
|
||||
py__call__(params: Array) On callable objects, returns types.
|
||||
py__bool__() Returns True/False/None; None means that
|
||||
there's no certainty.
|
||||
py__bases__() Returns a list of base classes.
|
||||
py__mro__() Returns a list of classes (the mro).
|
||||
py__iter__() Returns a generator of a set of types.
|
||||
py__class__() Returns the class of an instance.
|
||||
py__getitem__(index: int/str) Returns a a set of types of the index.
|
||||
Can raise an IndexError/KeyError.
|
||||
py__file__() Only on modules. Returns None if does
|
||||
not exist.
|
||||
py__package__() Only on modules. For the import system.
|
||||
py__path__() Only on modules. For the import system.
|
||||
py__get__(call_object) Only on instances. Simulates
|
||||
descriptors.
|
||||
py__doc__(include_call_signature: Returns the docstring for a context.
|
||||
bool)
|
||||
====================================== ========================================
|
||||
|
||||
"""
|
||||
import os
|
||||
import pkgutil
|
||||
import imp
|
||||
import re
|
||||
from itertools import chain
|
||||
|
||||
from parso.python import tree
|
||||
from parso import python_bytes_to_unicode
|
||||
|
||||
from jedi._compatibility import use_metaclass
|
||||
from jedi import debug
|
||||
from jedi.evaluate.cache import evaluator_method_cache, CachedMetaClass
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import recursion
|
||||
from jedi.evaluate import iterable
|
||||
from jedi.evaluate import docstrings
|
||||
from jedi.evaluate import pep0484
|
||||
from jedi.evaluate import param
|
||||
from jedi.evaluate import flow_analysis
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
|
||||
GlobalNameFilter, DictFilter, ContextName, AbstractNameDefinition, \
|
||||
ParamName, AnonymousInstanceParamName, TreeNameDefinition, \
|
||||
ContextNameMixin
|
||||
from jedi.evaluate import context
|
||||
from jedi.evaluate.context import ContextualizedNode
|
||||
from jedi import parser_utils
|
||||
from jedi.evaluate.parser_cache import get_yield_exprs
|
||||
|
||||
|
||||
def apply_py__get__(context, base_context):
|
||||
try:
|
||||
method = context.py__get__
|
||||
except AttributeError:
|
||||
yield context
|
||||
else:
|
||||
for descriptor_context in method(base_context):
|
||||
yield descriptor_context
|
||||
|
||||
|
||||
class ClassName(TreeNameDefinition):
|
||||
def __init__(self, parent_context, tree_name, name_context):
|
||||
super(ClassName, self).__init__(parent_context, tree_name)
|
||||
self._name_context = name_context
|
||||
|
||||
def infer(self):
|
||||
# TODO this _name_to_types might get refactored and be a part of the
|
||||
# parent class. Once it is, we can probably just overwrite method to
|
||||
# achieve this.
|
||||
from jedi.evaluate.finder import _name_to_types
|
||||
inferred = _name_to_types(
|
||||
self.parent_context.evaluator, self._name_context, self.tree_name)
|
||||
|
||||
for result_context in inferred:
|
||||
for c in apply_py__get__(result_context, self.parent_context):
|
||||
yield c
|
||||
|
||||
|
||||
class ClassFilter(ParserTreeFilter):
|
||||
name_class = ClassName
|
||||
|
||||
def _convert_names(self, names):
|
||||
return [self.name_class(self.context, name, self._node_context)
|
||||
for name in names]
|
||||
|
||||
|
||||
class ClassContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
"""
|
||||
This class is not only important to extend `tree.Class`, it is also a
|
||||
important for descriptors (if the descriptor methods are evaluated or not).
|
||||
"""
|
||||
api_type = 'class'
|
||||
|
||||
def __init__(self, evaluator, classdef, parent_context):
|
||||
super(ClassContext, self).__init__(evaluator, parent_context=parent_context)
|
||||
self.tree_node = classdef
|
||||
|
||||
@evaluator_method_cache(default=())
|
||||
def py__mro__(self):
|
||||
def add(cls):
|
||||
if cls not in mro:
|
||||
mro.append(cls)
|
||||
|
||||
mro = [self]
|
||||
# TODO Do a proper mro resolution. Currently we are just listing
|
||||
# classes. However, it's a complicated algorithm.
|
||||
for lazy_cls in self.py__bases__():
|
||||
# TODO there's multiple different mro paths possible if this yields
|
||||
# multiple possibilities. Could be changed to be more correct.
|
||||
for cls in lazy_cls.infer():
|
||||
# TODO detect for TypeError: duplicate base class str,
|
||||
# e.g. `class X(str, str): pass`
|
||||
try:
|
||||
mro_method = cls.py__mro__
|
||||
except AttributeError:
|
||||
# TODO add a TypeError like:
|
||||
"""
|
||||
>>> class Y(lambda: test): pass
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: function() argument 1 must be code, not str
|
||||
>>> class Y(1): pass
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: int() takes at most 2 arguments (3 given)
|
||||
"""
|
||||
pass
|
||||
else:
|
||||
add(cls)
|
||||
for cls_new in mro_method():
|
||||
add(cls_new)
|
||||
return tuple(mro)
|
||||
|
||||
@evaluator_method_cache(default=())
|
||||
def py__bases__(self):
|
||||
arglist = self.tree_node.get_super_arglist()
|
||||
if arglist:
|
||||
args = param.TreeArguments(self.evaluator, self, arglist)
|
||||
return [value for key, value in args.unpack() if key is None]
|
||||
else:
|
||||
return [context.LazyKnownContext(compiled.create(self.evaluator, object))]
|
||||
|
||||
def py__call__(self, params):
|
||||
from jedi.evaluate.instance import TreeInstance
|
||||
return set([TreeInstance(self.evaluator, self.parent_context, self, params)])
|
||||
|
||||
def py__class__(self):
|
||||
return compiled.create(self.evaluator, type)
|
||||
|
||||
def get_params(self):
|
||||
from jedi.evaluate.instance import AnonymousInstance
|
||||
anon = AnonymousInstance(self.evaluator, self.parent_context, self)
|
||||
return [AnonymousInstanceParamName(anon, param.name) for param in self.funcdef.get_params()]
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None, is_instance=False):
|
||||
if search_global:
|
||||
yield ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
else:
|
||||
for cls in self.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
for filter in cls.get_filters(is_instance=is_instance):
|
||||
yield filter
|
||||
else:
|
||||
yield ClassFilter(
|
||||
self.evaluator, self, node_context=cls,
|
||||
origin_scope=origin_scope)
|
||||
|
||||
def is_class(self):
|
||||
return True
|
||||
|
||||
def get_function_slot_names(self, name):
|
||||
for filter in self.get_filters(search_global=False):
|
||||
names = filter.get(name)
|
||||
if names:
|
||||
return names
|
||||
return []
|
||||
|
||||
def get_param_names(self):
|
||||
for name in self.get_function_slot_names('__init__'):
|
||||
for context_ in name.infer():
|
||||
try:
|
||||
method = context_.get_param_names
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
return list(method())[1:]
|
||||
return []
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return ContextName(self, self.tree_node.name)
|
||||
|
||||
|
||||
class LambdaName(AbstractNameDefinition):
|
||||
string_name = '<lambda>'
|
||||
|
||||
def __init__(self, lambda_context):
|
||||
self._lambda_context = lambda_context
|
||||
self.parent_context = lambda_context.parent_context
|
||||
|
||||
def start_pos(self):
|
||||
return self._lambda_context.tree_node.start_pos
|
||||
|
||||
def infer(self):
|
||||
return set([self._lambda_context])
|
||||
|
||||
|
||||
class FunctionContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
"""
|
||||
Needed because of decorators. Decorators are evaluated here.
|
||||
"""
|
||||
api_type = 'function'
|
||||
|
||||
def __init__(self, evaluator, parent_context, funcdef):
|
||||
""" This should not be called directly """
|
||||
super(FunctionContext, self).__init__(evaluator, parent_context)
|
||||
self.tree_node = funcdef
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
if search_global:
|
||||
yield ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
else:
|
||||
scope = self.py__class__()
|
||||
for filter in scope.get_filters(search_global=False, origin_scope=origin_scope):
|
||||
yield filter
|
||||
|
||||
def infer_function_execution(self, function_execution):
|
||||
"""
|
||||
Created to be used by inheritance.
|
||||
"""
|
||||
yield_exprs = get_yield_exprs(self.evaluator, self.tree_node)
|
||||
if yield_exprs:
|
||||
return set([iterable.Generator(self.evaluator, function_execution)])
|
||||
else:
|
||||
return function_execution.get_return_values()
|
||||
|
||||
def get_function_execution(self, arguments=None):
|
||||
if arguments is None:
|
||||
arguments = param.AnonymousArguments()
|
||||
|
||||
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
|
||||
|
||||
def py__call__(self, arguments):
|
||||
function_execution = self.get_function_execution(arguments)
|
||||
return self.infer_function_execution(function_execution)
|
||||
|
||||
def py__class__(self):
|
||||
# This differentiation is only necessary for Python2. Python3 does not
|
||||
# use a different method class.
|
||||
if isinstance(parser_utils.get_parent_scope(self.tree_node), tree.Class):
|
||||
name = 'METHOD_CLASS'
|
||||
else:
|
||||
name = 'FUNCTION_CLASS'
|
||||
return compiled.get_special_object(self.evaluator, name)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self.tree_node.type == 'lambdef':
|
||||
return LambdaName(self)
|
||||
return ContextName(self, self.tree_node.name)
|
||||
|
||||
def get_param_names(self):
|
||||
function_execution = self.get_function_execution()
|
||||
return [ParamName(function_execution, param.name)
|
||||
for param in self.tree_node.get_params()]
|
||||
|
||||
|
||||
class FunctionExecutionContext(context.TreeContext):
|
||||
"""
|
||||
This class is used to evaluate functions and their returns.
|
||||
|
||||
This is the most complicated class, because it contains the logic to
|
||||
transfer parameters. It is even more complicated, because there may be
|
||||
multiple calls to functions and recursion has to be avoided. But this is
|
||||
responsibility of the decorators.
|
||||
"""
|
||||
function_execution_filter = FunctionExecutionFilter
|
||||
|
||||
def __init__(self, evaluator, parent_context, function_context, var_args):
|
||||
super(FunctionExecutionContext, self).__init__(evaluator, parent_context)
|
||||
self.function_context = function_context
|
||||
self.tree_node = function_context.tree_node
|
||||
self.var_args = var_args
|
||||
|
||||
@evaluator_method_cache(default=set())
|
||||
@recursion.execution_recursion_decorator()
|
||||
def get_return_values(self, check_yields=False):
|
||||
funcdef = self.tree_node
|
||||
if funcdef.type == 'lambdef':
|
||||
return self.evaluator.eval_element(self, funcdef.children[-1])
|
||||
|
||||
if check_yields:
|
||||
types = set()
|
||||
returns = get_yield_exprs(self.evaluator, funcdef)
|
||||
else:
|
||||
returns = funcdef.iter_return_stmts()
|
||||
types = set(docstrings.infer_return_types(self.function_context))
|
||||
types |= set(pep0484.infer_return_types(self.function_context))
|
||||
|
||||
for r in returns:
|
||||
check = flow_analysis.reachability_check(self, funcdef, r)
|
||||
if check is flow_analysis.UNREACHABLE:
|
||||
debug.dbg('Return unreachable: %s', r)
|
||||
else:
|
||||
if check_yields:
|
||||
types |= set(self._eval_yield(r))
|
||||
else:
|
||||
try:
|
||||
children = r.children
|
||||
except AttributeError:
|
||||
types.add(compiled.create(self.evaluator, None))
|
||||
else:
|
||||
types |= self.eval_node(children[1])
|
||||
if check is flow_analysis.REACHABLE:
|
||||
debug.dbg('Return reachable: %s', r)
|
||||
break
|
||||
return types
|
||||
|
||||
def _eval_yield(self, yield_expr):
|
||||
if yield_expr.type == 'keyword':
|
||||
# `yield` just yields None.
|
||||
yield context.LazyKnownContext(compiled.create(self.evaluator, None))
|
||||
return
|
||||
|
||||
node = yield_expr.children[1]
|
||||
if node.type == 'yield_arg': # It must be a yield from.
|
||||
cn = ContextualizedNode(self, node.children[1])
|
||||
for lazy_context in iterable.py__iter__(self.evaluator, cn.infer(), cn):
|
||||
yield lazy_context
|
||||
else:
|
||||
yield context.LazyTreeContext(self, node)
|
||||
|
||||
@recursion.execution_recursion_decorator(default=iter([]))
|
||||
def get_yield_values(self):
|
||||
for_parents = [(y, tree.search_ancestor(y, 'for_stmt', 'funcdef',
|
||||
'while_stmt', 'if_stmt'))
|
||||
for y in get_yield_exprs(self.evaluator, self.tree_node)]
|
||||
|
||||
# Calculate if the yields are placed within the same for loop.
|
||||
yields_order = []
|
||||
last_for_stmt = None
|
||||
for yield_, for_stmt in for_parents:
|
||||
# For really simple for loops we can predict the order. Otherwise
|
||||
# we just ignore it.
|
||||
parent = for_stmt.parent
|
||||
if parent.type == 'suite':
|
||||
parent = parent.parent
|
||||
if for_stmt.type == 'for_stmt' and parent == self.tree_node \
|
||||
and parser_utils.for_stmt_defines_one_name(for_stmt): # Simplicity for now.
|
||||
if for_stmt == last_for_stmt:
|
||||
yields_order[-1][1].append(yield_)
|
||||
else:
|
||||
yields_order.append((for_stmt, [yield_]))
|
||||
elif for_stmt == self.tree_node:
|
||||
yields_order.append((None, [yield_]))
|
||||
else:
|
||||
types = self.get_return_values(check_yields=True)
|
||||
if types:
|
||||
yield context.get_merged_lazy_context(list(types))
|
||||
return
|
||||
last_for_stmt = for_stmt
|
||||
|
||||
evaluator = self.evaluator
|
||||
for for_stmt, yields in yields_order:
|
||||
if for_stmt is None:
|
||||
# No for_stmt, just normal yields.
|
||||
for yield_ in yields:
|
||||
for result in self._eval_yield(yield_):
|
||||
yield result
|
||||
else:
|
||||
input_node = for_stmt.get_testlist()
|
||||
cn = ContextualizedNode(self, input_node)
|
||||
ordered = iterable.py__iter__(evaluator, cn.infer(), cn)
|
||||
ordered = list(ordered)
|
||||
for lazy_context in ordered:
|
||||
dct = {str(for_stmt.children[1].value): lazy_context.infer()}
|
||||
with helpers.predefine_names(self, for_stmt, dct):
|
||||
for yield_in_same_for_stmt in yields:
|
||||
for result in self._eval_yield(yield_in_same_for_stmt):
|
||||
yield result
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield self.function_execution_filter(self.evaluator, self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope)
|
||||
|
||||
@evaluator_method_cache()
|
||||
def get_params(self):
|
||||
return self.var_args.get_params(self)
|
||||
|
||||
|
||||
class ModuleAttributeName(AbstractNameDefinition):
|
||||
"""
|
||||
For module attributes like __file__, __str__ and so on.
|
||||
"""
|
||||
api_type = 'instance'
|
||||
|
||||
def __init__(self, parent_module, string_name):
|
||||
self.parent_context = parent_module
|
||||
self.string_name = string_name
|
||||
|
||||
def infer(self):
|
||||
return compiled.create(self.parent_context.evaluator, str).execute(
|
||||
param.ValuesArguments([])
|
||||
)
|
||||
|
||||
|
||||
class ModuleName(ContextNameMixin, AbstractNameDefinition):
|
||||
start_pos = 1, 0
|
||||
|
||||
def __init__(self, context, name):
|
||||
self._context = context
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
return self._name
|
||||
|
||||
|
||||
class ModuleContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
api_type = 'module'
|
||||
parent_context = None
|
||||
|
||||
def __init__(self, evaluator, module_node, path):
|
||||
super(ModuleContext, self).__init__(evaluator, parent_context=None)
|
||||
self.tree_node = module_node
|
||||
self._path = path
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
yield GlobalNameFilter(self, self.tree_node)
|
||||
yield DictFilter(self._sub_modules_dict())
|
||||
yield DictFilter(self._module_attributes_dict())
|
||||
for star_module in self.star_imports():
|
||||
yield next(star_module.get_filters(search_global))
|
||||
|
||||
# I'm not sure if the star import cache is really that effective anymore
|
||||
# with all the other really fast import caches. Recheck. Also we would need
|
||||
# to push the star imports into Evaluator.modules, if we reenable this.
|
||||
@evaluator_method_cache([])
|
||||
def star_imports(self):
|
||||
modules = []
|
||||
for i in self.tree_node.iter_imports():
|
||||
if i.is_star_import():
|
||||
name = i.get_paths()[-1][-1]
|
||||
new = imports.infer_import(self, name)
|
||||
for module in new:
|
||||
if isinstance(module, ModuleContext):
|
||||
modules += module.star_imports()
|
||||
modules += new
|
||||
return modules
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _module_attributes_dict(self):
|
||||
names = ['__file__', '__package__', '__doc__', '__name__']
|
||||
# All the additional module attributes are strings.
|
||||
return dict((n, ModuleAttributeName(self, n)) for n in names)
|
||||
|
||||
@property
|
||||
def _string_name(self):
|
||||
""" This is used for the goto functions. """
|
||||
if self._path is None:
|
||||
return '' # no path -> empty name
|
||||
else:
|
||||
sep = (re.escape(os.path.sep),) * 2
|
||||
r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self._path)
|
||||
# Remove PEP 3149 names
|
||||
return re.sub('\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
|
||||
|
||||
@property
|
||||
@evaluator_method_cache()
|
||||
def name(self):
|
||||
return ModuleName(self, self._string_name)
|
||||
|
||||
def _get_init_directory(self):
|
||||
"""
|
||||
:return: The path to the directory of a package. None in case it's not
|
||||
a package.
|
||||
"""
|
||||
for suffix, _, _ in imp.get_suffixes():
|
||||
ending = '__init__' + suffix
|
||||
py__file__ = self.py__file__()
|
||||
if py__file__ is not None and py__file__.endswith(ending):
|
||||
# Remove the ending, including the separator.
|
||||
return self.py__file__()[:-len(ending) - 1]
|
||||
return None
|
||||
|
||||
def py__name__(self):
|
||||
for name, module in self.evaluator.modules.items():
|
||||
if module == self and name != '':
|
||||
return name
|
||||
|
||||
return '__main__'
|
||||
|
||||
def py__file__(self):
|
||||
"""
|
||||
In contrast to Python's __file__ can be None.
|
||||
"""
|
||||
if self._path is None:
|
||||
return None
|
||||
|
||||
return os.path.abspath(self._path)
|
||||
|
||||
def py__package__(self):
|
||||
if self._get_init_directory() is None:
|
||||
return re.sub(r'\.?[^\.]+$', '', self.py__name__())
|
||||
else:
|
||||
return self.py__name__()
|
||||
|
||||
def _py__path__(self):
|
||||
search_path = self.evaluator.sys_path
|
||||
init_path = self.py__file__()
|
||||
if os.path.basename(init_path) == '__init__.py':
|
||||
with open(init_path, 'rb') as f:
|
||||
content = python_bytes_to_unicode(f.read(), errors='replace')
|
||||
# these are strings that need to be used for namespace packages,
|
||||
# the first one is ``pkgutil``, the second ``pkg_resources``.
|
||||
options = ('declare_namespace(__name__)', 'extend_path(__path__')
|
||||
if options[0] in content or options[1] in content:
|
||||
# It is a namespace, now try to find the rest of the
|
||||
# modules on sys_path or whatever the search_path is.
|
||||
paths = set()
|
||||
for s in search_path:
|
||||
other = os.path.join(s, self.name.string_name)
|
||||
if os.path.isdir(other):
|
||||
paths.add(other)
|
||||
if paths:
|
||||
return list(paths)
|
||||
# TODO I'm not sure if this is how nested namespace
|
||||
# packages work. The tests are not really good enough to
|
||||
# show that.
|
||||
# Default to this.
|
||||
return [self._get_init_directory()]
|
||||
|
||||
@property
|
||||
def py__path__(self):
|
||||
"""
|
||||
Not seen here, since it's a property. The callback actually uses a
|
||||
variable, so use it like::
|
||||
|
||||
foo.py__path__(sys_path)
|
||||
|
||||
In case of a package, this returns Python's __path__ attribute, which
|
||||
is a list of paths (strings).
|
||||
Raises an AttributeError if the module is not a package.
|
||||
"""
|
||||
path = self._get_init_directory()
|
||||
|
||||
if path is None:
|
||||
raise AttributeError('Only packages have __path__ attributes.')
|
||||
else:
|
||||
return self._py__path__
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _sub_modules_dict(self):
|
||||
"""
|
||||
Lists modules in the directory of this module (if this module is a
|
||||
package).
|
||||
"""
|
||||
path = self._path
|
||||
names = {}
|
||||
if path is not None and path.endswith(os.path.sep + '__init__.py'):
|
||||
mods = pkgutil.iter_modules([os.path.dirname(path)])
|
||||
for module_loader, name, is_pkg in mods:
|
||||
# It's obviously a relative import to the current module.
|
||||
names[name] = imports.SubModuleName(self, name)
|
||||
|
||||
# TODO add something like this in the future, its cleaner than the
|
||||
# import hacks.
|
||||
# ``os.path`` is a hardcoded exception, because it's a
|
||||
# ``sys.modules`` modification.
|
||||
# if str(self.name) == 'os':
|
||||
# names.append(Name('path', parent_context=self))
|
||||
|
||||
return names
|
||||
|
||||
def py__class__(self):
|
||||
return compiled.get_special_object(self.evaluator, 'MODULE_CLASS')
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s@%s-%s>" % (
|
||||
self.__class__.__name__, self._string_name,
|
||||
self.tree_node.start_pos[0], self.tree_node.end_pos[0])
|
||||
|
||||
|
||||
class ImplicitNSName(AbstractNameDefinition):
|
||||
"""
|
||||
Accessing names for implicit namespace packages should infer to nothing.
|
||||
This object will prevent Jedi from raising exceptions
|
||||
"""
|
||||
def __init__(self, implicit_ns_context, string_name):
|
||||
self.implicit_ns_context = implicit_ns_context
|
||||
self.string_name = string_name
|
||||
|
||||
def infer(self):
|
||||
return []
|
||||
|
||||
def get_root_context(self):
|
||||
return self.implicit_ns_context
|
||||
|
||||
|
||||
class ImplicitNamespaceContext(use_metaclass(CachedMetaClass, context.TreeContext)):
|
||||
"""
|
||||
Provides support for implicit namespace packages
|
||||
"""
|
||||
api_type = 'module'
|
||||
parent_context = None
|
||||
|
||||
def __init__(self, evaluator, fullname):
|
||||
super(ImplicitNamespaceContext, self).__init__(evaluator, parent_context=None)
|
||||
self.evaluator = evaluator
|
||||
self.fullname = fullname
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield DictFilter(self._sub_modules_dict())
|
||||
|
||||
@property
|
||||
@evaluator_method_cache()
|
||||
def name(self):
|
||||
string_name = self.py__package__().rpartition('.')[-1]
|
||||
return ImplicitNSName(self, string_name)
|
||||
|
||||
def py__file__(self):
|
||||
return None
|
||||
|
||||
def py__package__(self):
|
||||
"""Return the fullname
|
||||
"""
|
||||
return self.fullname
|
||||
|
||||
@property
|
||||
def py__path__(self):
|
||||
return lambda: [self.paths]
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _sub_modules_dict(self):
|
||||
names = {}
|
||||
|
||||
paths = self.paths
|
||||
file_names = chain.from_iterable(os.listdir(path) for path in paths)
|
||||
mods = [
|
||||
file_name.rpartition('.')[0] if '.' in file_name else file_name
|
||||
for file_name in file_names
|
||||
if file_name != '__pycache__'
|
||||
]
|
||||
|
||||
for name in mods:
|
||||
names[name] = imports.SubModuleName(self, name)
|
||||
return names
|
@ -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
|
@ -0,0 +1,323 @@
|
||||
"""
|
||||
Implementations of standard library functions, because it's not possible to
|
||||
understand them with Jedi.
|
||||
|
||||
To add a new implementation, create a function and add it to the
|
||||
``_implemented`` dict at the bottom of this module.
|
||||
|
||||
Note that this module exists only to implement very specific functionality in
|
||||
the standard library. The usual way to understand the standard library is the
|
||||
compiled module that returns the types for C-builtins.
|
||||
"""
|
||||
import collections
|
||||
import re
|
||||
|
||||
from jedi.common import unite
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import representation as er
|
||||
from jedi.evaluate.instance import InstanceFunctionExecution, \
|
||||
AbstractInstanceContext, CompiledInstance, BoundMethod, \
|
||||
AnonymousInstanceFunctionExecution
|
||||
from jedi.evaluate import iterable
|
||||
from jedi import debug
|
||||
from jedi.evaluate import precedence
|
||||
from jedi.evaluate import param
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate.context import LazyTreeContext, ContextualizedNode
|
||||
|
||||
# Now this is all part of fake tuples in Jedi. However super doesn't work on
|
||||
# __init__ and __new__ doesn't work at all. So adding this to nametuples is
|
||||
# just the easiest way.
|
||||
_NAMEDTUPLE_INIT = """
|
||||
def __init__(_cls, {arg_list}):
|
||||
'A helper function for namedtuple.'
|
||||
self.__iterable = ({arg_list})
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def __getitem__(self, y):
|
||||
return self.__iterable[y]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class NotInStdLib(LookupError):
|
||||
pass
|
||||
|
||||
|
||||
def execute(evaluator, obj, arguments):
|
||||
if isinstance(obj, BoundMethod):
|
||||
raise NotInStdLib()
|
||||
|
||||
try:
|
||||
obj_name = obj.name.string_name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if obj.parent_context == evaluator.BUILTINS:
|
||||
module_name = 'builtins'
|
||||
elif isinstance(obj.parent_context, er.ModuleContext):
|
||||
module_name = obj.parent_context.name.string_name
|
||||
else:
|
||||
module_name = ''
|
||||
|
||||
# for now we just support builtin functions.
|
||||
try:
|
||||
func = _implemented[module_name][obj_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return func(evaluator, obj, arguments)
|
||||
raise NotInStdLib()
|
||||
|
||||
|
||||
def _follow_param(evaluator, arguments, index):
|
||||
try:
|
||||
key, lazy_context = list(arguments.unpack())[index]
|
||||
except IndexError:
|
||||
return set()
|
||||
else:
|
||||
return lazy_context.infer()
|
||||
|
||||
|
||||
def argument_clinic(string, want_obj=False, want_context=False, want_arguments=False):
|
||||
"""
|
||||
Works like Argument Clinic (PEP 436), to validate function params.
|
||||
"""
|
||||
clinic_args = []
|
||||
allow_kwargs = False
|
||||
optional = False
|
||||
while string:
|
||||
# Optional arguments have to begin with a bracket. And should always be
|
||||
# at the end of the arguments. This is therefore not a proper argument
|
||||
# clinic implementation. `range()` for exmple allows an optional start
|
||||
# value at the beginning.
|
||||
match = re.match('(?:(?:(\[),? ?|, ?|)(\w+)|, ?/)\]*', string)
|
||||
string = string[len(match.group(0)):]
|
||||
if not match.group(2): # A slash -> allow named arguments
|
||||
allow_kwargs = True
|
||||
continue
|
||||
optional = optional or bool(match.group(1))
|
||||
word = match.group(2)
|
||||
clinic_args.append((word, optional, allow_kwargs))
|
||||
|
||||
def f(func):
|
||||
def wrapper(evaluator, obj, arguments):
|
||||
debug.dbg('builtin start %s' % obj, color='MAGENTA')
|
||||
try:
|
||||
lst = list(arguments.eval_argument_clinic(clinic_args))
|
||||
except ValueError:
|
||||
return set()
|
||||
else:
|
||||
kwargs = {}
|
||||
if want_context:
|
||||
kwargs['context'] = arguments.context
|
||||
if want_obj:
|
||||
kwargs['obj'] = obj
|
||||
if want_arguments:
|
||||
kwargs['arguments'] = arguments
|
||||
return func(evaluator, *lst, **kwargs)
|
||||
finally:
|
||||
debug.dbg('builtin end', color='MAGENTA')
|
||||
|
||||
return wrapper
|
||||
return f
|
||||
|
||||
|
||||
@argument_clinic('iterator[, default], /')
|
||||
def builtins_next(evaluator, iterators, defaults):
|
||||
"""
|
||||
TODO this function is currently not used. It's a stab at implementing next
|
||||
in a different way than fake objects. This would be a bit more flexible.
|
||||
"""
|
||||
if evaluator.python_version[0] == 2:
|
||||
name = 'next'
|
||||
else:
|
||||
name = '__next__'
|
||||
|
||||
types = set()
|
||||
for iterator in iterators:
|
||||
if isinstance(iterator, AbstractInstanceContext):
|
||||
for filter in iterator.get_filters(include_self_names=True):
|
||||
for n in filter.get(name):
|
||||
for context in n.infer():
|
||||
types |= context.execute_evaluated()
|
||||
if types:
|
||||
return types
|
||||
return defaults
|
||||
|
||||
|
||||
@argument_clinic('object, name[, default], /')
|
||||
def builtins_getattr(evaluator, objects, names, defaults=None):
|
||||
# follow the first param
|
||||
for obj in objects:
|
||||
for name in names:
|
||||
if precedence.is_string(name):
|
||||
return obj.py__getattribute__(name.obj)
|
||||
else:
|
||||
debug.warning('getattr called without str')
|
||||
continue
|
||||
return set()
|
||||
|
||||
|
||||
@argument_clinic('object[, bases, dict], /')
|
||||
def builtins_type(evaluator, objects, bases, dicts):
|
||||
if bases or dicts:
|
||||
# It's a type creation... maybe someday...
|
||||
return set()
|
||||
else:
|
||||
return set([o.py__class__() for o in objects])
|
||||
|
||||
|
||||
class SuperInstance(AbstractInstanceContext):
|
||||
"""To be used like the object ``super`` returns."""
|
||||
def __init__(self, evaluator, cls):
|
||||
su = cls.py_mro()[1]
|
||||
super().__init__(evaluator, su and su[0] or self)
|
||||
|
||||
|
||||
@argument_clinic('[type[, obj]], /', want_context=True)
|
||||
def builtins_super(evaluator, types, objects, context):
|
||||
# TODO make this able to detect multiple inheritance super
|
||||
if isinstance(context, (InstanceFunctionExecution,
|
||||
AnonymousInstanceFunctionExecution)):
|
||||
su = context.instance.py__class__().py__bases__()
|
||||
return unite(context.execute_evaluated() for context in su[0].infer())
|
||||
return set()
|
||||
|
||||
|
||||
@argument_clinic('sequence, /', want_obj=True, want_arguments=True)
|
||||
def builtins_reversed(evaluator, sequences, obj, arguments):
|
||||
# While we could do without this variable (just by using sequences), we
|
||||
# want static analysis to work well. Therefore we need to generated the
|
||||
# values again.
|
||||
key, lazy_context = next(arguments.unpack())
|
||||
cn = None
|
||||
if isinstance(lazy_context, LazyTreeContext):
|
||||
# TODO access private
|
||||
cn = ContextualizedNode(lazy_context._context, lazy_context.data)
|
||||
ordered = list(iterable.py__iter__(evaluator, sequences, cn))
|
||||
|
||||
rev = list(reversed(ordered))
|
||||
# Repack iterator values and then run it the normal way. This is
|
||||
# necessary, because `reversed` is a function and autocompletion
|
||||
# would fail in certain cases like `reversed(x).__iter__` if we
|
||||
# just returned the result directly.
|
||||
seq = iterable.FakeSequence(evaluator, 'list', rev)
|
||||
arguments = param.ValuesArguments([[seq]])
|
||||
return set([CompiledInstance(evaluator, evaluator.BUILTINS, obj, arguments)])
|
||||
|
||||
|
||||
@argument_clinic('obj, type, /', want_arguments=True)
|
||||
def builtins_isinstance(evaluator, objects, types, arguments):
|
||||
bool_results = set([])
|
||||
for o in objects:
|
||||
try:
|
||||
mro_func = o.py__class__().py__mro__
|
||||
except AttributeError:
|
||||
# This is temporary. Everything should have a class attribute in
|
||||
# Python?! Maybe we'll leave it here, because some numpy objects or
|
||||
# whatever might not.
|
||||
return set([compiled.create(True), compiled.create(False)])
|
||||
|
||||
mro = mro_func()
|
||||
|
||||
for cls_or_tup in types:
|
||||
if cls_or_tup.is_class():
|
||||
bool_results.add(cls_or_tup in mro)
|
||||
elif cls_or_tup.name.string_name == 'tuple' \
|
||||
and cls_or_tup.get_root_context() == evaluator.BUILTINS:
|
||||
# Check for tuples.
|
||||
classes = unite(
|
||||
lazy_context.infer()
|
||||
for lazy_context in cls_or_tup.py__iter__()
|
||||
)
|
||||
bool_results.add(any(cls in mro for cls in classes))
|
||||
else:
|
||||
_, lazy_context = list(arguments.unpack())[1]
|
||||
if isinstance(lazy_context, LazyTreeContext):
|
||||
node = lazy_context.data
|
||||
message = 'TypeError: isinstance() arg 2 must be a ' \
|
||||
'class, type, or tuple of classes and types, ' \
|
||||
'not %s.' % cls_or_tup
|
||||
analysis.add(lazy_context._context, 'type-error-isinstance', node, message)
|
||||
|
||||
return set(compiled.create(evaluator, x) for x in bool_results)
|
||||
|
||||
|
||||
def collections_namedtuple(evaluator, obj, arguments):
|
||||
"""
|
||||
Implementation of the namedtuple function.
|
||||
|
||||
This has to be done by processing the namedtuple class template and
|
||||
evaluating the result.
|
||||
|
||||
.. note:: |jedi| only supports namedtuples on Python >2.6.
|
||||
|
||||
"""
|
||||
# Namedtuples are not supported on Python 2.6
|
||||
if not hasattr(collections, '_class_template'):
|
||||
return set()
|
||||
|
||||
# Process arguments
|
||||
# TODO here we only use one of the types, we should use all.
|
||||
name = list(_follow_param(evaluator, arguments, 0))[0].obj
|
||||
_fields = list(_follow_param(evaluator, arguments, 1))[0]
|
||||
if isinstance(_fields, compiled.CompiledObject):
|
||||
fields = _fields.obj.replace(',', ' ').split()
|
||||
elif isinstance(_fields, iterable.AbstractSequence):
|
||||
fields = [
|
||||
v.obj
|
||||
for lazy_context in _fields.py__iter__()
|
||||
for v in lazy_context.infer() if hasattr(v, 'obj')
|
||||
]
|
||||
else:
|
||||
return set()
|
||||
|
||||
base = collections._class_template
|
||||
base += _NAMEDTUPLE_INIT
|
||||
# Build source
|
||||
source = base.format(
|
||||
typename=name,
|
||||
field_names=tuple(fields),
|
||||
num_fields=len(fields),
|
||||
arg_list = repr(tuple(fields)).replace("'", "")[1:-1],
|
||||
repr_fmt=', '.join(collections._repr_template.format(name=name) for name in fields),
|
||||
field_defs='\n'.join(collections._field_template.format(index=index, name=name)
|
||||
for index, name in enumerate(fields))
|
||||
)
|
||||
|
||||
# Parse source
|
||||
module = evaluator.grammar.parse(source)
|
||||
generated_class = next(module.iter_classdefs())
|
||||
parent_context = er.ModuleContext(evaluator, module, '')
|
||||
return set([er.ClassContext(evaluator, generated_class, parent_context)])
|
||||
|
||||
|
||||
@argument_clinic('first, /')
|
||||
def _return_first_param(evaluator, firsts):
|
||||
return firsts
|
||||
|
||||
|
||||
_implemented = {
|
||||
'builtins': {
|
||||
'getattr': builtins_getattr,
|
||||
'type': builtins_type,
|
||||
'super': builtins_super,
|
||||
'reversed': builtins_reversed,
|
||||
'isinstance': builtins_isinstance,
|
||||
},
|
||||
'copy': {
|
||||
'copy': _return_first_param,
|
||||
'deepcopy': _return_first_param,
|
||||
},
|
||||
'json': {
|
||||
'load': lambda *args: set(),
|
||||
'loads': lambda *args: set(),
|
||||
},
|
||||
'collections': {
|
||||
'namedtuple': collections_namedtuple,
|
||||
},
|
||||
}
|
@ -0,0 +1,314 @@
|
||||
import glob
|
||||
import os
|
||||
import sys
|
||||
import imp
|
||||
from jedi.evaluate.site import addsitedir
|
||||
|
||||
from jedi._compatibility import exec_function, unicode
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate.compiled import CompiledObject
|
||||
from jedi.evaluate.context import ContextualizedNode
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi import common
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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 before
|
||||
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:
|
||||
# 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:
|
||||
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
|
||||
result.append(p)
|
||||
return result
|
||||
|
||||
|
||||
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):
|
||||
if os.name == 'nt':
|
||||
p = os.path.join(venv, 'lib', 'site-packages')
|
||||
else:
|
||||
p = os.path.join(venv, 'lib', 'python%d.%d' % sys.version_info[:2],
|
||||
'site-packages')
|
||||
return p
|
||||
|
||||
|
||||
def _execute_code(module_path, code):
|
||||
c = "import os; from os.path import *; result=%s"
|
||||
variables = {'__file__': module_path}
|
||||
try:
|
||||
exec_function(c % code, variables)
|
||||
except Exception:
|
||||
debug.warning('sys.path manipulation detected, but failed to evaluate.')
|
||||
else:
|
||||
try:
|
||||
res = variables['result']
|
||||
if isinstance(res, str):
|
||||
return [os.path.abspath(res)]
|
||||
except KeyError:
|
||||
pass
|
||||
return []
|
||||
|
||||
|
||||
def _paths_from_assignment(module_context, expr_stmt):
|
||||
"""
|
||||
Extracts the assigned strings from an assignment that looks as follows::
|
||||
|
||||
>>> sys.path[0:0] = ['module/path', 'another/module/path']
|
||||
|
||||
This function is in general pretty tolerant (and therefore 'buggy').
|
||||
However, it's not a big issue usually to add more paths to Jedi's sys_path,
|
||||
because it will only affect Jedi in very random situations and by adding
|
||||
more paths than necessary, it usually benefits the general user.
|
||||
"""
|
||||
for assignee, operator in zip(expr_stmt.children[::2], expr_stmt.children[1::2]):
|
||||
try:
|
||||
assert operator in ['=', '+=']
|
||||
assert assignee.type in ('power', 'atom_expr') and \
|
||||
len(assignee.children) > 1
|
||||
c = assignee.children
|
||||
assert c[0].type == 'name' and c[0].value == 'sys'
|
||||
trailer = c[1]
|
||||
assert trailer.children[0] == '.' and trailer.children[1].value == 'path'
|
||||
# TODO Essentially we're not checking details on sys.path
|
||||
# manipulation. Both assigment of the sys.path and changing/adding
|
||||
# parts of the sys.path are the same: They get added to the current
|
||||
# sys.path.
|
||||
"""
|
||||
execution = c[2]
|
||||
assert execution.children[0] == '['
|
||||
subscript = execution.children[1]
|
||||
assert subscript.type == 'subscript'
|
||||
assert ':' in subscript.children
|
||||
"""
|
||||
except AssertionError:
|
||||
continue
|
||||
|
||||
from jedi.evaluate.iterable import py__iter__
|
||||
from jedi.evaluate.precedence import is_string
|
||||
cn = ContextualizedNode(module_context.create_context(expr_stmt), expr_stmt)
|
||||
for lazy_context in py__iter__(module_context.evaluator, cn.infer(), cn):
|
||||
for context in lazy_context.infer():
|
||||
if is_string(context):
|
||||
yield context.obj
|
||||
|
||||
|
||||
def _paths_from_list_modifications(module_path, trailer1, trailer2):
|
||||
""" extract the path from either "sys.path.append" or "sys.path.insert" """
|
||||
# Guarantee that both are trailers, the first one a name and the second one
|
||||
# a function execution with at least one param.
|
||||
if not (trailer1.type == 'trailer' and trailer1.children[0] == '.'
|
||||
and trailer2.type == 'trailer' and trailer2.children[0] == '('
|
||||
and len(trailer2.children) == 3):
|
||||
return []
|
||||
|
||||
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]
|
||||
return _execute_code(module_path, arg.get_code())
|
||||
|
||||
|
||||
def _check_module(module_context):
|
||||
"""
|
||||
Detect sys.path modifications within module.
|
||||
"""
|
||||
def get_sys_path_powers(names):
|
||||
for name in names:
|
||||
power = name.parent.parent
|
||||
if power.type in ('power', 'atom_expr'):
|
||||
c = power.children
|
||||
if c[0].type == 'name' and c[0].value == 'sys' \
|
||||
and c[1].type == 'trailer':
|
||||
n = c[1].children[1]
|
||||
if n.type == 'name' and n.value == 'path':
|
||||
yield name, power
|
||||
|
||||
sys_path = list(module_context.evaluator.sys_path) # copy
|
||||
if isinstance(module_context, CompiledObject):
|
||||
return sys_path
|
||||
|
||||
try:
|
||||
possible_names = module_context.tree_node.get_used_names()['path']
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
for name, power in get_sys_path_powers(possible_names):
|
||||
expr_stmt = power.parent
|
||||
if len(power.children) >= 4:
|
||||
sys_path.extend(
|
||||
_paths_from_list_modifications(
|
||||
module_context.py__file__(), *power.children[2:4]
|
||||
)
|
||||
)
|
||||
elif expr_stmt is not None and expr_stmt.type == 'expr_stmt':
|
||||
sys_path.extend(_paths_from_assignment(module_context, expr_stmt))
|
||||
return sys_path
|
||||
|
||||
|
||||
@evaluator_function_cache(default=[])
|
||||
def sys_path_with_modifications(evaluator, module_context):
|
||||
path = module_context.py__file__()
|
||||
if path is None:
|
||||
# Support for modules without a path is bad, therefore return the
|
||||
# normal path.
|
||||
return list(evaluator.sys_path)
|
||||
|
||||
curdir = os.path.abspath(os.curdir)
|
||||
#TODO why do we need a chdir?
|
||||
with common.ignored(OSError):
|
||||
os.chdir(os.path.dirname(path))
|
||||
|
||||
buildout_script_paths = set()
|
||||
|
||||
result = _check_module(module_context)
|
||||
result += _detect_django_path(path)
|
||||
for buildout_script_path in _get_buildout_script_paths(path):
|
||||
for path in _get_paths_from_buildout_script(evaluator, buildout_script_path):
|
||||
buildout_script_paths.add(path)
|
||||
# cleanup, back to old directory
|
||||
os.chdir(curdir)
|
||||
return list(result) + list(buildout_script_paths)
|
||||
|
||||
|
||||
def _get_paths_from_buildout_script(evaluator, buildout_script_path):
|
||||
try:
|
||||
module_node = evaluator.grammar.parse(
|
||||
path=buildout_script_path,
|
||||
cache=True,
|
||||
cache_path=settings.cache_directory
|
||||
)
|
||||
except IOError:
|
||||
debug.warning('Error trying to read buildout_script: %s', buildout_script_path)
|
||||
return
|
||||
|
||||
from jedi.evaluate.representation import ModuleContext
|
||||
for path in _check_module(ModuleContext(evaluator, module_node, buildout_script_path)):
|
||||
yield path
|
||||
|
||||
|
||||
def traverse_parents(path):
|
||||
while True:
|
||||
new = os.path.dirname(path)
|
||||
if new == path:
|
||||
return
|
||||
path = new
|
||||
yield path
|
||||
|
||||
|
||||
def _get_parent_dir_with_file(path, filename):
|
||||
for parent in traverse_parents(path):
|
||||
if os.path.isfile(os.path.join(parent, filename)):
|
||||
return parent
|
||||
return None
|
||||
|
||||
|
||||
def _detect_django_path(module_path):
|
||||
""" Detects the path of the very well known Django library (if used) """
|
||||
result = []
|
||||
|
||||
for parent in traverse_parents(module_path):
|
||||
with common.ignored(IOError):
|
||||
with open(parent + os.path.sep + 'manage.py'):
|
||||
debug.dbg('Found django path: %s', module_path)
|
||||
result.append(parent)
|
||||
return result
|
||||
|
||||
|
||||
def _get_buildout_script_paths(module_path):
|
||||
"""
|
||||
if there is a 'buildout.cfg' file in one of the parent directories of the
|
||||
given module it will return a list of all files in the buildout bin
|
||||
directory that look like python files.
|
||||
|
||||
:param module_path: absolute path to the module.
|
||||
:type module_path: str
|
||||
"""
|
||||
project_root = _get_parent_dir_with_file(module_path, 'buildout.cfg')
|
||||
if not project_root:
|
||||
return []
|
||||
bin_path = os.path.join(project_root, 'bin')
|
||||
if not os.path.exists(bin_path):
|
||||
return []
|
||||
extra_module_paths = []
|
||||
for filename in os.listdir(bin_path):
|
||||
try:
|
||||
filepath = os.path.join(bin_path, filename)
|
||||
with open(filepath, 'r') as f:
|
||||
firstline = f.readline()
|
||||
if firstline.startswith('#!') and 'python' in firstline:
|
||||
extra_module_paths.append(filepath)
|
||||
except (UnicodeDecodeError, IOError) as e:
|
||||
# Probably a binary file; permission error or race cond. because file got deleted
|
||||
# ignore
|
||||
debug.warning(unicode(e))
|
||||
continue
|
||||
return extra_module_paths
|
||||
|
||||
|
||||
def dotted_path_in_sys_path(sys_path, module_path):
|
||||
"""
|
||||
Returns the dotted path inside a sys.path.
|
||||
"""
|
||||
# First remove the suffix.
|
||||
for suffix, _, _ in imp.get_suffixes():
|
||||
if module_path.endswith(suffix):
|
||||
module_path = module_path[:-len(suffix)]
|
||||
break
|
||||
else:
|
||||
# There should always be a suffix in a valid Python file on the path.
|
||||
return None
|
||||
|
||||
if module_path.startswith(os.path.sep):
|
||||
# The paths in sys.path most of the times don't end with a slash.
|
||||
module_path = module_path[1:]
|
||||
|
||||
for p in sys_path:
|
||||
if module_path.startswith(p):
|
||||
rest = module_path[len(p):]
|
||||
if rest:
|
||||
split = rest.split(os.path.sep)
|
||||
for string in split:
|
||||
if not string or '.' in string:
|
||||
return None
|
||||
return '.'.join(split)
|
||||
|
||||
return None
|
@ -0,0 +1,241 @@
|
||||
import textwrap
|
||||
from inspect import cleandoc
|
||||
|
||||
from jedi._compatibility import literal_eval, is_py3
|
||||
from parso.python import tree
|
||||
|
||||
_EXECUTE_NODES = set([
|
||||
'funcdef', 'classdef', 'import_from', 'import_name', 'test', 'or_test',
|
||||
'and_test', 'not_test', 'comparison', 'expr', 'xor_expr', 'and_expr',
|
||||
'shift_expr', 'arith_expr', 'atom_expr', 'term', 'factor', 'power', 'atom'
|
||||
])
|
||||
|
||||
_FLOW_KEYWORDS = (
|
||||
'try', 'except', 'finally', 'else', 'if', 'elif', 'with', 'for', 'while'
|
||||
)
|
||||
|
||||
|
||||
def get_executable_nodes(node, last_added=False):
|
||||
"""
|
||||
For static analysis.
|
||||
"""
|
||||
result = []
|
||||
typ = node.type
|
||||
if typ == 'name':
|
||||
next_leaf = node.get_next_leaf()
|
||||
if last_added is False and node.parent.type != 'param' and next_leaf != '=':
|
||||
result.append(node)
|
||||
elif typ == 'expr_stmt':
|
||||
# I think evaluating the statement (and possibly returned arrays),
|
||||
# should be enough for static analysis.
|
||||
result.append(node)
|
||||
for child in node.children:
|
||||
result += get_executable_nodes(child, last_added=True)
|
||||
elif typ == 'decorator':
|
||||
# decorator
|
||||
if node.children[-2] == ')':
|
||||
node = node.children[-3]
|
||||
if node != '(':
|
||||
result += get_executable_nodes(node)
|
||||
else:
|
||||
try:
|
||||
children = node.children
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if node.type in _EXECUTE_NODES and not last_added:
|
||||
result.append(node)
|
||||
|
||||
for child in children:
|
||||
result += get_executable_nodes(child, last_added)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def get_comp_fors(comp_for):
|
||||
yield comp_for
|
||||
last = comp_for.children[-1]
|
||||
while True:
|
||||
if last.type == 'comp_for':
|
||||
yield last
|
||||
elif not last.type == 'comp_if':
|
||||
break
|
||||
last = last.children[-1]
|
||||
|
||||
|
||||
def for_stmt_defines_one_name(for_stmt):
|
||||
"""
|
||||
Returns True if only one name is returned: ``for x in y``.
|
||||
Returns False if the for loop is more complicated: ``for x, z in y``.
|
||||
|
||||
:returns: bool
|
||||
"""
|
||||
return for_stmt.children[1].type == 'name'
|
||||
|
||||
|
||||
def get_flow_branch_keyword(flow_node, node):
|
||||
start_pos = node.start_pos
|
||||
if not (flow_node.start_pos < start_pos <= flow_node.end_pos):
|
||||
raise ValueError('The node is not part of the flow.')
|
||||
|
||||
keyword = None
|
||||
for i, child in enumerate(flow_node.children):
|
||||
if start_pos < child.start_pos:
|
||||
return keyword
|
||||
first_leaf = child.get_first_leaf()
|
||||
if first_leaf in _FLOW_KEYWORDS:
|
||||
keyword = first_leaf
|
||||
return 0
|
||||
|
||||
def get_statement_of_position(node, pos):
|
||||
for c in node.children:
|
||||
if c.start_pos <= pos <= c.end_pos:
|
||||
if c.type not in ('decorated', 'simple_stmt', 'suite') \
|
||||
and not isinstance(c, (tree.Flow, tree.ClassOrFunc)):
|
||||
return c
|
||||
else:
|
||||
try:
|
||||
return get_statement_of_position(c, pos)
|
||||
except AttributeError:
|
||||
pass # Must be a non-scope
|
||||
return None
|
||||
|
||||
|
||||
def clean_scope_docstring(scope_node):
|
||||
""" Returns a cleaned version of the docstring token. """
|
||||
node = scope_node.get_doc_node()
|
||||
if node is not None:
|
||||
# TODO We have to check next leaves until there are no new
|
||||
# leaves anymore that might be part of the docstring. A
|
||||
# docstring can also look like this: ``'foo' 'bar'
|
||||
# Returns a literal cleaned version of the ``Token``.
|
||||
cleaned = cleandoc(safe_literal_eval(node.value))
|
||||
# Since we want the docstr output to be always unicode, just
|
||||
# force it.
|
||||
if is_py3 or isinstance(cleaned, unicode):
|
||||
return cleaned
|
||||
else:
|
||||
return unicode(cleaned, 'UTF-8', 'replace')
|
||||
return ''
|
||||
|
||||
|
||||
def safe_literal_eval(value):
|
||||
first_two = value[:2].lower()
|
||||
if first_two[0] == 'f' or first_two in ('fr', 'rf'):
|
||||
# literal_eval is not able to resovle f literals. We have to do that
|
||||
# manually, but that's right now not implemented.
|
||||
return ''
|
||||
|
||||
try:
|
||||
return literal_eval(value)
|
||||
except SyntaxError:
|
||||
# It's possible to create syntax errors with literals like rb'' in
|
||||
# Python 2. This should not be possible and in that case just return an
|
||||
# empty string.
|
||||
# Before Python 3.3 there was a more strict definition in which order
|
||||
# you could define literals.
|
||||
return ''
|
||||
|
||||
|
||||
def get_call_signature(funcdef, width=72, call_string=None):
|
||||
"""
|
||||
Generate call signature of this function.
|
||||
|
||||
:param width: Fold lines if a line is longer than this value.
|
||||
:type width: int
|
||||
:arg func_name: Override function name when given.
|
||||
:type func_name: str
|
||||
|
||||
:rtype: str
|
||||
"""
|
||||
# Lambdas have no name.
|
||||
if call_string is None:
|
||||
if funcdef.type == 'lambdef':
|
||||
call_string = '<lambda>'
|
||||
else:
|
||||
call_string = funcdef.name.value
|
||||
if funcdef.type == 'lambdef':
|
||||
p = '(' + ''.join(param.get_code() for param in funcdef.get_params()).strip() + ')'
|
||||
else:
|
||||
p = funcdef.children[2].get_code()
|
||||
code = call_string + p
|
||||
|
||||
return '\n'.join(textwrap.wrap(code, width))
|
||||
|
||||
|
||||
def get_doc_with_call_signature(scope_node):
|
||||
"""
|
||||
Return a document string including call signature.
|
||||
"""
|
||||
call_signature = None
|
||||
if scope_node.type == 'classdef':
|
||||
for funcdef in scope_node.iter_funcdefs():
|
||||
if funcdef.name.value == '__init__':
|
||||
call_signature = \
|
||||
get_call_signature(funcdef, call_string=scope_node.name.value)
|
||||
elif scope_node.type in ('funcdef', 'lambdef'):
|
||||
call_signature = get_call_signature(scope_node)
|
||||
|
||||
doc = clean_scope_docstring(scope_node)
|
||||
if call_signature is None:
|
||||
return doc
|
||||
return '%s\n\n%s' % (call_signature, doc)
|
||||
|
||||
|
||||
def move(node, line_offset):
|
||||
"""
|
||||
Move the `Node` start_pos.
|
||||
"""
|
||||
try:
|
||||
children = node.children
|
||||
except AttributeError:
|
||||
node.line += line_offset
|
||||
else:
|
||||
for c in children:
|
||||
move(c, line_offset)
|
||||
|
||||
|
||||
def get_following_comment_same_line(node):
|
||||
"""
|
||||
returns (as string) any comment that appears on the same line,
|
||||
after the node, including the #
|
||||
"""
|
||||
try:
|
||||
if node.type == 'for_stmt':
|
||||
whitespace = node.children[5].get_first_leaf().prefix
|
||||
elif node.type == 'with_stmt':
|
||||
whitespace = node.children[3].get_first_leaf().prefix
|
||||
else:
|
||||
whitespace = node.get_last_leaf().get_next_leaf().prefix
|
||||
except AttributeError:
|
||||
return None
|
||||
except ValueError:
|
||||
# TODO in some particular cases, the tree doesn't seem to be linked
|
||||
# correctly
|
||||
return None
|
||||
if "#" not in whitespace:
|
||||
return None
|
||||
comment = whitespace[whitespace.index("#"):]
|
||||
if "\r" in comment:
|
||||
comment = comment[:comment.index("\r")]
|
||||
if "\n" in comment:
|
||||
comment = comment[:comment.index("\n")]
|
||||
return comment
|
||||
|
||||
|
||||
def is_scope(node):
|
||||
return node.type in ('file_input', 'classdef', 'funcdef', 'lambdef', 'comp_for')
|
||||
|
||||
|
||||
def get_parent_scope(node, include_flows=False):
|
||||
"""
|
||||
Returns the underlying scope.
|
||||
"""
|
||||
scope = node.parent
|
||||
while scope is not None:
|
||||
if include_flows and isinstance(scope, tree.Flow):
|
||||
return scope
|
||||
if is_scope(scope):
|
||||
break
|
||||
scope = scope.parent
|
||||
return scope
|
@ -0,0 +1,202 @@
|
||||
"""
|
||||
Introduce some basic refactoring functions to |jedi|. This module is still in a
|
||||
very early development stage and needs much testing and improvement.
|
||||
|
||||
.. warning:: I won't do too much here, but if anyone wants to step in, please
|
||||
do. Refactoring is none of my priorities
|
||||
|
||||
It uses the |jedi| `API <plugin-api.html>`_ and supports currently the
|
||||
following functions (sometimes bug-prone):
|
||||
|
||||
- rename
|
||||
- extract variable
|
||||
- inline variable
|
||||
"""
|
||||
import difflib
|
||||
|
||||
from jedi import common
|
||||
from parso import python_bytes_to_unicode, split_lines
|
||||
from jedi.evaluate import helpers
|
||||
|
||||
|
||||
class Refactoring(object):
|
||||
def __init__(self, change_dct):
|
||||
"""
|
||||
:param change_dct: dict(old_path=(new_path, old_lines, new_lines))
|
||||
"""
|
||||
self.change_dct = change_dct
|
||||
|
||||
def old_files(self):
|
||||
dct = {}
|
||||
for old_path, (new_path, old_l, new_l) in self.change_dct.items():
|
||||
dct[old_path] = '\n'.join(old_l)
|
||||
return dct
|
||||
|
||||
def new_files(self):
|
||||
dct = {}
|
||||
for old_path, (new_path, old_l, new_l) in self.change_dct.items():
|
||||
dct[new_path] = '\n'.join(new_l)
|
||||
return dct
|
||||
|
||||
def diff(self):
|
||||
texts = []
|
||||
for old_path, (new_path, old_l, new_l) in self.change_dct.items():
|
||||
if old_path:
|
||||
udiff = difflib.unified_diff(old_l, new_l)
|
||||
else:
|
||||
udiff = difflib.unified_diff(old_l, new_l, old_path, new_path)
|
||||
texts.append('\n'.join(udiff))
|
||||
return '\n'.join(texts)
|
||||
|
||||
|
||||
def rename(script, new_name):
|
||||
""" The `args` / `kwargs` params are the same as in `api.Script`.
|
||||
:param operation: The refactoring operation to execute.
|
||||
:type operation: str
|
||||
:type source: str
|
||||
:return: list of changed lines/changed files
|
||||
"""
|
||||
return Refactoring(_rename(script.usages(), new_name))
|
||||
|
||||
|
||||
def _rename(names, replace_str):
|
||||
""" For both rename and inline. """
|
||||
order = sorted(names, key=lambda x: (x.module_path, x.line, x.column),
|
||||
reverse=True)
|
||||
|
||||
def process(path, old_lines, new_lines):
|
||||
if new_lines is not None: # goto next file, save last
|
||||
dct[path] = path, old_lines, new_lines
|
||||
|
||||
dct = {}
|
||||
current_path = object()
|
||||
new_lines = old_lines = None
|
||||
for name in order:
|
||||
if name.in_builtin_module():
|
||||
continue
|
||||
if current_path != name.module_path:
|
||||
current_path = name.module_path
|
||||
|
||||
process(current_path, old_lines, new_lines)
|
||||
if current_path is not None:
|
||||
# None means take the source that is a normal param.
|
||||
with open(current_path) as f:
|
||||
source = f.read()
|
||||
|
||||
new_lines = split_lines(python_bytes_to_unicode(source))
|
||||
old_lines = new_lines[:]
|
||||
|
||||
nr, indent = name.line, name.column
|
||||
line = new_lines[nr - 1]
|
||||
new_lines[nr - 1] = line[:indent] + replace_str + \
|
||||
line[indent + len(name.name):]
|
||||
process(current_path, old_lines, new_lines)
|
||||
return dct
|
||||
|
||||
|
||||
def extract(script, new_name):
|
||||
""" The `args` / `kwargs` params are the same as in `api.Script`.
|
||||
:param operation: The refactoring operation to execute.
|
||||
:type operation: str
|
||||
:type source: str
|
||||
:return: list of changed lines/changed files
|
||||
"""
|
||||
new_lines = split_lines(python_bytes_to_unicode(script.source))
|
||||
old_lines = new_lines[:]
|
||||
|
||||
user_stmt = script._parser.user_stmt()
|
||||
|
||||
# TODO care for multiline extracts
|
||||
dct = {}
|
||||
if user_stmt:
|
||||
pos = script._pos
|
||||
line_index = pos[0] - 1
|
||||
arr, index = helpers.array_for_pos(user_stmt, pos)
|
||||
if arr is not None:
|
||||
start_pos = arr[index].start_pos
|
||||
end_pos = arr[index].end_pos
|
||||
|
||||
# take full line if the start line is different from end line
|
||||
e = end_pos[1] if end_pos[0] == start_pos[0] else None
|
||||
start_line = new_lines[start_pos[0] - 1]
|
||||
text = start_line[start_pos[1]:e]
|
||||
for l in range(start_pos[0], end_pos[0] - 1):
|
||||
text += '\n' + l
|
||||
if e is None:
|
||||
end_line = new_lines[end_pos[0] - 1]
|
||||
text += '\n' + end_line[:end_pos[1]]
|
||||
|
||||
# remove code from new lines
|
||||
t = text.lstrip()
|
||||
del_start = start_pos[1] + len(text) - len(t)
|
||||
|
||||
text = t.rstrip()
|
||||
del_end = len(t) - len(text)
|
||||
if e is None:
|
||||
new_lines[end_pos[0] - 1] = end_line[end_pos[1] - del_end:]
|
||||
e = len(start_line)
|
||||
else:
|
||||
e = e - del_end
|
||||
start_line = start_line[:del_start] + new_name + start_line[e:]
|
||||
new_lines[start_pos[0] - 1] = start_line
|
||||
new_lines[start_pos[0]:end_pos[0] - 1] = []
|
||||
|
||||
# add parentheses in multiline case
|
||||
open_brackets = ['(', '[', '{']
|
||||
close_brackets = [')', ']', '}']
|
||||
if '\n' in text and not (text[0] in open_brackets and text[-1] ==
|
||||
close_brackets[open_brackets.index(text[0])]):
|
||||
text = '(%s)' % text
|
||||
|
||||
# add new line before statement
|
||||
indent = user_stmt.start_pos[1]
|
||||
new = "%s%s = %s" % (' ' * indent, new_name, text)
|
||||
new_lines.insert(line_index, new)
|
||||
dct[script.path] = script.path, old_lines, new_lines
|
||||
return Refactoring(dct)
|
||||
|
||||
|
||||
def inline(script):
|
||||
"""
|
||||
:type script: api.Script
|
||||
"""
|
||||
new_lines = split_lines(python_bytes_to_unicode(script.source))
|
||||
|
||||
dct = {}
|
||||
|
||||
definitions = script.goto_assignments()
|
||||
with common.ignored(AssertionError):
|
||||
assert len(definitions) == 1
|
||||
stmt = definitions[0]._definition
|
||||
usages = script.usages()
|
||||
inlines = [r for r in usages
|
||||
if not stmt.start_pos <= (r.line, r.column) <= stmt.end_pos]
|
||||
inlines = sorted(inlines, key=lambda x: (x.module_path, x.line, x.column),
|
||||
reverse=True)
|
||||
expression_list = stmt.expression_list()
|
||||
# don't allow multiline refactorings for now.
|
||||
assert stmt.start_pos[0] == stmt.end_pos[0]
|
||||
index = stmt.start_pos[0] - 1
|
||||
|
||||
line = new_lines[index]
|
||||
replace_str = line[expression_list[0].start_pos[1]:stmt.end_pos[1] + 1]
|
||||
replace_str = replace_str.strip()
|
||||
# tuples need parentheses
|
||||
if expression_list and isinstance(expression_list[0], pr.Array):
|
||||
arr = expression_list[0]
|
||||
if replace_str[0] not in ['(', '[', '{'] and len(arr) > 1:
|
||||
replace_str = '(%s)' % replace_str
|
||||
|
||||
# if it's the only assignment, remove the statement
|
||||
if len(stmt.get_defined_names()) == 1:
|
||||
line = line[:stmt.start_pos[1]] + line[stmt.end_pos[1]:]
|
||||
|
||||
dct = _rename(inlines, replace_str)
|
||||
# remove the empty line
|
||||
new_lines = dct[script.path][2]
|
||||
if line.strip():
|
||||
new_lines[index] = line
|
||||
else:
|
||||
new_lines.pop(index)
|
||||
|
||||
return Refactoring(dct)
|
@ -0,0 +1,163 @@
|
||||
"""
|
||||
This module contains variables with global |jedi| settings. To change the
|
||||
behavior of |jedi|, change the variables defined in :mod:`jedi.settings`.
|
||||
|
||||
Plugins should expose an interface so that the user can adjust the
|
||||
configuration.
|
||||
|
||||
|
||||
Example usage::
|
||||
|
||||
from jedi import settings
|
||||
settings.case_insensitive_completion = True
|
||||
|
||||
|
||||
Completion output
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autodata:: case_insensitive_completion
|
||||
.. autodata:: add_bracket_after_function
|
||||
.. autodata:: no_completion_duplicates
|
||||
|
||||
|
||||
Filesystem cache
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autodata:: cache_directory
|
||||
.. autodata:: use_filesystem_cache
|
||||
|
||||
|
||||
Parser
|
||||
~~~~~~
|
||||
|
||||
.. autodata:: fast_parser
|
||||
|
||||
|
||||
Dynamic stuff
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. autodata:: dynamic_array_additions
|
||||
.. autodata:: dynamic_params
|
||||
.. autodata:: dynamic_params_for_other_modules
|
||||
.. autodata:: additional_dynamic_modules
|
||||
.. autodata:: auto_import_modules
|
||||
|
||||
|
||||
Caching
|
||||
~~~~~~~
|
||||
|
||||
.. autodata:: call_signatures_validity
|
||||
|
||||
|
||||
"""
|
||||
import os
|
||||
import platform
|
||||
|
||||
# ----------------
|
||||
# completion output settings
|
||||
# ----------------
|
||||
|
||||
case_insensitive_completion = True
|
||||
"""
|
||||
The completion is by default case insensitive.
|
||||
"""
|
||||
|
||||
add_bracket_after_function = False
|
||||
"""
|
||||
Adds an opening bracket after a function, because that's normal behaviour.
|
||||
Removed it again, because in VIM that is not very practical.
|
||||
"""
|
||||
|
||||
no_completion_duplicates = True
|
||||
"""
|
||||
If set, completions with the same name don't appear in the output anymore,
|
||||
but are in the `same_name_completions` attribute.
|
||||
"""
|
||||
|
||||
# ----------------
|
||||
# Filesystem cache
|
||||
# ----------------
|
||||
|
||||
use_filesystem_cache = True
|
||||
"""
|
||||
Use filesystem cache to save once parsed files with pickle.
|
||||
"""
|
||||
|
||||
if platform.system().lower() == 'windows':
|
||||
_cache_directory = os.path.join(os.getenv('APPDATA') or '~', 'Jedi',
|
||||
'Jedi')
|
||||
elif platform.system().lower() == 'darwin':
|
||||
_cache_directory = os.path.join('~', 'Library', 'Caches', 'Jedi')
|
||||
else:
|
||||
_cache_directory = os.path.join(os.getenv('XDG_CACHE_HOME') or '~/.cache',
|
||||
'jedi')
|
||||
cache_directory = os.path.expanduser(_cache_directory)
|
||||
"""
|
||||
The path where the cache is stored.
|
||||
|
||||
On Linux, this defaults to ``~/.cache/jedi/``, on OS X to
|
||||
``~/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.
|
||||
"""
|
||||
|
||||
# ----------------
|
||||
# parser
|
||||
# ----------------
|
||||
|
||||
fast_parser = True
|
||||
"""
|
||||
Use the fast parser. This means that reparsing is only being done if
|
||||
something has been changed e.g. to a function. If this happens, only the
|
||||
function is being reparsed.
|
||||
"""
|
||||
|
||||
# ----------------
|
||||
# dynamic stuff
|
||||
# ----------------
|
||||
|
||||
dynamic_array_additions = True
|
||||
"""
|
||||
check for `append`, etc. on arrays: [], {}, () as well as list/set calls.
|
||||
"""
|
||||
|
||||
dynamic_params = True
|
||||
"""
|
||||
A dynamic param completion, finds the callees of the function, which define
|
||||
the params of a function.
|
||||
"""
|
||||
|
||||
dynamic_params_for_other_modules = True
|
||||
"""
|
||||
Do the same for other modules.
|
||||
"""
|
||||
|
||||
additional_dynamic_modules = []
|
||||
"""
|
||||
Additional modules in which |jedi| checks if statements are to be found. This
|
||||
is practical for IDEs, that want to administrate their modules themselves.
|
||||
"""
|
||||
|
||||
dynamic_flow_information = True
|
||||
"""
|
||||
Check for `isinstance` and other information to infer a type.
|
||||
"""
|
||||
|
||||
auto_import_modules = [
|
||||
'hashlib', # setattr
|
||||
]
|
||||
"""
|
||||
Modules that are not analyzed but imported, although they contain Python code.
|
||||
This improves autocompletion for libraries that use ``setattr`` or
|
||||
``globals()`` modifications a lot.
|
||||
"""
|
||||
|
||||
# ----------------
|
||||
# caching validity (time)
|
||||
# ----------------
|
||||
|
||||
call_signatures_validity = 3.0
|
||||
"""
|
||||
Finding function calls might be slow (0.1-0.5s). This is not acceptible for
|
||||
normal writing. Therefore cache it for a short time.
|
||||
"""
|
@ -0,0 +1,136 @@
|
||||
"""
|
||||
Utilities for end-users.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import __main__
|
||||
from collections import namedtuple
|
||||
import logging
|
||||
import traceback
|
||||
import re
|
||||
import os
|
||||
import sys
|
||||
|
||||
from parso import split_lines
|
||||
|
||||
from jedi import Interpreter
|
||||
from jedi.api.helpers import get_on_completion_name
|
||||
|
||||
|
||||
READLINE_DEBUG = False
|
||||
|
||||
|
||||
def setup_readline(namespace_module=__main__):
|
||||
"""
|
||||
Install Jedi completer to :mod:`readline`.
|
||||
|
||||
This function setups :mod:`readline` to use Jedi in Python interactive
|
||||
shell. If you want to use a custom ``PYTHONSTARTUP`` file (typically
|
||||
``$HOME/.pythonrc.py``), you can add this piece of code::
|
||||
|
||||
try:
|
||||
from jedi.utils import setup_readline
|
||||
setup_readline()
|
||||
except ImportError:
|
||||
# Fallback to the stdlib readline completer if it is installed.
|
||||
# Taken from http://docs.python.org/2/library/rlcompleter.html
|
||||
print("Jedi is not installed, falling back to readline")
|
||||
try:
|
||||
import readline
|
||||
import rlcompleter
|
||||
readline.parse_and_bind("tab: complete")
|
||||
except ImportError:
|
||||
print("Readline is not installed either. No tab completion is enabled.")
|
||||
|
||||
This will fallback to the readline completer if Jedi is not installed.
|
||||
The readline completer will only complete names in the global namespace,
|
||||
so for example::
|
||||
|
||||
ran<TAB>
|
||||
|
||||
will complete to ``range``
|
||||
|
||||
with both Jedi and readline, but::
|
||||
|
||||
range(10).cou<TAB>
|
||||
|
||||
will show complete to ``range(10).count`` only with Jedi.
|
||||
|
||||
You'll also need to add ``export PYTHONSTARTUP=$HOME/.pythonrc.py`` to
|
||||
your shell profile (usually ``.bash_profile`` or ``.profile`` if you use
|
||||
bash).
|
||||
|
||||
"""
|
||||
if READLINE_DEBUG:
|
||||
logging.basicConfig(
|
||||
filename='/tmp/jedi.log',
|
||||
filemode='a',
|
||||
level=logging.DEBUG
|
||||
)
|
||||
|
||||
class JediRL(object):
|
||||
def complete(self, text, state):
|
||||
"""
|
||||
This complete stuff is pretty weird, a generator would make
|
||||
a lot more sense, but probably due to backwards compatibility
|
||||
this is still the way how it works.
|
||||
|
||||
The only important part is stuff in the ``state == 0`` flow,
|
||||
everything else has been copied from the ``rlcompleter`` std.
|
||||
library module.
|
||||
"""
|
||||
if state == 0:
|
||||
sys.path.insert(0, os.getcwd())
|
||||
# Calling python doesn't have a path, so add to sys.path.
|
||||
try:
|
||||
logging.debug("Start REPL completion: " + repr(text))
|
||||
interpreter = Interpreter(text, [namespace_module.__dict__])
|
||||
|
||||
lines = split_lines(text)
|
||||
position = (len(lines), len(lines[-1]))
|
||||
name = get_on_completion_name(
|
||||
interpreter._get_module_node(),
|
||||
lines,
|
||||
position
|
||||
)
|
||||
before = text[:len(text) - len(name)]
|
||||
completions = interpreter.completions()
|
||||
except:
|
||||
logging.error("REPL Completion error:\n" + traceback.format_exc())
|
||||
raise
|
||||
finally:
|
||||
sys.path.pop(0)
|
||||
|
||||
self.matches = [before + c.name_with_symbols for c in completions]
|
||||
try:
|
||||
return self.matches[state]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
print("Jedi: Module readline not available.")
|
||||
else:
|
||||
readline.set_completer(JediRL().complete)
|
||||
readline.parse_and_bind("tab: complete")
|
||||
# jedi itself does the case matching
|
||||
readline.parse_and_bind("set completion-ignore-case on")
|
||||
# because it's easier to hit the tab just once
|
||||
readline.parse_and_bind("set show-all-if-unmodified")
|
||||
readline.parse_and_bind("set show-all-if-ambiguous on")
|
||||
# don't repeat all the things written in the readline all the time
|
||||
readline.parse_and_bind("set completion-prefix-display-length 2")
|
||||
# No delimiters, Jedi handles that.
|
||||
readline.set_completer_delims('')
|
||||
|
||||
|
||||
def version_info():
|
||||
"""
|
||||
Returns a namedtuple of Jedi's version, similar to Python's
|
||||
``sys.version_info``.
|
||||
"""
|
||||
Version = namedtuple('Version', 'major, minor, micro')
|
||||
from jedi import __version__
|
||||
tupl = re.findall('[a-z]+|\d+', __version__)
|
||||
return Version(*[x if i == 3 else int(x) for i, x in enumerate(tupl)])
|
@ -0,0 +1,10 @@
|
||||
[pytest]
|
||||
addopts = --doctest-modules
|
||||
|
||||
# Ignore broken files in blackbox test directories
|
||||
norecursedirs = .* docs completion refactor absolute_import namespace_package scripts extensions speed static_analysis not_in_sys_path buildout_project sample_venvs init_extension_module simple_import
|
||||
|
||||
# Activate `clean_jedi_cache` fixture for all tests. This should be
|
||||
# fine as long as we are using `clean_jedi_cache` as a session scoped
|
||||
# fixture.
|
||||
usefixtures = clean_jedi_cache
|
@ -0,0 +1 @@
|
||||
parso==0.1.0
|
@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Profile a piece of Python code with ``cProfile`` that uses the diff parser.
|
||||
|
||||
Usage:
|
||||
profile.py <file> [-d] [-s <sort>]
|
||||
profile.py -h | --help
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
-d --debug Enable Jedi internal debugging.
|
||||
-s <sort> Sort the profile results, e.g. cumtime, name [default: time].
|
||||
"""
|
||||
|
||||
import cProfile
|
||||
|
||||
from docopt import docopt
|
||||
from jedi.parser.python import load_grammar
|
||||
from jedi.parser.diff import DiffParser
|
||||
from jedi.parser.python import ParserWithRecovery
|
||||
from jedi._compatibility import u
|
||||
from jedi.common import splitlines
|
||||
import jedi
|
||||
|
||||
|
||||
def run(parser, lines):
|
||||
diff_parser = DiffParser(parser)
|
||||
diff_parser.update(lines)
|
||||
# Make sure used_names is loaded
|
||||
parser.module.used_names
|
||||
|
||||
|
||||
def main(args):
|
||||
if args['--debug']:
|
||||
jedi.set_debug_function(notices=True)
|
||||
|
||||
with open(args['<file>']) as f:
|
||||
code = f.read()
|
||||
grammar = load_grammar()
|
||||
parser = ParserWithRecovery(grammar, u(code))
|
||||
# Make sure used_names is loaded
|
||||
parser.module.used_names
|
||||
|
||||
code = code + '\na\n' # Add something so the diff parser needs to run.
|
||||
lines = splitlines(code, keepends=True)
|
||||
cProfile.runctx('run(parser, lines)', globals(), locals(), sort=args['-s'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = docopt(__doc__)
|
||||
main(args)
|
@ -0,0 +1,58 @@
|
||||
#! /usr/bin/env python
|
||||
"""
|
||||
This is a convenience script to test the speed and memory usage of Jedi with
|
||||
large libraries.
|
||||
|
||||
Each library is preloaded by jedi, recording the time and memory consumed by
|
||||
each operation.
|
||||
|
||||
You can provide additional libraries via command line arguments.
|
||||
|
||||
Note: This requires the psutil library, available on PyPI.
|
||||
"""
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import psutil
|
||||
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__) + '/..'))
|
||||
import jedi
|
||||
|
||||
|
||||
def used_memory():
|
||||
"""Return the total MB of System Memory in use."""
|
||||
return psutil.virtual_memory().used / 2 ** 20
|
||||
|
||||
|
||||
def profile_preload(mod):
|
||||
"""Preload a module into Jedi, recording time and memory used."""
|
||||
base = used_memory()
|
||||
t0 = time.time()
|
||||
jedi.preload_module(mod)
|
||||
elapsed = time.time() - t0
|
||||
used = used_memory() - base
|
||||
return elapsed, used
|
||||
|
||||
|
||||
def main(mods):
|
||||
"""Preload the modules, and print the time and memory used."""
|
||||
t0 = time.time()
|
||||
baseline = used_memory()
|
||||
print('Time (s) | Mem (MB) | Package')
|
||||
print('------------------------------')
|
||||
for mod in mods:
|
||||
elapsed, used = profile_preload(mod)
|
||||
if used > 0:
|
||||
print('%8.2f | %8d | %s' % (elapsed, used, mod))
|
||||
print('------------------------------')
|
||||
elapsed = time.time() - t0
|
||||
used = used_memory() - baseline
|
||||
print('%8.2f | %8d | %s' % (elapsed, used, 'Total'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if sys.argv[1:]:
|
||||
mods = sys.argv[1:]
|
||||
else:
|
||||
mods = ['re', 'numpy', 'scipy', 'scipy.sparse', 'scipy.stats',
|
||||
'wx', 'decimal', 'PyQt4.QtGui', 'PySide.QtGui', 'Tkinter']
|
||||
main(mods)
|
@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env python
|
||||
"""
|
||||
Profile a piece of Python code with ``cProfile``. Tries a completion on a
|
||||
certain piece of code.
|
||||
|
||||
Usage:
|
||||
profile.py [<code>] [-n <number>] [-d] [-o] [-s <sort>]
|
||||
profile.py -h | --help
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
-n <number> Number of passes before profiling [default: 1].
|
||||
-d --debug Enable Jedi internal debugging.
|
||||
-o --omit Omit profiler, just do a normal run.
|
||||
-s <sort> Sort the profile results, e.g. cum, name [default: time].
|
||||
"""
|
||||
|
||||
import time
|
||||
import cProfile
|
||||
|
||||
from docopt import docopt
|
||||
import jedi
|
||||
|
||||
|
||||
def run(code, index):
|
||||
start = time.time()
|
||||
result = jedi.Script(code).completions()
|
||||
print('Used %ss for the %sth run.' % (time.time() - start, index + 1))
|
||||
return result
|
||||
|
||||
|
||||
def main(args):
|
||||
code = args['<code>']
|
||||
n = int(args['-n'])
|
||||
for i in range(n):
|
||||
run(code, i)
|
||||
|
||||
jedi.set_debug_function(notices=args['--debug'])
|
||||
if args['--omit']:
|
||||
run(code, n)
|
||||
else:
|
||||
cProfile.runctx('run(code, n)', globals(), locals(), sort=args['-s'])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = docopt(__doc__)
|
||||
if args['<code>'] is None:
|
||||
args['<code>'] = 'import numpy; numpy.array([0]).'
|
||||
main(args)
|
@ -0,0 +1,61 @@
|
||||
#! /usr/bin/env python
|
||||
"""
|
||||
Depends: ``objgraph`` (third party Python library)
|
||||
|
||||
``wx._core`` is a very nice module to test Jedi's speed and memory performance
|
||||
on big Python modules. Its size is ~16kLOC (one file). It also seems to look
|
||||
like a typical big Python modules. A mix between a lot of different Python
|
||||
things.
|
||||
|
||||
You can view a markup version of it here:
|
||||
http://svn.wxwidgets.org/viewvc/wx/wxPython/trunk/src/gtk/_core.py?view=markup
|
||||
"""
|
||||
|
||||
import resource
|
||||
import time
|
||||
import sys
|
||||
try:
|
||||
import urllib.request as urllib2
|
||||
except ImportError:
|
||||
import urllib2
|
||||
import gc
|
||||
from os.path import abspath, dirname
|
||||
|
||||
import objgraph
|
||||
|
||||
sys.path.insert(0, dirname(dirname(abspath(__file__))))
|
||||
import jedi
|
||||
|
||||
|
||||
def process_memory():
|
||||
"""
|
||||
In kB according to
|
||||
http://stackoverflow.com/questions/938733/total-memory-used-by-python-process
|
||||
"""
|
||||
return resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
||||
|
||||
uri = 'http://svn.wxwidgets.org/viewvc/wx/wxPython/trunk/src/gtk/_core.py?revision=74740&content-type=text%2Fplain&view=co'
|
||||
|
||||
wx_core = urllib2.urlopen(uri).read()
|
||||
|
||||
|
||||
def run():
|
||||
start = time.time()
|
||||
print('Process Memory before: %skB' % process_memory())
|
||||
# After this the module should be cached.
|
||||
# Need to invent a path so that it's really cached.
|
||||
jedi.Script(wx_core, path='foobar.py').completions()
|
||||
|
||||
gc.collect() # make sure that it's all fair and the gc did its job.
|
||||
print('Process Memory after: %skB' % process_memory())
|
||||
|
||||
print(objgraph.most_common_types(limit=50))
|
||||
print('\nIt took %s seconds to parse the file.' % (time.time() - start))
|
||||
|
||||
|
||||
print('First pass')
|
||||
run()
|
||||
print('\nSecond pass')
|
||||
run()
|
||||
print('\nThird pass')
|
||||
run()
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user