1
0
forked from VimPlug/jedi

Merge branch 'refactor'

This commit is contained in:
Dave Halter
2020-03-21 02:54:07 +01:00
116 changed files with 4326 additions and 2483 deletions

View File

@@ -4,7 +4,6 @@ omit =
jedi/inference/compiled/subprocess/__main__.py
jedi/__main__.py
# For now this is not being used.
jedi/refactoring.py
[report]
# Regexes for lines to exclude from consideration

View File

@@ -1,20 +1,18 @@
dist: xenial
language: python
python:
- 2.7
- 3.4
- 3.5
- 3.6
- 3.7
- 3.8
- 3.7
- 3.6
- 3.5
- 2.7
env:
- JEDI_TEST_ENVIRONMENT=27
- JEDI_TEST_ENVIRONMENT=34
- JEDI_TEST_ENVIRONMENT=35
- JEDI_TEST_ENVIRONMENT=36
- JEDI_TEST_ENVIRONMENT=37
- JEDI_TEST_ENVIRONMENT=38
- JEDI_TEST_ENVIRONMENT=37
- JEDI_TEST_ENVIRONMENT=36
- JEDI_TEST_ENVIRONMENT=35
- JEDI_TEST_ENVIRONMENT=27
- JEDI_TEST_ENVIRONMENT=interpreter
matrix:
@@ -42,7 +40,8 @@ script:
python_bin=python$test_env_version
python_path="$(which $python_bin || true)"
if [ -z "$python_path" ]; then
# Only required for JEDI_TEST_ENVIRONMENT=34.
# Only required for JEDI_TEST_ENVIRONMENT=38, because it's not always
# available.
download_name=python-$test_env_version
wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2
sudo tar xjf $download_name.tar.bz2 --directory / opt/python

View File

@@ -1,60 +1,62 @@
Main Authors
============
------------
David Halter (@davidhalter) <davidhalter88@gmail.com>
Takafumi Arakaki (@tkf) <aka.tkf@gmail.com>
- 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)
Robin Roth (@robinro)
Malte Plath (@langsamer)
Anton Zub (@zabulazza)
Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
Tobias Rzepka (@TobiasRzepka)
micbou (@micbou)
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
Max Woerner Chase (@mwchase) <max.chase@gmail.com>
Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
Shane Steinert-Threlkeld (@shanest) <ssshanest@gmail.com>
Tim Gates (@timgates42) <tim.gates@iress.com>
Lior Goldberg (@goldberglior)
- 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)
- Robin Roth (@robinro)
- Malte Plath (@langsamer)
- Anton Zub (@zabulazza)
- Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
- Tobias Rzepka (@TobiasRzepka)
- micbou (@micbou)
- Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
- Max Woerner Chase (@mwchase) <max.chase@gmail.com>
- Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
- Shane Steinert-Threlkeld (@shanest) <ssshanest@gmail.com>
- Tim Gates (@timgates42) <tim.gates@iress.com>
- Lior Goldberg (@goldberglior)
And a few more "anonymous" contributors.
Note: (@user) means a github user name.

View File

@@ -3,9 +3,32 @@
Changelog
---------
0.16.1 (2020--)
0.17.0 (2020-03-)
+++++++++++++++++++
- Added support projects. This allows user to specify which folders Jedi should
search.
- Added support for Refactoring. The following refactorings have been
implemented: ``Script.rename``, ``Script.inline``,
``Script.extract_variable`` and ``Script.extract_function``.
- Added ``Script.get_syntax_errors`` to display syntax errors in the current
script.
- Added code search capabilities both for individual files and projects. The
new functions are ``Project.search``, ``Project.complete_search``,
``Script.search`` and ``Script.complete_search``.
- Added ``Script.help`` to make it easier to display a help window to people.
Now returns pydoc information as well for Python keywords/operators. This
means that on the class keyword it will now return the docstring of Python's
builtin function ``help('class')``.
- The API documentation is now way more readable and complete. Check it out
under https://jedi.readthedocs.io. A lot of it has been rewritten.
- Removed Python 3.4 support
- Many bugfixes
This is likely going to be the last minor version that supports Python 2 and
Python3.5. Bugfixes will be provided in 0.17.1+. The next minor/major version
will probably be Jedi 1.0.0.
0.16.0 (2020-01-26)
+++++++++++++++++++
@@ -28,8 +51,8 @@ Changelog
- ``call_signatures`` deprecated, use ``get_signatures`` instead
- ``usages`` deprecated, use ``get_references`` instead
- ``jedi.names`` deprecated, use ``jedi.Script(...).get_names()``
- ``BaseDefinition.goto_assignments`` renamed to ``BaseDefinition.goto``
- Add follow_imports to ``Definition.goto``. Now its signature matches
- ``BaseName.goto_assignments`` renamed to ``BaseName.goto``
- Add follow_imports to ``Name.goto``. Now its signature matches
``Script.goto``.
- **Python 2 support deprecated**. For this release it is best effort. Python 2
has reached the end of its life and now it's just about a smooth transition.
@@ -69,13 +92,13 @@ Changelog
New APIs:
- ``Definition.get_signatures() -> List[Signature]``. Signatures are similar to
``CallSignature``. ``Definition.params`` is therefore deprecated.
- ``Name.get_signatures() -> List[Signature]``. Signatures are similar to
``CallSignature``. ``Name.params`` is therefore deprecated.
- ``Signature.to_string()`` to format signatures.
- ``Signature.params -> List[ParamDefinition]``, ParamDefinition has the
- ``Signature.params -> List[ParamName]``, ParamName has the
following additional attributes ``infer_default()``, ``infer_annotation()``,
``to_string()``, and ``kind``.
- ``Definition.execute() -> List[Definition]``, makes it possible to infer
- ``Name.execute() -> List[Name]``, makes it possible to infer
return values of functions.
@@ -91,7 +114,7 @@ New APIs:
- Added ``goto_*(prefer_stubs=True)`` as well as ``goto_*(prefer_stubs=True)``
- Stubs are used now for type inference
- Typeshed is used for better type inference
- Reworked Definition.full_name, should have more correct return values
- Reworked Name.full_name, should have more correct return values
0.13.3 (2019-02-24)
+++++++++++++++++++
@@ -171,7 +194,7 @@ New APIs:
- 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.
- Added ``get_line_code`` to ``Name`` and ``Completion`` objects.
- Completely rewritten the type inference engine.
- A new and better parser for (fast) parsing diffs of Python code.

View File

@@ -1,14 +1,14 @@
###################################################################
Jedi - an awesome autocompletion/static analysis library for Python
###################################################################
####################################################################################
Jedi - an awesome autocompletion, static analysis and refactoring library for Python
####################################################################################
.. image:: https://img.shields.io/pypi/v/jedi.svg?style=flat
:target: https://pypi.python.org/pypi/jedi
:alt: PyPI version
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
:target: https://github.com/davidhalter/jedi/issues
:alt: The percentage of open issues and pull requests
.. image:: https://img.shields.io/pypi/pyversions/jedi.svg
:target: https://pypi.python.org/pypi/jedi
:alt: Supported Python versions
.. image:: http://isitmaintained.com/badge/resolution/davidhalter/jedi.svg
:target: https://github.com/davidhalter/jedi/issues
:alt: The resolution time is the median time an issue or pull request stays open.
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master
:target: https://travis-ci.org/davidhalter/jedi
@@ -23,22 +23,15 @@ Jedi - an awesome autocompletion/static analysis library for Python
:alt: Coverage status
*If you have specific questions, please add an issue or ask on* `Stack Overflow
<https://stackoverflow.com/questions/tagged/python-jedi>`_ *with the label* ``python-jedi``.
Jedi is a static analysis tool for Python that is typically used in
IDEs/editors plugins. Jedi has a focus on autocompletion and goto
functionality. Other features include refactoring, code search and finding
references.
Jedi is a static analysis tool for Python that can be used in IDEs/editors.
Jedi has a focus on autocompletion and goto functionality. Jedi is fast and is
very well tested. It understands Python and stubs on a deep level.
Jedi has support for different goto functions. It's possible to search for
references and list names in a Python file to get information about them.
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.
Autocompletion in your REPL is also possible, IPython uses it natively and for
the CPython REPL you have to install it.
Jedi has a simple API to work with. There is a reference implementation as a
`VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
REPL is also possible, IPython uses it natively and for the CPython REPL you
can install it. Jedi is well tested and bugs should be rare.
Jedi can currently be used with the following editors/projects:
@@ -47,7 +40,7 @@ Jedi can currently be used with the following editors/projects:
- 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
- Kate_ version 4.13+ supports it natively, you have to enable it, though. [`see
<https://projects.kde.org/projects/kde/applications/kate/repository/show?rev=KDE%2F4.13>`_]
- Atom_ (autocomplete-python-jedi_)
- `GNOME Builder`_ (with support for GObject Introspection)
@@ -58,7 +51,6 @@ Jedi can currently be used with the following editors/projects:
and many more!
Here are some pictures taken from jedi-vim_:
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png
@@ -71,47 +63,41 @@ 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.
Documentation support (Shift+k).
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 <https://semver.org/>`_.
<https://jedi.readthedocs.org/en/latest/>`_. Pull requests with enhancements
and/or fixes are awesome and most welcome. Jedi uses `semantic versioning
<https://semver.org/>`_.
If you want to stay up-to-date (News / RFCs), please subscribe to this `github
thread <https://github.com/davidhalter/jedi/issues/1063>`_.:
Issues & Questions
==================
You can file issues and questions in the `issue tracker
<https://github.com/davidhalter/jedi/>`. Alternatively you can also ask on
`Stack Overflow <https://stackoverflow.com/questions/tagged/python-jedi>`_ with
the label ``python-jedi``.
Installation
============
pip install jedi
`Check out the docs <https://jedi.readthedocs.org/en/latest/docs/installation.html>`_.
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.
Features and Limitations
========================
You don't want to use ``pip``? Please refer to the `manual
<https://jedi.readthedocs.org/en/latest/docs/installation.html>`_.
Jedi's features are listed here:
`Features <https://jedi.readthedocs.org/en/latest/docs/features.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.7 or 3.4+ but it should also
understand/parse code older than those versions. Additionally you should be able
to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
You can run Jedi on CPython 2.7 or 3.5+ but it should also
understand code that is older than those versions. Additionally you should be
able to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
very well.
Tips on how to use Jedi efficiently can be found `here
@@ -120,47 +106,62 @@ Tips on how to use Jedi efficiently can be found `here
API
---
You can find the documentation for the `API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_.
You can find a comprehensive documentation for the
`API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_.
Autocompletion / Goto / Documentation
-------------------------------------
Autocompletion / Goto / Pydoc
-----------------------------
Please check the API for a good explanation. There are the following commands:
There are the following commands:
- ``jedi.Script.goto``
- ``jedi.Script.infer``
- ``jedi.Script.help``
- ``jedi.Script.complete``
- ``jedi.Script.get_references``
- ``jedi.Script.get_signatures``
- ``jedi.Script.get_context``
The returned objects are very powerful and really all you might need.
The returned objects are very powerful and are 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.
Jedi is a dependency of IPython. Autocompletion in IPython with Jedi 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
Here is an `example video <https://vimeo.com/122332037>`_ how REPL completion
can look like.
For the ``python`` shell 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
------------------------
---------------
To do all forms of static analysis, please try to use
``jedi.Script(...).get_names``. It will return a list of names that you can use
to infer types and so on.
For a lot of forms of static analysis, you can try to use
``jedi.Script(...).get_names``. It will return a list of names that you can
then filter and work with. There is also a way to list the syntax errors in a
file: ``jedi.Script.get_syntax_errors``.
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.
Jedi supports the following refactorings:
- ``jedi.Script.inline``
- ``jedi.Script.rename``
- ``jedi.Script.extract_function``
- ``jedi.Script.extract_variable``
Code Search
-----------
There is support for module search with ``jedi.Script.search``, and project
search for ``jedi.Project.search``. The way to search is either by providing a
name like ``foo`` or by using dotted syntax like ``foo.bar``. Additionally you
can provide the API type like ``class foo.bar.Bar``. There are also the
functions ``jedi.Script.complete_search`` and ``jedi.Project.complete_search``.
Development
===========
@@ -168,39 +169,26 @@ 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``::
The test suite uses ``pytest``::
pip install tox pytest
pip install pytest
To run the tests for all supported Python versions::
If you want to test only a specific Python version (e.g. Python 3.8), it is as
easy as::
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/>`_.
python3.8 -m pytest
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).
Thanks a lot to all the
`contributors <https://jedi.readthedocs.org/en/latest/docs/acknowledgements.html>`_!
.. _jedi-vim: https://github.com/davidhalter/jedi-vim

View File

@@ -1,68 +1,56 @@
environment:
matrix:
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py27
PYTHON_PATH: C:\Python27
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 37
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py35
PYTHON_PATH: C:\Python35
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 37
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 35
JEDI_TEST_ENVIRONMENT: 37
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 37
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py36
PYTHON_PATH: C:\Python36
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 34
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py37
PYTHON_PATH: C:\Python37
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py37
PYTHON_PATH: C:\Python37
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 37
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py35
PYTHON_PATH: C:\Python35
JEDI_TEST_ENVIRONMENT: 27
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 37
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 36
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 35
- TOXENV: py27
PYTHON_PATH: C:\Python27
JEDI_TEST_ENVIRONMENT: 27
install:
- git submodule update --init --recursive
- set PATH=%PYTHON_PATH%;%PYTHON_PATH%\Scripts;%PATH%

9
docs/_static/custom_style.css vendored Normal file
View File

@@ -0,0 +1,9 @@
div.version {
color: black !important;
margin-top: -1.2em !important;
margin-bottom: .6em !important;
}
div.wy-side-nav-search {
padding-top: 0 !important;
}

View File

@@ -1,37 +0,0 @@
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.

View File

@@ -1,27 +0,0 @@
{%- 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">
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
</div>
{% if pagename == 'index' %}
</div>
{% endif %}
{%- endblock %}

View File

@@ -1,19 +0,0 @@
<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>

View File

@@ -1,394 +0,0 @@
/*
* 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;
}

View File

@@ -1,70 +0,0 @@
/*
* 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;
}

View File

@@ -1,9 +0,0 @@
[theme]
inherit = basic
stylesheet = flasky.css
pygments_style = flask_theme_support.FlaskyStyle
[options]
index_logo =
index_logo_height = 120px
touch_icon =

View File

@@ -1,125 +0,0 @@
"""
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'
}

View File

@@ -13,13 +13,11 @@
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 -----------------------------------------------------
@@ -29,7 +27,8 @@ sys.path.append(os.path.abspath('_themes'))
# 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']
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram',
'sphinx_rtd_theme', 'sphinx.ext.autosummary']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -54,8 +53,8 @@ from jedi.utils import version_info
# |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 short X.Y.Z version.
version = '.'.join(str(x) for x in version_info()[:3])
# The full version, including alpha/beta/rc tags.
release = jedi.__version__
@@ -98,12 +97,15 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
html_theme = 'flask'
html_theme = 'sphinx_rtd_theme'
# 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 = {}
html_theme_options = {
'logo_only': True,
'style_nav_header_background': 'white',
}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
@@ -117,7 +119,7 @@ html_theme_path = ['_themes']
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
html_logo = '_static/logo.png'
# 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
@@ -129,6 +131,8 @@ html_theme_path = ['_themes']
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
html_css_files = ['custom_style.css']
# 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'
@@ -145,7 +149,7 @@ html_sidebars = {
#'relations.html',
'ghbuttons.html',
#'sourcelink.html',
#'searchbox.html'
'searchbox.html'
]
}
@@ -163,13 +167,13 @@ html_sidebars = {
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
html_show_sphinx = False
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
html_show_copyright = False
# 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
@@ -274,7 +278,8 @@ autodoc_default_flags = []
# -- Options for intersphinx module --------------------------------------------
intersphinx_mapping = {
'https://docs.python.org/': None,
'python': ('https://docs.python.org/', None),
'parso': ('https://parso.readthedocs.io/en/latest/', None),
}

View File

@@ -0,0 +1,66 @@
.. include global.rst
History & Acknowledgements
==========================
Acknowledgements
----------------
- Dave Halter for creating and maintaining Jedi & Parso.
- 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).
- Thanks to all the :ref:`contributors <contributors>`.
A Little Bit of History
-----------------------
Written by Dave.
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
<https://youtu.be/yHRJLIf7wMU>`_ of Monty Python Jedis :-).
But actually the name has not much to do with Star Wars. It's part of my
second name Jedidjah.
I actually started Jedi back in 2012, because there were no good solutions
available for VIM. Most auto-completion solutions just did not work well. The
only good solution was PyCharm. But I liked my good old VIM very much. There
was also a solution called Rope that did not work at all for me. So I decided
to write my own version of a completion engine.
The first idea was to execute non-dangerous code. But I soon realized, that
this would not work. So I started to build a static analysis tool.
The biggest problem that I had at the time was that I did not know a thing
about parsers.I did not did not even know the word static analysis. It turns
out they are the foundation of a good static analysis tool. I of course did not
know that and tried to write my own poor version of a parser that I ended up
throwing away two years later.
Because of my lack of knowledge, everything after 2012 and before 2020 was
basically refactoring. I rewrote the core parts of Jedi probably like 5-10
times. The last big rewrite (that I did twice) was the inclusion of
gradual typing and stubs.
I learned during that time that it is crucial to have a good understanding of
your problem. Otherwise you just end up doing it again. I only wrote features
in the beginning and in the end. Everything else was bugfixing and refactoring.
However now I am really happy with the result. It works well, bugfixes can be
quick and is pretty much feature complete.
--------
I will leave you with a small annectote that happend in 2012, if I remember
correctly. After I explained Guido van Rossum, how some parts of my
auto-completion work, he said:
*"Oh, that worries me..."*
Now that it is finished, I hope he likes it :-).
.. _contributors:
.. include:: ../../AUTHORS.txt

View File

@@ -5,6 +5,49 @@
API Return Classes
------------------
.. automodule:: jedi.api.classes
Abstract Base Class
~~~~~~~~~~~~~~~~~~~
.. autoclass:: jedi.api.classes.BaseName
:members:
:undoc-members:
:show-inheritance:
Name
~~~~
.. autoclass:: jedi.api.classes.Name
:members:
:show-inheritance:
Completion
~~~~~~~~~~
.. autoclass:: jedi.api.classes.Completion
:members:
:show-inheritance:
BaseSignature
~~~~~~~~~~~~~
.. autoclass:: jedi.api.classes.BaseSignature
:members:
:show-inheritance:
Signature
~~~~~~~~~
.. autoclass:: jedi.api.classes.Signature
:members:
:show-inheritance:
ParamName
~~~~~~~~~
.. autoclass:: jedi.api.classes.ParamName
:members:
:show-inheritance:
Refactoring
~~~~~~~~~~~
.. autoclass:: jedi.api.refactoring.Refactoring
:members:
:show-inheritance:
.. autoclass:: jedi.api.errors.SyntaxError
:members:
:show-inheritance:

View File

@@ -3,56 +3,74 @@
API Overview
============
.. 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
<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
-----------------
The API consists of a few different parts:
- The main starting points for complete/goto: :class:`.Script` and :class:`.Interpreter`
- Helpful functions: :func:`.preload_module` and :func:`.set_debug_function`
- :ref:`API Result Classes <api-classes>`
- :ref:`Python Versions/Virtualenv Support <environments>` with functions like
:func:`.find_system_environments` and :func:`.find_virtualenvs`
.. note:: This documentation is mostly for Plugin developers, who want to
improve their editors/IDE with Jedi.
.. _api:
Static Analysis Interface
~~~~~~~~~~~~~~~~~~~~~~~~~
The API consists of a few different parts:
.. automodule:: jedi
- The main starting points for complete/goto: :class:`.Script` and
:class:`.Interpreter`. If you work with Jedi you want to understand these
classes first.
- :ref:`API Result Classes <api-classes>`
- :ref:`Python Versions/Virtualenv Support <environments>` with functions like
:func:`.find_system_environments` and :func:`.find_virtualenvs`
- A way to work with different :ref:`Folders / Projects <projects>`
- Helpful functions: :func:`.preload_module` and :func:`.set_debug_function`
The methods that you are most likely going to use to work with Jedi are the
following ones:
.. currentmodule:: jedi
.. autosummary::
:nosignatures:
Script.complete
Script.goto
Script.infer
Script.help
Script.get_signatures
Script.get_references
Script.get_context
Script.get_names
Script.get_syntax_errors
Script.rename
Script.inline
Script.extract_variable
Script.extract_function
Script.search
Script.complete_search
Project.search
Project.complete_search
Script
------
.. autoclass:: jedi.Script
:members:
Interpreter
-----------
.. autoclass:: jedi.Interpreter
:members:
.. autofunction:: jedi.preload_module
.. autofunction:: jedi.set_debug_function
.. _projects:
Projects
--------
.. automodule:: jedi.api.project
.. autofunction:: jedi.get_default_project
.. autoclass:: jedi.Project
:members:
.. _environments:
Environments
~~~~~~~~~~~~
------------
.. automodule:: jedi.api.environment
@@ -65,18 +83,31 @@ Environments
.. autoclass:: jedi.api.environment.Environment
:members:
Helper Functions
----------------
.. autofunction:: jedi.preload_module
.. autofunction:: jedi.set_debug_function
Errors
------
.. autoexception:: jedi.InternalError
.. autoexception:: jedi.RefactoringError
Examples
--------
Completions:
Completions
~~~~~~~~~~~
.. sourcecode:: python
>>> import jedi
>>> source = '''import json; json.l'''
>>> script = jedi.Script(source, path='')
>>> code = '''import json; json.l'''
>>> script = jedi.Script(code, path='example.py')
>>> script
<jedi.api.Script object at 0x2121b10>
<Script: 'example.py' <SameEnvironment: 3.5.2 in /usr>>
>>> completions = script.complete(1, 19)
>>> completions
[<Completion: load>, <Completion: loads>]
@@ -87,12 +118,14 @@ Completions:
>>> completions[1].name
'loads'
Definitions / Goto:
Type Inference / Goto
~~~~~~~~~~~~~~~~~~~~~
.. sourcecode:: python
>>> import jedi
>>> source = '''def my_func():
>>> code = '''\
... def my_func():
... print 'called'
...
... alias = my_func
@@ -100,30 +133,42 @@ Definitions / Goto:
... inception = my_list[2]
...
... inception()'''
>>> script = jedi.Script(source, path='')
>>> script = jedi.Script(code)
>>>
>>> script.goto(8, 1)
[<Definition inception=my_list[2]>]
[<Name full_name='__main__.inception', description='inception = my_list[2]'>]
>>>
>>> script.infer(8, 1)
[<Definition def my_func>]
[<Name full_name='__main__.my_func', description='def my_func'>]
References:
References
~~~~~~~~~~
.. sourcecode:: python
>>> import jedi
>>> source = '''x = 3
>>> code = '''\
... x = 3
... if 1 == 2:
... x = 4
... else:
... del x'''
>>> script = jedi.Script(source, '')
>>> script = jedi.Script(code)
>>> rns = script.get_references(5, 8)
>>> rns
[<Definition full_name='__main__.x', description='x = 3'>,
<Definition full_name='__main__.x', description='x'>]
[<Name full_name='__main__.x', description='x = 3'>,
<Name full_name='__main__.x', description='x = 4'>,
<Name full_name='__main__.x', description='del x'>]
>>> rns[1].line
5
>>> rns[0].column
8
3
>>> rns[1].column
4
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 and at least two minor releases until we
remove the deprecated functionality.

1
docs/docs/changelog.rst Normal file
View File

@@ -0,0 +1 @@
.. include:: ../../CHANGELOG.rst

View File

@@ -22,16 +22,12 @@ 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|:
In six 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:`Stubs & Annotations <stubs>`
- :ref:`Caching & Recursions <caching-recursions>`
- :ref:`Helper modules <dev-helpers>`
@@ -59,17 +55,17 @@ because that's where all the magic happens. I need to introduce the :ref:`parser
Parser
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Jedi used to have it's internal parser, however this is now a separate project
Jedi used to have its internal parser, however this is now a separate project
and is called `parso <http://parso.readthedocs.io>`_.
The parser creates a syntax tree that |jedi| analyses and tries to understand.
The grammar that this parsers uses is very similar to the official Python
The grammar that this parser uses is very similar to the official Python
`grammar files <https://docs.python.org/3/reference/grammar.html>`_.
.. _inference:
Type inference of python code (inference/__init__.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference
@@ -80,7 +76,7 @@ Inference Values (inference/base_value.py)
.. inheritance-diagram::
jedi.inference.value.instance.TreeInstance
jedi.inference.value.klass.Classvalue
jedi.inference.value.klass.ClassValue
jedi.inference.value.function.FunctionValue
jedi.inference.value.function.FunctionExecutionContext
:parts: 1
@@ -89,7 +85,7 @@ Inference Values (inference/base_value.py)
.. _name_resolution:
Name resolution (inference/finder.py)
++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++
.. automodule:: jedi.inference.finder
@@ -114,7 +110,7 @@ Core Extensions
Core Extensions is a summary of the following topics:
- :ref:`Iterables & Dynamic Arrays <iterables>`
- :ref:`Dynamic Parameters <dynamic>`
- :ref:`Dynamic Parameters <dynamic_params>`
- :ref:`Docstrings <docstrings>`
- :ref:`Refactoring <refactoring>`
@@ -125,7 +121,7 @@ without some features.
.. _iterables:
Iterables & Dynamic Arrays (inference/value/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:
@@ -133,33 +129,33 @@ dynamic features of Python like lists that are filled after creation:
.. automodule:: jedi.inference.value.iterable
.. _dynamic:
.. _dynamic_params:
Parameter completion (inference/dynamic.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Parameter completion (inference/dynamic_params.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.dynamic
.. automodule:: jedi.inference.dynamic_params
.. _docstrings:
Docstrings (inference/docstrings.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.docstrings
.. _refactoring:
Refactoring (inference/refactoring.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Refactoring (inference/api/refactoring.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.refactoring
.. automodule:: jedi.api.refactoring
.. _imports-modules:
Imports & Modules
-------------------
-----------------
- :ref:`Modules <modules>`
@@ -170,7 +166,7 @@ Imports & Modules
.. _builtin:
Compiled Modules (inference/compiled.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.compiled
@@ -178,10 +174,16 @@ Compiled Modules (inference/compiled.py)
.. _imports:
Imports (inference/imports.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.imports
.. _stubs:
Stubs & Annotations (inference/gradual)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: jedi.inference.gradual
.. _caching-recursions:
@@ -210,13 +212,8 @@ Recursions (recursion.py)
.. _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

View File

@@ -1,29 +1,30 @@
.. include:: ../global.rst
Features and Caveats
====================
Features and Limitations
========================
Jedi obviously supports autocompletion. It's also possible to get it working in
(:ref:`your REPL (IPython, etc.) <repl-completion>`).
Jedi's main API calls and features are:
Static analysis is also possible by using ``jedi.Script(...).get_names``.
- Autocompletion: :meth:`.Script.complete`; It's also possible to get it
working in :ref:`your REPL (IPython, etc.) <repl-completion>`
- Goto/Type Inference: :meth:`.Script.goto` and :meth:`.Script.infer`
- Static Analysis: :meth:`.Script.get_names` and :meth:`.Script.get_syntax_errors`
- Refactorings: :meth:`.Script.rename`, :meth:`.Script.inline`,
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`
- Code Search: :meth:`.Script.search` and :meth:`.Project.search`
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.
Basic Features
--------------
General Features
----------------
- Python 2.7 and 3.4+ support
- Python 2.7 and 3.5+ support
- Ignores syntax errors and wrong indentation
- Can deal with complex module / function / class structures
- Great Virtualenv support
- Can infer function arguments from sphinx, epydoc and basic numpydoc docstrings,
and PEP0484-style type hints (:ref:`type hinting <type-hinting>`)
- Stub files
- Great ``virtualenv``/``venv`` support
- Works great with Python's :ref:`type hinting <type-hinting>`,
- Understands stub files
- Can infer function arguments for sphinx, epydoc and basic numpydoc docstrings
- Is overall a very solid piece of software that has been refined for a long
time. Bug reports are very welcome and are usually fixed within a few weeks.
Supported Python Features
@@ -38,7 +39,7 @@ Supported Python Features
- ``*args`` / ``**kwargs``
- decorators / lambdas / closures
- generators / iterators
- some descriptors: property / staticmethod / classmethod
- descriptors: property / staticmethod / classmethod / custom descriptors
- some magic methods: ``__call__``, ``__iter__``, ``__next__``, ``__get__``,
``__getitem__``, ``__init__``
- ``list.append()``, ``set.add()``, ``list.extend()``, etc.
@@ -46,191 +47,64 @@ Supported Python Features
- relative imports
- ``getattr()`` / ``__getattr__`` / ``__getattribute__``
- function annotations
- 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
- simple/typical ``sys.path`` modifications
- ``isinstance`` checks for if/while/assert
- namespace packages (includes ``pkgutil``, ``pkg_resources`` and PEP420 namespaces)
- Django / Flask / Buildout support
- Understands Pytest fixtures
Not Supported
-------------
Limitations
-----------
Not yet implemented:
In general Jedi's limit are quite high, but for very big projects or very
complex code, sometimes Jedi intentionally stops type inference, to avoid
hanging for a long time.
- manipulations of instances outside the instance variables without using
Additionally there are some Python patterns Jedi does not support. This is
intentional and below should be a complete list:
- Arbitrary metaclasses: Some metaclasses like enums and dataclasses are
reimplemented in Jedi to make them work. Most of the time stubs are good
enough to get type inference working, even when metaclasses are involved.
- ``setattr()``, ``__import__()``
- Writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
- Manipulations of instances outside the instance variables without using
methods
Will probably never be implemented:
- metaclasses (how could an auto-completion ever support this)
- ``setattr()``, ``__import__()``
- writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
Caveats
-------
**Slow Performance**
Performance Issues
~~~~~~~~~~~~~~~~~~
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.
builtins the first time. If you want to speed things up, you could preload
libriaries in |jedi|, with :func:`.preload_module`. However, once loaded, this
should not be a problem anymore. The same is true for huge modules like
``PySide``, ``wx``, ``tensorflow``, ``pandas``, etc.
**Security**
Jedi does not have a very good cache layer. This is probably the biggest and
only architectural `issue <https://github.com/davidhalter/jedi/issues/1059>`_ in
Jedi. Unfortunately it is not easy to change that. Dave Halter is thinking
about rewriting Jedi in Rust, but it has taken Jedi more than 8 years to reach
version 1.0, a rewrite will probably also take years.
Security is an important issue for |jedi|. Therefore no Python code is
executed. As long as you write pure Python, everything is inferred
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.
Security
--------
For :class:`.Script`
~~~~~~~~~~~~~~~~~~~~
Recipes
-------
Security is an important topic for |jedi|. By default, no code is executed
within Jedi. As long as you write pure Python, everything is inferred
statically. If you enable ``load_unsafe_extensions=True`` for your
:class:`.Project` and you use builtin modules (``c_builtin``) Jedi will execute
those modules. If you don't trust a code base, please do not enable that
option. It might lead to arbitrary code execution.
Here are some tips on how to use |jedi| efficiently.
For :class:`.Interpreter`
~~~~~~~~~~~~~~~~~~~~~~~~~
.. _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
::
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.
You can also use stub files.
**Sphinx style**
http://www.sphinx-doc.org/en/stable/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
<https://youtu.be/yHRJLIf7wMU>`_ 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.
If you want security for :class:`.Interpreter`, ``do not`` use it. Jedi does
execute properties and in general is not very careful to avoid code execution.
This is intentional: Most people trust the code bases they have imported,
because at that point a malicious code base would have had code execution
already.

View File

@@ -3,6 +3,15 @@
Installation and Configuration
==============================
.. warning:: Most people will want to install Jedi as a submodule/vendored and
not through pip/system wide. The reason for this is that it makes sense that
the plugin that uses Jedi has always access to it. Otherwise Jedi will not
work properly when virtualenvs are activated. So please read the
documentation of your editor/IDE plugin to install Jedi.
For plugin developers, Jedi works best if it is always available. Vendoring
is a pretty good option for that.
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.

View File

@@ -3,18 +3,14 @@
Jedi Testing
============
The test suite depends on ``tox`` and ``pytest``::
The test suite depends on ``pytest``::
pip install tox pytest
pip install 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
If you want to test only a specific Python version (e.g. Python 3.8), it is as
easy as::
tox -e py27
python3.8 -m pytest
Tests are also run automatically on `Travis CI
<https://travis-ci.org/davidhalter/jedi/>`_.
@@ -28,8 +24,8 @@ simple and readable testing structure.
.. _blackbox:
Blackbox Tests (run.py)
~~~~~~~~~~~~~~~~~~~~~~~
Integration Tests (run.py)
~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: test.run

View File

@@ -1,15 +1,12 @@
.. include:: ../global.rst
End User Usage
==============
Using Jedi
==========
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 can be used with a variety of plugins and software. It is also possible
to use |jedi| in the :ref:`Python shell or with IPython <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.
Below you can also find a list of :ref:`recipes for type hinting <recipes>`.
.. _editor-plugins:
@@ -17,60 +14,72 @@ developers.
Editor Plugins
--------------
Vim:
Vim
~~~
- jedi-vim_
- YouCompleteMe_
- deoplete-jedi_
Visual Studio Code:
Visual Studio Code
~~~~~~~~~~~~~~~~~~
- `Python Extension`_
Emacs:
Emacs
~~~~~
- Jedi.el_
- elpy_
- anaconda-mode_
Sublime Text 2/3:
Sublime Text 2/3
~~~~~~~~~~~~~~~~
- SublimeJEDI_ (ST2 & ST3)
- anaconda_ (only ST3)
SynWrite:
SynWrite
~~~~~~~~
- SynJedi_
TextMate:
TextMate
~~~~~~~~
- Textmate_ (Not sure if it's actually working)
Kate:
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.
Atom:
Atom
~~~~
- autocomplete-python-jedi_
GNOME Builder:
GNOME Builder
~~~~~~~~~~~~~
- `GNOME Builder`_ `supports it natively
<https://git.gnome.org/browse/gnome-builder/tree/plugins/jedi>`__,
and is enabled by default.
Gedit:
Gedit
~~~~~
- gedi_
Eric IDE:
Eric IDE
~~~~~~~~
- `Eric IDE`_ (Available as a plugin)
Web Debugger:
Web Debugger
~~~~~~~~~~~~
- wdb_
@@ -81,11 +90,14 @@ and many more!
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.
Jedi is a dependency of IPython. Autocompletion in IPython is therefore
possible without additional configuration.
Here is an `example video <https://vimeo.com/122332037>`_ how REPL completion
can look like in a different shell.
There are two different options how you can use Jedi autocompletion in
your Python interpreter. One with your custom ``$HOME/.pythonrc.py`` file
your ``python`` interpreter. One with your custom ``$HOME/.pythonrc.py`` file
and one that uses ``PYTHONSTARTUP``.
Using ``PYTHONSTARTUP``
@@ -93,11 +105,137 @@ Using ``PYTHONSTARTUP``
.. automodule:: jedi.api.replstartup
Using a custom ``$HOME/.pythonrc.py``
Using a Custom ``$HOME/.pythonrc.py``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. autofunction:: jedi.utils.setup_readline
.. _recipes:
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 docstring/annotation styles below. **Only gradual typing will
always work**, all the docstring solutions are glorified hacks and more
complicated cases will probably not work.
Official Gradual Typing (Recommended)
+++++++++++++++++++++++++++++++++++++
You can read a lot about Python's gradual typing system in the corresponding
PEPs like:
- `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_ as an introduction
- `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ for variable annotations
- `PEP 589 <https://www.python.org/dev/peps/pep-0589/>`_ for ``TypeDict``
- There are probably more :)
Below you can find a few examples how you can use this feature.
Function annotations::
def myfunction(node: ProgramNode, foo: str) -> None:
"""Do something with a ``node``.
"""
node.| # complete here
Assignment, for-loop and with-statement type hints::
import typing
x: int = foo()
y: typing.Optional[int] = 3
key: str
value: Employee
for key, value in foo.items():
pass
f: Union[int, float]
with foo() as f:
print(f + 3)
PEP-0484 should be supported in its entirety. Feel free to open issues if that
is not the case. You can also use stub files.
Sphinx style
++++++++++++
http://www.sphinx-doc.org/en/stable/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
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi

View File

@@ -1,3 +1,3 @@
:orphan:
.. |jedi| replace:: *Jedi*
.. |jedi| replace:: Jedi

View File

@@ -1,13 +1,40 @@
.. include global.rst
Jedi - an awesome autocompletion/static analysis library for Python
===================================================================
.. meta::
:github_url: https://github.com/davidhalter/jedi
Release v\ |release|. (:doc:`Installation <docs/installation>`)
Jedi - an awesome autocompletion, static analysis and refactoring library for Python
====================================================================================
.. image:: https://img.shields.io/github/stars/davidhalter/jedi.svg?style=social&label=Star&maxAge=2592000
:target: https://github.com/davidhalter/jedi
:alt: GitHub stars
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
:target: https://github.com/davidhalter/jedi/issues
:alt: The percentage of open issues and pull requests
.. image:: http://isitmaintained.com/badge/resolution/davidhalter/jedi.svg
:target: https://github.com/davidhalter/jedi/issues
:alt: The resolution time is the median time an issue or pull request stays open.
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master
:target: https://travis-ci.org/davidhalter/jedi
:alt: Linux Tests
.. image:: https://ci.appveyor.com/api/projects/status/mgva3bbawyma1new/branch/master?svg=true
:target: https://ci.appveyor.com/project/davidhalter/jedi/branch/master
:alt: Windows Tests
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
:target: https://coveralls.io/r/davidhalter/jedi
:alt: Coverage status
`Github Repository <https://github.com/davidhalter/jedi>`_
.. automodule:: jedi
Autocompletion can look like this (e.g. VIM plugin):
Autocompletion can for example look like this in jedi-vim:
.. figure:: _screenshots/screenshot_complete.png
@@ -18,16 +45,18 @@ Docs
----
.. toctree::
:maxdepth: 2
:maxdepth: 1
docs/usage
docs/installation
docs/features
docs/api
docs/api-classes
docs/installation
docs/settings
docs/development
docs/testing
docs/acknowledgements
docs/changelog
.. _resources:

View File

@@ -1,16 +1,13 @@
"""
Jedi is a static analysis tool for Python that can be used in IDEs/editors.
Jedi has a focus on autocompletion and goto functionality. Jedi is fast and is
very well tested. It understands Python and stubs on a deep level.
Jedi is a static analysis tool for Python that is typically used in
IDEs/editors plugins. Jedi has a focus on autocompletion and goto
functionality. Other features include refactoring, code search and finding
references.
Jedi has support for different goto functions. It's possible to search for
references and list names in a Python file to get information about them.
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.
Autocompletion in your REPL is also possible, IPython uses it natively and for
the CPython REPL you have to install it.
Jedi has a simple API to work with. There is a reference implementation as a
`VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
REPL is also possible, IPython uses it natively and for the CPython REPL you
can install it. Jedi is well tested and bugs should be rare.
Here's a simple example of the autocompletion feature:
@@ -28,12 +25,9 @@ Here's a simple example of the autocompletion feature:
ad
>>> print(completions[0].name)
load
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.16.1'
__version__ = '0.17.0'
from jedi.api import Script, Interpreter, set_debug_function, \
preload_module, names
@@ -42,7 +36,8 @@ from jedi.api.environment import find_virtualenvs, find_system_environments, \
get_default_environment, InvalidPythonEnvironment, create_environment, \
get_system_environment, InterpreterEnvironment
from jedi.api.project import Project, get_default_project
from jedi.api.exceptions import InternalError
from jedi.api.exceptions import InternalError, RefactoringError
# Finally load the internal plugins. This is only internal.
from jedi.plugins import registry
del registry

View File

@@ -11,7 +11,6 @@ import os
import re
import pkgutil
import warnings
import inspect
import subprocess
import weakref
try:
@@ -219,65 +218,6 @@ if the module is contained in a package.
"""
def _iter_modules(paths, prefix=''):
# Copy of pkgutil.iter_modules adapted to work with namespaces
for path in paths:
importer = pkgutil.get_importer(path)
if not isinstance(importer, importlib.machinery.FileFinder):
# We're only modifying the case for FileFinder. All the other cases
# still need to be checked (like zip-importing). Do this by just
# calling the pkgutil version.
for mod_info in pkgutil.iter_modules([path], prefix):
yield mod_info
continue
# START COPY OF pkutils._iter_file_finder_modules.
if importer.path is None or not os.path.isdir(importer.path):
return
yielded = {}
try:
filenames = os.listdir(importer.path)
except OSError:
# ignore unreadable directories like import does
filenames = []
filenames.sort() # handle packages before same-named modules
for fn in filenames:
modname = inspect.getmodulename(fn)
if modname == '__init__' or modname in yielded:
continue
# jedi addition: Avoid traversing special directories
if fn.startswith('.') or fn == '__pycache__':
continue
path = os.path.join(importer.path, fn)
ispkg = False
if not modname and os.path.isdir(path) and '.' not in fn:
modname = fn
# A few jedi modifications: Don't check if there's an
# __init__.py
try:
os.listdir(path)
except OSError:
# ignore unreadable directories like import does
continue
ispkg = True
if modname and '.' not in modname:
yielded[modname] = 1
yield importer, prefix + modname, ispkg
# END COPY
iter_modules = _iter_modules if py_version >= 34 else pkgutil.iter_modules
class ImplicitNSInfo(object):
"""Stores information returned from an implicit namespace spec"""
def __init__(self, name, paths):
@@ -442,64 +382,6 @@ try:
import cPickle as pickle
except ImportError:
import pickle
if sys.version_info[:2] == (3, 3):
"""
Monkeypatch the unpickler in Python 3.3. This is needed, because the
argument `encoding='bytes'` is not supported in 3.3, but badly needed to
communicate with Python 2.
"""
class NewUnpickler(pickle._Unpickler):
dispatch = dict(pickle._Unpickler.dispatch)
def _decode_string(self, value):
# Used to allow strings from Python 2 to be decoded either as
# bytes or Unicode strings. This should be used only with the
# STRING, BINSTRING and SHORT_BINSTRING opcodes.
if self.encoding == "bytes":
return value
else:
return value.decode(self.encoding, self.errors)
def load_string(self):
data = self.readline()[:-1]
# Strip outermost quotes
if len(data) >= 2 and data[0] == data[-1] and data[0] in b'"\'':
data = data[1:-1]
else:
raise pickle.UnpicklingError("the STRING opcode argument must be quoted")
self.append(self._decode_string(pickle.codecs.escape_decode(data)[0]))
dispatch[pickle.STRING[0]] = load_string
def load_binstring(self):
# Deprecated BINSTRING uses signed 32-bit length
len, = pickle.struct.unpack('<i', self.read(4))
if len < 0:
raise pickle.UnpicklingError("BINSTRING pickle has negative byte count")
data = self.read(len)
self.append(self._decode_string(data))
dispatch[pickle.BINSTRING[0]] = load_binstring
def load_short_binstring(self):
len = self.read(1)[0]
data = self.read(len)
self.append(self._decode_string(data))
dispatch[pickle.SHORT_BINSTRING[0]] = load_short_binstring
def load(file, fix_imports=True, encoding="ASCII", errors="strict"):
return NewUnpickler(file, fix_imports=fix_imports,
encoding=encoding, errors=errors).load()
def loads(s, fix_imports=True, encoding="ASCII", errors="strict"):
if isinstance(s, str):
raise TypeError("Can't load pickle from unicode string")
file = pickle.io.BytesIO(s)
return NewUnpickler(file, fix_imports=fix_imports,
encoding=encoding, errors=errors).load()
pickle.Unpickler = NewUnpickler
pickle.load = load
pickle.loads = loads
def pickle_load(file):

View File

@@ -6,12 +6,11 @@ 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 warnings
from functools import wraps
import parso
from parso.python import tree
@@ -26,16 +25,18 @@ from jedi.api import classes
from jedi.api import interpreter
from jedi.api import helpers
from jedi.api.helpers import validate_line_column
from jedi.api.completion import Completion
from jedi.api.completion import Completion, search_in_module
from jedi.api.keywords import KeywordName
from jedi.api.environment import InterpreterEnvironment
from jedi.api.project import get_default_project, Project
from jedi.api.errors import parso_to_jedi_errors
from jedi.api import refactoring
from jedi.api.refactoring.extract import extract_function, extract_variable
from jedi.inference import InferenceState
from jedi.inference import imports
from jedi.inference.references import find_references
from jedi.inference.arguments import try_iter_content
from jedi.inference.helpers import get_module_names, infer_call_of_leaf
from jedi.inference.helpers import infer_call_of_leaf
from jedi.inference.sys_path import transform_path_to_dotted
from jedi.inference.syntax_tree import tree_name_to_values
from jedi.inference.value import ModuleValue
@@ -43,72 +44,90 @@ from jedi.inference.base_value import ValueSet
from jedi.inference.value.iterable import unpack_tuple_to_dict
from jedi.inference.gradual.conversion import convert_names, convert_values
from jedi.inference.gradual.utils import load_proper_stub_module
from jedi.inference.utils import to_list
# 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)
def _no_python2_support(func):
# TODO remove when removing Python 2/3.5
@wraps(func)
def wrapper(self, *args, **kwargs):
if self._inference_state.grammar.version_info < (3, 6) or sys.version_info < (3, 6):
raise NotImplementedError(
"No support for refactorings/search on Python 2/3.5"
)
return func(self, *args, **kwargs)
return wrapper
class Script(object):
"""
A Script is the base for completions, goto or whatever you want to do with
|jedi|.
Jedi. The counter part of this class is :class:`Interpreter`, which works
with actual dictionaries and can work with a REPL. This class
should be used when a user edits code in an editor.
You can either use the ``source`` parameter or ``path`` to read a file.
You can either use the ``code`` 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|:
The Script's ``sys.path`` is very customizable:
- if `sys_path` parameter is not ``None``, it will be used as ``sys.path``
for the script;
- If `project` is provided with a ``sys_path``, that is going to be used.
- If `environment` is provided, its ``sys.path`` will be used
(see :func:`Environment.get_sys_path <jedi.api.environment.Environment.get_sys_path>`);
- Otherwise ``sys.path`` will match that of the default environment of
Jedi, which typically matches the sys path that was used at the time
when Jedi was imported.
- 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.inference.sys_path.get_venv_path`) and used for
the script;
Most methods have a ``line`` and a ``column`` parameter. Lines in Jedi are
always 1-based and columns are always zero based. To avoid repetition they
are not always documented. You can omit both line and column. Jedi will
then just do whatever action you are calling at the end of the file. If you
provide only the line, just will complete at the end of that line.
- otherwise ``sys.path`` will match that of |jedi|.
.. warning:: By default :attr:`jedi.settings.fast_parser` is enabled, which means
that parso reuses modules (i.e. they are not immutable). With this setting
Jedi is **not thread safe** and it is also not safe to use multiple
:class:`.Script` instances and its definitions at the same time.
:param source: The source code of the current file, separated by newlines.
:type source: str
:param line: Deprecated, please use it directly on e.g. `.complete`
If you are a normal plugin developer this should not be an issue. It is
an issue for people that do more complex stuff with Jedi.
This is purely a performance optimization and works pretty well for all
typical usages, however consider to turn the setting of if it causes
you problems. See also
`this discussion <https://github.com/davidhalter/jedi/issues/1240>`_.
:param code: The source code of the current file, separated by newlines.
:type code: str
:param line: Deprecated, please use it directly on e.g. ``.complete``
:type line: int
:param column: Deprecated, please use it directly on e.g. `.complete`
:param column: Deprecated, please use it directly on e.g. ``.complete``
: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'``).
:param encoding: Deprecated, cast to unicode yourself. The encoding of
``code``, 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
:param environment: TODO
:type environment: Environment
:param sys_path: Deprecated, use the project parameter.
:type sys_path: typing.List[str]
:param Environment environment: Provide a predefined :ref:`Environment <environments>`
to work with a specific Python version or virtualenv.
:param Project project: Provide a :class:`.Project` to make sure finding
references works well, because the right folder is searched. There are
also ways to modify the sys path and other things.
"""
def __init__(self, source=None, line=None, column=None, path=None,
encoding='utf-8', sys_path=None, environment=None,
project=None):
def __init__(self, code=None, line=None, column=None, path=None,
encoding=None, sys_path=None, environment=None,
project=None, source=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()
# Load the Python grammar of the current interpreter.
self._grammar = parso.load_grammar()
if sys_path is not None and not is_py3:
sys_path = list(map(force_unicode, sys_path))
if project is None:
# Load the Python grammar of the current interpreter.
project = get_default_project(
os.path.dirname(self.path) if path else None
)
# TODO deprecate and remove sys_path from the Script API.
if sys_path is not None:
project._sys_path = sys_path
@@ -118,12 +137,56 @@ class Script(object):
DeprecationWarning,
stacklevel=2
)
if encoding is None:
encoding = 'utf-8'
else:
warnings.warn(
"Deprecated since version 0.17.0. You should cast to valid "
"unicode yourself, especially if you are not using utf-8.",
DeprecationWarning,
stacklevel=2
)
if line is not None:
warnings.warn(
"Providing the line is now done in the functions themselves "
"like `Script(...).complete(line, column)`",
DeprecationWarning,
stacklevel=2
)
if column is not None:
warnings.warn(
"Providing the column is now done in the functions themselves "
"like `Script(...).complete(line, column)`",
DeprecationWarning,
stacklevel=2
)
if source is not None:
code = source
warnings.warn(
"Use the code keyword argument instead.",
DeprecationWarning,
stacklevel=2
)
if code is None:
# TODO add a better warning than the traceback!
with open(path, 'rb') as f:
code = f.read()
if sys_path is not None and not is_py3:
sys_path = list(map(force_unicode, sys_path))
if project is None:
# Load the Python grammar of the current interpreter.
project = get_default_project(
os.path.dirname(self.path) if path else None
)
self._inference_state = InferenceState(
project, environment=environment, script_path=self.path
)
debug.speed('init')
self._module_node, source = self._inference_state.parse_and_get_code(
code=source,
self._module_node, code = self._inference_state.parse_and_get_code(
code=code,
path=self.path,
encoding=encoding,
use_latest_grammar=path and path.endswith('.pyi'),
@@ -132,8 +195,8 @@ class Script(object):
cache_path=settings.cache_directory,
)
debug.speed('parsed')
self._code_lines = parso.split_lines(source, keepends=True)
self._code = source
self._code_lines = parso.split_lines(code, keepends=True)
self._code = code
self._pos = line, column
cache.clear_time_caches()
@@ -197,13 +260,17 @@ class Script(object):
@validate_line_column
def complete(self, line=None, column=None, **kwargs):
"""
Return :class:`classes.Completion` objects. Those objects contain
information about the completions, more than just names.
Completes objects under the cursor.
Those objects contain information about the completions, more than just
names.
:param fuzzy: Default False. Will return fuzzy completions, which means
that e.g. ``ooa`` will match ``foobar``.
:return: Completion objects, sorted by name and ``__`` comes last.
:rtype: list of :class:`classes.Completion`
:return: Completion objects, sorted by name. Normal names appear
before "private" names that start with ``_`` and those appear
before magic methods and name mangled names that start with ``__``.
:rtype: list of :class:`.Completion`
"""
return self._complete(line, column, **kwargs)
@@ -216,30 +283,39 @@ class Script(object):
return completion.complete()
def completions(self, fuzzy=False):
# Deprecated, will be removed.
warnings.warn(
"Deprecated since version 0.16.0. Use Script(...).complete instead.",
DeprecationWarning,
stacklevel=2
)
return self.complete(*self._pos, fuzzy=fuzzy)
@validate_line_column
def infer(self, line=None, column=None, **kwargs):
"""
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` and
Return the definitions of under the cursor. It is basically a wrapper
around Jedi's type inference.
This method follows complicated paths and returns the end, not the
first definition. The big difference between :meth:`goto` and
:meth:`infer` is that :meth:`goto` 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.
because depending on an option you can have two different versions of a
function.
:param only_stubs: Only return stubs for this goto call.
:param prefer_stubs: Prefer stubs to Python objects for this type
inference call.
:rtype: list of :class:`classes.Definition`
:param only_stubs: Only return stubs for this method.
:param prefer_stubs: Prefer stubs to Python objects for this method.
:rtype: list of :class:`.Name`
"""
with debug.increase_indent_cm('infer'):
return self._infer(line, column, **kwargs)
def goto_definitions(self, **kwargs):
# Deprecated, will be removed.
warnings.warn(
"Deprecated since version 0.16.0. Use Script(...).infer instead.",
DeprecationWarning,
stacklevel=2
)
return self.infer(*self._pos, **kwargs)
def _infer(self, line, column, only_stubs=False, prefer_stubs=False):
@@ -259,14 +335,18 @@ class Script(object):
prefer_stubs=prefer_stubs,
)
defs = [classes.Definition(self._inference_state, c.name) for c in values]
defs = [classes.Name(self._inference_state, c.name) for c in values]
# 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, follow_builtin_imports=False, **kwargs):
# Deprecated, will be removed.
warnings.warn(
"Deprecated since version 0.16.0. Use Script(...).goto instead.",
DeprecationWarning,
stacklevel=2
)
return self.goto(*self._pos,
follow_imports=follow_imports,
follow_builtin_imports=follow_builtin_imports,
@@ -275,17 +355,17 @@ class Script(object):
@validate_line_column
def goto(self, line=None, column=None, **kwargs):
"""
Return the first definition found, while optionally following imports.
Multiple objects may be returned, because Python itself is a
dynamic language, which means you can have two different versions of a
function.
Goes to the name that defined the object under the cursor. Optionally
you can follow imports.
Multiple objects may be returned, depending on an if you can have two
different versions of a function.
:param follow_imports: The goto call will follow imports.
:param follow_builtin_imports: If follow_imports is True will try to
look up names in builtins (i.e. compiled or extension modules).
:param only_stubs: Only return stubs for this goto call.
:param prefer_stubs: Prefer stubs to Python objects for this goto call.
:rtype: list of :class:`classes.Definition`
:param follow_imports: The method will follow imports.
:param follow_builtin_imports: If ``follow_imports`` is True will try
to look up names in builtins (i.e. compiled or extension modules).
:param only_stubs: Only return stubs for this method.
:param prefer_stubs: Prefer stubs to Python objects for this method.
:rtype: list of :class:`.Name`
"""
with debug.increase_indent_cm('goto'):
return self._goto(line, column, **kwargs)
@@ -323,48 +403,100 @@ class Script(object):
prefer_stubs=prefer_stubs,
)
defs = [classes.Definition(self._inference_state, d) for d in set(names)]
defs = [classes.Name(self._inference_state, d) for d in set(names)]
# Avoid duplicates
return list(set(helpers.sorted_definitions(defs)))
@_no_python2_support
def search(self, string, **kwargs):
"""
Searches a name in the current file. For a description of how the
search string should look like, please have a look at
:meth:`.Project.search`.
:param bool all_scopes: Default False; searches not only for
definitions on the top level of a module level, but also in
functions and classes.
:yields: :class:`.Name`
"""
return self._search(string, **kwargs) # Python 2 ...
def _search(self, string, all_scopes=False):
return self._search_func(string, all_scopes=all_scopes)
@to_list
def _search_func(self, string, all_scopes=False, complete=False, fuzzy=False):
names = self._names(all_scopes=all_scopes)
wanted_type, wanted_names = helpers.split_search_string(string)
return search_in_module(
self._inference_state,
self._get_module_context(),
names=names,
wanted_type=wanted_type,
wanted_names=wanted_names,
complete=complete,
fuzzy=fuzzy,
)
def complete_search(self, string, **kwargs):
"""
Like :meth:`.Script.search`, but completes that string. If you want to
have all possible definitions in a file you can also provide an empty
string.
:param bool all_scopes: Default False; searches not only for
definitions on the top level of a module level, but also in
functions and classes.
:param fuzzy: Default False. Will return fuzzy completions, which means
that e.g. ``ooa`` will match ``foobar``.
:yields: :class:`.Completion`
"""
return self._search_func(string, complete=True, **kwargs)
@validate_line_column
def help(self, line=None, column=None):
"""
Works like goto and returns a list of Definition objects. Returns
additional definitions for keywords and operators.
Used to display a help window to users. Uses :meth:`.Script.goto` and
returns additional definitions for keywords and operators.
The additional definitions are of ``Definition(...).type == 'keyword'``.
Typically you will want to display :meth:`.BaseName.docstring` to the
user for all the returned definitions.
The additional definitions are ``Name(...).type == 'keyword'``.
These definitions do not have a lot of value apart from their docstring
attribute, which contains the output of Python's ``help()`` function.
attribute, which contains the output of Python's :func:`help` function.
:rtype: list of :class:`classes.Definition`
:rtype: list of :class:`.Name`
"""
definitions = self.goto(line, column, follow_imports=True)
if definitions:
return definitions
leaf = self._module_node.get_leaf_for_position((line, column))
if leaf.type in ('keyword', 'operator', 'error_leaf'):
reserved = self._grammar._pgen_grammar.reserved_syntax_strings.keys()
reserved = self._inference_state.grammar._pgen_grammar.reserved_syntax_strings.keys()
if leaf.value in reserved:
name = KeywordName(self._inference_state, leaf.value)
return [classes.Definition(self._inference_state, name)]
return [classes.Name(self._inference_state, name)]
return []
def usages(self, **kwargs):
# Deprecated, will be removed.
warnings.warn(
"Deprecated since version 0.16.0. Use Script(...).get_references instead.",
DeprecationWarning,
stacklevel=2
)
return self.get_references(*self._pos, **kwargs)
@validate_line_column
def get_references(self, line=None, column=None, **kwargs):
"""
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 references of
a variable.
Lists all references of a variable in a project. Since this can be
quite hard to do for Jedi, if it is too complicated, Jedi will stop
searching.
:param include_builtins: Default True, checks if a reference is a
builtin (e.g. ``sys``) and in that case does not return it.
:rtype: list of :class:`classes.Definition`
:rtype: list of :class:`.Name`
"""
def _references(include_builtins=True):
@@ -375,20 +507,24 @@ class Script(object):
names = find_references(self._get_module_context(), tree_name)
definitions = [classes.Definition(self._inference_state, n) for n in names]
definitions = [classes.Name(self._inference_state, n) for n in names]
if not include_builtins:
definitions = [d for d in definitions if not d.in_builtin_module()]
return helpers.sorted_definitions(definitions)
return _references(**kwargs)
def call_signatures(self):
# Deprecated, will be removed.
warnings.warn(
"Deprecated since version 0.16.0. Use Script(...).get_signatures instead.",
DeprecationWarning,
stacklevel=2
)
return self.get_signatures(*self._pos)
@validate_line_column
def get_signatures(self, line=None, column=None):
"""
Return the function object of the call you're currently in.
Return the function object of the call under the cursor.
E.g. if the cursor is here::
@@ -400,7 +536,7 @@ class Script(object):
This would return an empty list..
:rtype: list of :class:`classes.Signature`
:rtype: list of :class:`.Signature`
"""
pos = line, column
call_details = helpers.get_signature_details(self._module_node, pos)
@@ -424,6 +560,12 @@ class Script(object):
@validate_line_column
def get_context(self, line=None, column=None):
"""
Returns the scope context under the cursor. This basically means the
function, class or module where the cursor is at.
:rtype: :class:`.Name`
"""
pos = (line, column)
leaf = self._module_node.get_leaf_for_position(pos, include_prefixes=True)
if leaf.start_pos > pos or leaf.type == 'endmarker':
@@ -446,7 +588,7 @@ class Script(object):
while context.name is None:
context = context.parent_context # comprehensions
definition = classes.Definition(self._inference_state, context.name)
definition = classes.Name(self._inference_state, context.name)
while definition.type != 'module':
name = definition._name # TODO private access
tree_name = name.tree_name
@@ -493,69 +635,197 @@ class Script(object):
def get_names(self, **kwargs):
"""
Returns a list of `Definition` objects, containing name parts.
This means you can call ``Definition.goto()`` and get the
reference of a name.
Returns names defined in the current file.
:param all_scopes: If True lists the names of all scopes instead of only
the module namespace.
: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``.
:rtype: list of :class:`.Name`
"""
return self._names(**kwargs) # Python 2...
names = self._names(**kwargs)
return [classes.Name(self._inference_state, n) for n in names]
def get_syntax_errors(self):
return parso_to_jedi_errors(self._grammar, self._module_node)
"""
Lists all syntax errors in the current file.
:rtype: list of :class:`.SyntaxError`
"""
return parso_to_jedi_errors(self._inference_state.grammar, self._module_node)
def _names(self, all_scopes=False, definitions=True, references=False):
def def_ref_filter(_def):
is_def = _def._name.tree_name.is_definition()
return definitions and is_def or references and not is_def
# Set line/column to a random position, because they don't matter.
module_context = self._get_module_context()
defs = [
classes.Definition(
self._inference_state,
module_context.create_name(name)
) for name in get_module_names(self._module_node, all_scopes)
for name in helpers.get_module_names(
self._module_node,
all_scopes=all_scopes,
definitions=definitions,
references=references,
)
]
return sorted(filter(def_ref_filter, defs), key=lambda x: (x.line, x.column))
return sorted(defs, key=lambda x: x.start_pos)
@_no_python2_support
def rename(self, line=None, column=None, **kwargs):
"""
Renames all references of the variable under the cursor.
:param new_name: The variable under the cursor will be renamed to this
string.
:raises: :exc:`.RefactoringError`
:rtype: :class:`.Refactoring`
"""
return self._rename(line, column, **kwargs)
def _rename(self, line, column, new_name): # Python 2...
definitions = self.get_references(line, column, include_builtins=False)
return refactoring.rename(self._inference_state, definitions, new_name)
@_no_python2_support
@validate_line_column
def extract_variable(self, line=None, column=None, **kwargs):
"""
Moves an expression to a new statemenet.
For example if you have the cursor on ``foo`` and provide a
``new_name`` called ``bar``::
foo = 3.1
x = int(foo + 1)
the code above will become::
foo = 3.1
bar = foo + 1
x = int(bar)
:param new_name: The expression under the cursor will be renamed to
this string.
:param int until_line: The the selection range ends at this line, when
omitted, Jedi will be clever and try to define the range itself.
:param int until_column: The the selection range ends at this column, when
omitted, Jedi will be clever and try to define the range itself.
:raises: :exc:`.RefactoringError`
:rtype: :class:`.Refactoring`
"""
return self._extract_variable(line, column, **kwargs) # Python 2...
def _extract_variable(self, line, column, new_name, until_line=None, until_column=None):
if until_line is None and until_column is None:
until_pos = None
else:
if until_line is None:
until_line = line
if until_column is None:
until_column = len(self._code_lines[until_line - 1])
until_pos = until_line, until_column
return extract_variable(
self._inference_state, self.path, self._module_node,
new_name, (line, column), until_pos
)
@_no_python2_support
def extract_function(self, line, column, **kwargs):
"""
Moves an expression to a new function.
For example if you have the cursor on ``foo`` and provide a
``new_name`` called ``bar``::
global_var = 3
def x():
foo = 3.1
x = int(foo + 1 + global_var)
the code above will become::
global_var = 3
def bar(foo):
return foo + 1 + global_var
def x(foo):
x = int(bar(foo))
:param new_name: The expression under the cursor will be replaced with
a function with this name.
:param int until_line: The the selection range ends at this line, when
omitted, Jedi will be clever and try to define the range itself.
:param int until_column: The the selection range ends at this column, when
omitted, Jedi will be clever and try to define the range itself.
:raises: :exc:`.RefactoringError`
:rtype: :class:`.Refactoring`
"""
return self._extract_function(line, column, **kwargs) # Python 2...
def _extract_function(self, line, column, new_name, until_line=None, until_column=None):
if until_line is None and until_column is None:
until_pos = None
else:
if until_line is None:
until_line = line
if until_column is None:
until_column = len(self._code_lines[until_line - 1])
until_pos = until_line, until_column
return extract_function(
self._inference_state, self.path, self._get_module_context(),
new_name, (line, column), until_pos
)
@_no_python2_support
def inline(self, line=None, column=None):
"""
Inlines a variable under the cursor. This is basically the opposite of
extracting a variable. For example with the cursor on bar::
foo = 3.1
bar = foo + 1
x = int(bar)
the code above will become::
foo = 3.1
x = int(foo + 1)
:raises: :exc:`.RefactoringError`
:rtype: :class:`.Refactoring`
"""
names = [d._name for d in self.get_references(line, column, include_builtins=True)]
return refactoring.inline(self._inference_state, names)
class Interpreter(Script):
"""
Jedi API for Python REPLs.
Jedi's 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.
Implements all of the methods that are present in :class:`.Script` as well.
In addition to completions that normal REPL completion does like
``str.upper``, Jedi also supports code completion based on static code
analysis. For example Jedi will complete ``str().upper``.
>>> from os.path import join
>>> namespace = locals()
>>> script = Interpreter('join("").up', [namespace])
>>> print(script.complete()[0].name)
upper
All keyword arguments are same as the arguments for :class:`.Script`.
:param str code: Code to parse.
:type namespaces: typing.List[dict]
:param namespaces: A list of namespace dictionaries such as the one
returned by :func:`globals` and :func:`locals`.
"""
_allow_descriptor_getattr_default = True
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`.
"""
def __init__(self, code, namespaces, **kwds):
try:
namespaces = [dict(n) for n in namespaces]
except Exception:
@@ -568,7 +838,7 @@ class Interpreter(Script):
if not isinstance(environment, InterpreterEnvironment):
raise TypeError("The environment needs to be an InterpreterEnvironment subclass.")
super(Interpreter, self).__init__(source, environment=environment,
super(Interpreter, self).__init__(code, environment=environment,
project=Project(os.getcwd()), **kwds)
self.namespaces = namespaces
self._inference_state.allow_descriptor_getattr = self._allow_descriptor_getattr_default
@@ -605,7 +875,8 @@ def names(source=None, path=None, encoding='utf-8', all_scopes=False,
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.
of modules. This can be useful for IDEs, to control which modules to load
on startup.
:param modules: different module names, list of string.
"""
@@ -621,7 +892,7 @@ def set_debug_function(func_cb=debug.print_to_stdout, warnings=True,
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.
:param func_cb: The callback function for debug messages.
"""
debug.debug_function = func_cb
debug.enable_warning = warnings

View File

@@ -1,7 +1,17 @@
"""
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.
There are a couple of classes documented in here:
- :class:`.BaseName` as an abstact base class for almost everything.
- :class:`.Name` used in a lot of places
- :class:`.Completion` for completions
- :class:`.BaseSignature` as a base class for signatures
- :class:`.Signature` for :meth:`.Script.get_signatures` only
- :class:`.ParamName` used for parameters of signatures
- :class:`.Refactoring` for refactorings
- :class:`.SyntaxError` for :meth:`.Script.get_syntax_errors` only
These classes are the much biggest part of the API, because they contain
the interesting information about all operations.
"""
import re
import sys
@@ -32,18 +42,21 @@ def defined_names(inference_state, context):
List sub-definitions (e.g., methods in class).
:type scope: Scope
:rtype: list of Definition
:rtype: list of Name
"""
filter = next(context.get_filters())
names = [name for name in filter.values()]
return [Definition(inference_state, n) for n in _sort_names_by_start_pos(names)]
return [Name(inference_state, n) for n in _sort_names_by_start_pos(names)]
def _values_to_definitions(values):
return [Definition(c.inference_state, c.name) for c in values]
return [Name(c.inference_state, c.name) for c in values]
class BaseDefinition(object):
class BaseName(object):
"""
The base class for all definitions, completions and signatures.
"""
_mapping = {
'posixpath': 'os.path',
'riscospath': 'os.path',
@@ -138,10 +151,10 @@ class BaseDefinition(object):
>>> defs = sorted(defs, key=lambda d: d.line)
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
[<Definition full_name='keyword', description='module keyword'>,
<Definition full_name='__main__.C', description='class C'>,
<Definition full_name='__main__.D', description='instance D'>,
<Definition full_name='__main__.f', description='def f'>]
[<Name full_name='keyword', description='module keyword'>,
<Name full_name='__main__.C', description='class C'>,
<Name full_name='__main__.D', description='instance D'>,
<Name full_name='__main__.f', description='def f'>]
Finally, here is what you can get from :attr:`type`:
@@ -176,7 +189,8 @@ class BaseDefinition(object):
@property
def module_name(self):
"""
The module name.
The module name, a bit similar to what ``__name__`` is in a random
Python module.
>>> from jedi import Script
>>> source = 'import json'
@@ -188,7 +202,9 @@ class BaseDefinition(object):
return self._get_module_context().py__name__()
def in_builtin_module(self):
"""Whether this is a builtin module."""
"""
Returns True, if this is a builtin module.
"""
value = self._get_module_context().get_value()
if isinstance(value, StubModuleValue):
return any(v.is_compiled() for v in value.non_stub_value_set)
@@ -265,7 +281,7 @@ class BaseDefinition(object):
@property
def description(self):
"""
A description of the :class:`.Definition` object, which is heavily used
A description of the :class:`.Name` object, which is heavily used
in testing. e.g. for ``isinstance`` it returns ``def isinstance``.
Example:
@@ -284,8 +300,8 @@ class BaseDefinition(object):
>>> defs = script.infer(column=3)
>>> defs = sorted(defs, key=lambda d: d.line)
>>> no_unicode_pprint(defs) # doctest: +NORMALIZE_WHITESPACE
[<Definition full_name='__main__.f', description='def f'>,
<Definition full_name='__main__.C', description='class C'>]
[<Name full_name='__main__.f', description='def f'>,
<Name full_name='__main__.C', description='class C'>]
>>> str(defs[0].description) # strip literals in python2
'def f'
>>> str(defs[1].description)
@@ -341,7 +357,7 @@ class BaseDefinition(object):
names = self._name.get_qualified_names(include_module_names=True)
if names is None:
return names
return None
names = list(names)
try:
@@ -352,12 +368,37 @@ class BaseDefinition(object):
return '.'.join(names)
def is_stub(self):
"""
Returns True if the current name is defined in a stub file.
"""
if not self._name.is_value_name:
return False
return self._name.get_root_context().is_stub()
def is_side_effect(self):
"""
Checks if a name is defined as ``self.foo = 3``. In case of self, this
function would return False, for foo it would return True.
"""
tree_name = self._name.tree_name
if tree_name is None:
return False
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
def goto(self, **kwargs):
"""
Like :meth:`.Script.goto` (also supports the same params), but does it
for the current name. This is typically useful if you are using
something like :meth:`.Script.get_names()`.
:param follow_imports: The goto call will follow imports.
:param follow_builtin_imports: If follow_imports is True will try to
look up names in builtins (i.e. compiled or extension modules).
:param only_stubs: Only return stubs for this goto call.
:param prefer_stubs: Prefer stubs to Python objects for this goto call.
:rtype: list of :class:`Name`
"""
with debug.increase_indent_cm('goto for %s' % self._name):
return self._goto(**kwargs)
@@ -383,10 +424,26 @@ class BaseDefinition(object):
only_stubs=only_stubs,
prefer_stubs=prefer_stubs,
)
return [self if n == self._name else Definition(self._inference_state, n)
return [self if n == self._name else Name(self._inference_state, n)
for n in names]
def infer(self, **kwargs): # Python 2...
"""
Like :meth:`.Script.infer`, it can be useful to understand which type
the current name has.
Return the actual 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 very, very slow.
:param only_stubs: Only return stubs for this goto call.
:param prefer_stubs: Prefer stubs to Python objects for this type
inference call.
:rtype: list of :class:`Name`
"""
with debug.increase_indent_cm('infer for %s' % self._name):
return self._infer(**kwargs)
@@ -406,18 +463,12 @@ class BaseDefinition(object):
prefer_stubs=prefer_stubs,
)
resulting_names = [c.name for c in values]
return [self if n == self._name else Definition(self._inference_state, n)
return [self if n == self._name else Name(self._inference_state, n)
for n in resulting_names]
@property
@memoize_method
def params(self):
"""
Deprecated! Will raise a warning soon. Use get_signatures()[...].params.
Raises an ``AttributeError`` if the definition is not callable.
Otherwise returns a list of `Definition` that represents the params.
"""
warnings.warn(
"Deprecated since version 0.16.0. Use get_signatures()[...].params",
DeprecationWarning,
@@ -427,7 +478,7 @@ class BaseDefinition(object):
# with overloading.
for signature in self._get_signatures():
return [
Definition(self._inference_state, n)
Name(self._inference_state, n)
for n in signature.get_param_names(resolve_stars=True)
]
@@ -438,6 +489,11 @@ class BaseDefinition(object):
raise AttributeError('There are no params defined on this.')
def parent(self):
"""
Returns the parent scope of this identifier.
:rtype: Name
"""
if not self._name.is_value_name:
return None
@@ -463,7 +519,7 @@ class BaseDefinition(object):
# Happens for comprehension contexts
context = context.parent_context
return Definition(self._inference_state, context.name)
return Name(self._inference_state, context.name)
def __repr__(self):
return "<%s %sname=%r, description=%r>" % (
@@ -505,12 +561,24 @@ class BaseDefinition(object):
return [sig for name in names for sig in name.infer().get_signatures()]
def get_signatures(self):
"""
Returns all potential signatures for a function or a class. Multiple
signatures are typical if you use Python stubs with ``@overload``.
:rtype: list of :class:`BaseSignature`
"""
return [
BaseSignature(self._inference_state, s)
for s in self._get_signatures()
]
def execute(self):
"""
Uses type inference to "execute" this identifier and returns the
executed objects.
:rtype: list of :class:`Name`
"""
return _values_to_definitions(self._name.infer().execute_with_values())
def get_type_hint(self):
@@ -520,13 +588,15 @@ class BaseDefinition(object):
This method might be quite slow, especially for functions. The problem
is finding executions for those functions to return something like
``Callable[[int, str], str]``.
:rtype: str
"""
return self._name.infer().get_type_hint()
class Completion(BaseDefinition):
class Completion(BaseName):
"""
`Completion` objects are returned from :meth:`api.Script.complete`. They
``Completion`` objects are returned from :meth:`.Script.complete`. They
provide additional information about a completion.
"""
def __init__(self, inference_state, name, stack, like_name_length,
@@ -564,15 +634,15 @@ class Completion(BaseDefinition):
isinstan# <-- Cursor is here
would return the string 'ce'. It also adds additional stuff, depending
on your `settings.py`.
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=`
completing ``foo(par`` would give a ``Completion`` which ``complete``
would be ``am=``.
"""
if self._is_fuzzy:
return None
@@ -581,7 +651,7 @@ class Completion(BaseDefinition):
@property
def name_with_symbols(self):
"""
Similar to :attr:`name`, but like :attr:`name` returns also the
Similar to :attr:`.name`, but like :attr:`.name` returns also the
symbols, for example assuming the following function definition::
def foo(param=0):
@@ -594,6 +664,9 @@ class Completion(BaseDefinition):
return self._complete(False)
def docstring(self, raw=False, fast=True):
"""
Documentated under :meth:`BaseName.docstring`.
"""
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.
@@ -629,6 +702,9 @@ class Completion(BaseDefinition):
@property
def type(self):
"""
Documentated under :meth:`BaseName.type`.
"""
# Purely a speed optimization.
if self._cached_name is not None:
return completion_cache.get_type(
@@ -642,45 +718,22 @@ class Completion(BaseDefinition):
def __repr__(self):
return '<%s: %s>' % (type(self).__name__, self._name.get_public_name())
@memoize_method
def follow_definition(self):
"""
Deprecated!
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.
class Name(BaseName):
"""
warnings.warn(
"Deprecated since version 0.14.0. Use .infer.",
DeprecationWarning,
stacklevel=2
)
return self.infer()
class Definition(BaseDefinition):
"""
*Definition* objects are returned from :meth:`api.Script.goto`
or :meth:`api.Script.infer`.
*Name* objects are returned from many different APIs including
:meth:`.Script.goto` or :meth:`.Script.infer`.
"""
def __init__(self, inference_state, definition):
super(Definition, self).__init__(inference_state, definition)
super(Name, self).__init__(inference_state, definition)
@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.
"""
warnings.warn(
"Deprecated since version 0.17.0. No replacement for now, maybe .full_name helps",
DeprecationWarning,
stacklevel=2
)
position = '' if self.in_builtin_module else '@%s' % self.line
return "%s:%s%s" % (self.module_name, self.description, position)
@@ -689,7 +742,7 @@ class Definition(BaseDefinition):
"""
List sub-definitions (e.g., methods in class).
:rtype: list of Definition
:rtype: list of :class:`Name`
"""
defs = self._name.infer()
return sorted(
@@ -707,16 +760,6 @@ class Definition(BaseDefinition):
else:
return self._name.tree_name.is_definition()
def is_side_effect(self):
"""
Checks if a name is defined as ``self.foo = 3``. In case of self, this
function would return False, for foo it would return True.
"""
tree_name = self._name.tree_name
if tree_name is None:
return False
return tree_name.is_definition() and tree_name.parent.type == 'trailer'
def __eq__(self, other):
return self._name.start_pos == other._name.start_pos \
and self.module_path == other.module_path \
@@ -730,11 +773,10 @@ class Definition(BaseDefinition):
return hash((self._name.start_pos, self.module_path, self.name, self._inference_state))
class BaseSignature(Definition):
class BaseSignature(Name):
"""
`BaseSignature` 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.
These signatures are returned by :meth:`BaseName.get_signatures`
calls.
"""
def __init__(self, inference_state, signature):
super(BaseSignature, self).__init__(inference_state, signature.name)
@@ -743,21 +785,28 @@ class BaseSignature(Definition):
@property
def params(self):
"""
:return list of ParamDefinition:
Returns definitions for all parameters that a signature defines.
This includes stuff like ``*args`` and ``**kwargs``.
:rtype: list of :class:`.ParamName`
"""
return [ParamDefinition(self._inference_state, n)
return [ParamName(self._inference_state, n)
for n in self._signature.get_param_names(resolve_stars=True)]
def to_string(self):
"""
Returns a text representation of the signature. This could for example
look like ``foo(bar, baz: int, **kwargs)``.
:return str
"""
return self._signature.to_string()
class Signature(BaseSignature):
"""
`Signature` objects is the return value of `Script.get_signatures`.
It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function with its params. Without `(` it would
return nothing.
A full signature object is the return value of
:meth:`.Script.get_signatures`.
"""
def __init__(self, inference_state, signature, call_details):
super(Signature, self).__init__(inference_state, signature)
@@ -767,8 +816,10 @@ class Signature(BaseSignature):
@property
def index(self):
"""
The Param index of the current call.
Returns the param index of the current cursor position.
Returns None if the index cannot be found in the curent call.
:rtype: int
"""
return self._call_details.calculate_index(
self._signature.get_param_names(resolve_stars=True)
@@ -777,8 +828,10 @@ class Signature(BaseSignature):
@property
def bracket_start(self):
"""
The line/column of the bracket that is responsible for the last
function call.
Returns a line/column tuple of the bracket that is responsible for the
last function call. The first line is 1 and the first column 0.
:rtype: int, int
"""
return self._call_details.bracket_leaf.start_pos
@@ -790,32 +843,38 @@ class Signature(BaseSignature):
)
class ParamDefinition(Definition):
class ParamName(Name):
def infer_default(self):
"""
:return list of Definition:
Returns default values like the ``1`` of ``def foo(x=1):``.
:rtype: list of :class:`.Name`
"""
return _values_to_definitions(self._name.infer_default())
def infer_annotation(self, **kwargs):
"""
:return list of Definition:
:param execute_annotation: If False, the values are not executed and
you get classes instead of instances.
:param execute_annotation: Default True; If False, values are not
executed and classes are returned instead of instances.
:rtype: list of :class:`.Name`
"""
return _values_to_definitions(self._name.infer_annotation(ignore_stars=True, **kwargs))
def to_string(self):
"""
Returns a simple representation of a param, like
``f: Callable[..., Any]``.
:rtype: :class:`str`
"""
return self._name.to_string()
@property
def kind(self):
"""
Returns an enum instance. Returns the same values as the builtin
:py:attr:`inspect.Parameter.kind`.
Returns an enum instance of :mod:`inspect`'s ``Parameter`` enum.
No support for Python < 3.4 anymore.
:rtype: :py:attr:`inspect.Parameter.kind`
"""
if sys.version_info < (3, 5):
raise NotImplementedError(

View File

@@ -19,8 +19,8 @@ from jedi.inference.base_value import ValueSet
from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names
from jedi.inference.context import get_global_filters
from jedi.inference.value import TreeInstance, ModuleValue
from jedi.inference.names import ParamNameWrapper
from jedi.inference.gradual.conversion import convert_values
from jedi.inference.names import ParamNameWrapper, SubModuleName
from jedi.inference.gradual.conversion import convert_values, convert_names
from jedi.parser_utils import cut_value_at_position
from jedi.plugins import plugin_manager
@@ -48,11 +48,7 @@ def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cac
string = name.string_name
if settings.case_insensitive_completion:
string = string.lower()
if fuzzy:
match = helpers.fuzzy_match(string, like_name)
else:
match = helpers.start_match(string, like_name)
if match:
if helpers.match(string, like_name, fuzzy=fuzzy):
new = classes.Completion(
inference_state,
name,
@@ -135,7 +131,7 @@ class Completion:
if string is not None and not prefixed_completions:
prefixed_completions = list(complete_file_name(
self._inference_state, self._module_context, start_leaf, string,
self._inference_state, self._module_context, start_leaf, quote, string,
self._like_name, self._signatures_callback,
self._code_lines, self._original_position,
self._fuzzy
@@ -361,80 +357,7 @@ class Completion:
def _complete_trailer_for_values(self, values):
user_context = get_user_context(self._module_context, self._position)
completion_names = []
for value in values:
for filter in value.get_filters(origin_scope=user_context.tree_node):
completion_names += filter.values()
if not value.is_stub() and isinstance(value, TreeInstance):
completion_names += self._complete_getattr(value)
python_values = convert_values(values)
for c in python_values:
if c not in values:
for filter in c.get_filters(origin_scope=user_context.tree_node):
completion_names += filter.values()
return completion_names
def _complete_getattr(self, instance):
"""
A heuristic to make completion for proxy objects work. This is not
intended to work in all cases. It works exactly in this case:
def __getattr__(self, name):
...
return getattr(any_object, name)
It is important that the return contains getattr directly, otherwise it
won't work anymore. It's really just a stupid heuristic. It will not
work if you write e.g. `return (getatr(o, name))`, because of the
additional parentheses. It will also not work if you move the getattr
to some other place that is not the return statement itself.
It is intentional that it doesn't work in all cases. Generally it's
really hard to do even this case (as you can see below). Most people
will write it like this anyway and the other ones, well they are just
out of luck I guess :) ~dave.
"""
names = (instance.get_function_slot_names(u'__getattr__')
or instance.get_function_slot_names(u'__getattribute__'))
functions = ValueSet.from_sets(
name.infer()
for name in names
)
for func in functions:
tree_node = func.tree_node
for return_stmt in tree_node.iter_return_stmts():
# Basically until the next comment we just try to find out if a
# return statement looks exactly like `return getattr(x, name)`.
if return_stmt.type != 'return_stmt':
continue
atom_expr = return_stmt.children[1]
if atom_expr.type != 'atom_expr':
continue
atom = atom_expr.children[0]
trailer = atom_expr.children[1]
if len(atom_expr.children) != 2 or atom.type != 'name' \
or atom.value != 'getattr':
continue
arglist = trailer.children[1]
if arglist.type != 'arglist' or len(arglist.children) < 3:
continue
context = func.as_context()
object_node = arglist.children[0]
# Make sure it's a param: foo in __getattr__(self, foo)
name_node = arglist.children[2]
name_list = context.goto(name_node, name_node.start_pos)
if not any(n.api_type == 'param' for n in name_list):
continue
# Now that we know that these are most probably completion
# objects, we just infer the object and return them as
# completions.
objects = context.infer_node(object_node)
return self._complete_trailer_for_values(objects)
return []
return complete_trailer(user_context, values)
def _get_importer_names(self, names, level=0, only_modules=True):
names = [n.value for n in names]
@@ -572,3 +495,123 @@ def _extract_string_while_in_string(leaf, position):
leaves.insert(0, leaf)
leaf = leaf.get_previous_leaf()
return None, None, None
def complete_trailer(user_context, values):
completion_names = []
for value in values:
for filter in value.get_filters(origin_scope=user_context.tree_node):
completion_names += filter.values()
if not value.is_stub() and isinstance(value, TreeInstance):
completion_names += _complete_getattr(user_context, value)
python_values = convert_values(values)
for c in python_values:
if c not in values:
for filter in c.get_filters(origin_scope=user_context.tree_node):
completion_names += filter.values()
return completion_names
def _complete_getattr(user_context, instance):
"""
A heuristic to make completion for proxy objects work. This is not
intended to work in all cases. It works exactly in this case:
def __getattr__(self, name):
...
return getattr(any_object, name)
It is important that the return contains getattr directly, otherwise it
won't work anymore. It's really just a stupid heuristic. It will not
work if you write e.g. `return (getatr(o, name))`, because of the
additional parentheses. It will also not work if you move the getattr
to some other place that is not the return statement itself.
It is intentional that it doesn't work in all cases. Generally it's
really hard to do even this case (as you can see below). Most people
will write it like this anyway and the other ones, well they are just
out of luck I guess :) ~dave.
"""
names = (instance.get_function_slot_names(u'__getattr__')
or instance.get_function_slot_names(u'__getattribute__'))
functions = ValueSet.from_sets(
name.infer()
for name in names
)
for func in functions:
tree_node = func.tree_node
for return_stmt in tree_node.iter_return_stmts():
# Basically until the next comment we just try to find out if a
# return statement looks exactly like `return getattr(x, name)`.
if return_stmt.type != 'return_stmt':
continue
atom_expr = return_stmt.children[1]
if atom_expr.type != 'atom_expr':
continue
atom = atom_expr.children[0]
trailer = atom_expr.children[1]
if len(atom_expr.children) != 2 or atom.type != 'name' \
or atom.value != 'getattr':
continue
arglist = trailer.children[1]
if arglist.type != 'arglist' or len(arglist.children) < 3:
continue
context = func.as_context()
object_node = arglist.children[0]
# Make sure it's a param: foo in __getattr__(self, foo)
name_node = arglist.children[2]
name_list = context.goto(name_node, name_node.start_pos)
if not any(n.api_type == 'param' for n in name_list):
continue
# Now that we know that these are most probably completion
# objects, we just infer the object and return them as
# completions.
objects = context.infer_node(object_node)
return complete_trailer(user_context, objects)
return []
def search_in_module(inference_state, module_context, names, wanted_names,
wanted_type, complete=False, fuzzy=False,
ignore_imports=False, convert=False):
for s in wanted_names[:-1]:
new_names = []
for n in names:
if s == n.string_name:
if n.tree_name is not None and n.api_type == 'module' \
and ignore_imports:
continue
new_names += complete_trailer(
module_context,
n.infer()
)
debug.dbg('dot lookup on search %s from %s', new_names, names[:10])
names = new_names
last_name = wanted_names[-1].lower()
for n in names:
string = n.string_name.lower()
if complete and helpers.match(string, last_name, fuzzy=fuzzy) \
or not complete and string == last_name:
if isinstance(n, SubModuleName):
names = [v.name for v in n.infer()]
else:
names = [n]
if convert:
names = convert_names(names)
for n2 in names:
if complete:
def_ = classes.Completion(
inference_state, n2,
stack=None,
like_name_length=len(last_name),
is_fuzzy=fuzzy,
)
else:
def_ = classes.Name(inference_state, n2)
if not wanted_type or wanted_type == def_.type:
yield def_

View File

@@ -17,7 +17,7 @@ import parso
_VersionInfo = namedtuple('VersionInfo', 'major minor micro')
_SUPPORTED_PYTHONS = ['3.8', '3.7', '3.6', '3.5', '3.4', '2.7']
_SUPPORTED_PYTHONS = ['3.8', '3.7', '3.6', '3.5', '2.7']
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
_CONDA_VAR = 'CONDA_PREFIX'
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
@@ -91,8 +91,8 @@ class Environment(_BaseEnvironment):
"""
self.version_info = _VersionInfo(*info[2])
"""
Like ``sys.version_info``. A tuple to show the current Environment's
Python version.
Like :data:`sys.version_info`: a tuple to show the current
Environment's Python version.
"""
# py2 sends bytes via pickle apparently?!
@@ -117,7 +117,7 @@ class Environment(_BaseEnvironment):
def get_sys_path(self):
"""
The sys path for this environment. Does not include potential
modifications like ``sys.path.append``.
modifications from e.g. appending to :data:`sys.path`.
:returns: list of str
"""
@@ -185,7 +185,7 @@ def get_default_environment():
makes it possible to use as many new Python features as possible when using
autocompletion and other functionality.
:returns: :class:`Environment`
:returns: :class:`.Environment`
"""
virtual_env = _get_virtual_env_from_var()
if virtual_env is not None:
@@ -272,7 +272,7 @@ def find_virtualenvs(paths=None, **kwargs):
CONDA_PREFIX will be checked to see if it contains a valid conda
environment.
:yields: :class:`Environment`
:yields: :class:`.Environment`
"""
def py27_comp(paths=None, safe=True, use_environment_vars=True):
if paths is None:
@@ -322,7 +322,7 @@ def find_system_environments():
The environments are sorted from latest to oldest Python version.
:yields: :class:`Environment`
:yields: :class:`.Environment`
"""
for version_string in _SUPPORTED_PYTHONS:
try:
@@ -339,7 +339,7 @@ def get_system_environment(version):
where X and Y are the major and minor versions of Python.
:raises: :exc:`.InvalidPythonEnvironment`
:returns: :class:`Environment`
:returns: :class:`.Environment`
"""
exe = which('python' + version)
if exe:
@@ -362,7 +362,7 @@ def create_environment(path, safe=True):
Virtualenv path or an executable path.
:raises: :exc:`.InvalidPythonEnvironment`
:returns: :class:`Environment`
:returns: :class:`.Environment`
"""
if os.path.isfile(path):
_assert_safe(path, safe)

View File

@@ -9,23 +9,30 @@ def parso_to_jedi_errors(grammar, module_node):
class SyntaxError(object):
"""
Syntax errors are generated by :meth:`.Script.get_syntax_errors`.
"""
def __init__(self, parso_error):
self._parso_error = parso_error
@property
def line(self):
"""The line where the error starts (starting with 1)."""
return self._parso_error.start_pos[0]
@property
def column(self):
"""The column where the error starts (starting with 0)."""
return self._parso_error.start_pos[1]
@property
def until_line(self):
"""The line where the error ends (starting with 1)."""
return self._parso_error.end_pos[0]
@property
def until_column(self):
"""The column where the error ends (starting with 0)."""
return self._parso_error.end_pos[1]
def __repr__(self):

View File

@@ -3,8 +3,29 @@ class _JediError(Exception):
class InternalError(_JediError):
pass
"""
This error might happen a subprocess is crashing. The reason for this is
usually broken C code in third party libraries. This is not a very common
thing and it is safe to use Jedi again. However using the same calls might
result in the same error again.
"""
class WrongVersion(_JediError):
pass
"""
This error is reserved for the future, shouldn't really be happening at the
moment.
"""
class RefactoringError(_JediError):
"""
Refactorings can fail for various reasons. So if you work with refactorings
like :meth:`.Script.rename`, :meth:`.Script.inline`,
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`, make
sure to catch these. The descriptions in the errors are ususally valuable
for end users.
A typical ``RefactoringError`` would tell the user that inlining is not
possible if no name is under the cursor.
"""

View File

@@ -3,7 +3,7 @@ import os
from jedi._compatibility import FileNotFoundError, force_unicode, scandir
from jedi.api import classes
from jedi.api.strings import StringName, get_quote_ending
from jedi.api.helpers import fuzzy_match, start_match
from jedi.api.helpers import match
from jedi.inference.helpers import get_str_or_none
@@ -11,7 +11,7 @@ class PathName(StringName):
api_type = u'path'
def complete_file_name(inference_state, module_context, start_leaf, string,
def complete_file_name(inference_state, module_context, start_leaf, quote, string,
like_name, signatures_callback, code_lines, position, fuzzy):
# First we want to find out what can actually be changed as a name.
like_name_length = len(os.path.basename(string))
@@ -42,15 +42,12 @@ def complete_file_name(inference_state, module_context, start_leaf, string,
# OSError: [Errno 36] File name too long: '...'
except (FileNotFoundError, OSError):
return
quote_ending = get_quote_ending(quote, code_lines, position)
for entry in listed:
name = entry.name
if fuzzy:
match = fuzzy_match(name, must_start_with)
else:
match = start_match(name, must_start_with)
if match:
if match(name, must_start_with, fuzzy=fuzzy):
if is_in_os_path_join or not entry.is_dir():
name += get_quote_ending(start_leaf.value, code_lines, position)
name += quote_ending
else:
name += os.path.sep

View File

@@ -4,6 +4,7 @@ Helpers for the API
import re
from collections import namedtuple
from textwrap import dedent
from itertools import chain
from functools import wraps
from parso.python.parser import Parser
@@ -15,24 +16,32 @@ from jedi.inference.syntax_tree import infer_atom
from jedi.inference.helpers import infer_call_of_leaf
from jedi.inference.compiled import get_string_value_set
from jedi.cache import signature_time_cache
from jedi.parser_utils import get_parent_scope
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
def start_match(string, like_name):
def _start_match(string, like_name):
return string.startswith(like_name)
def fuzzy_match(string, like_name):
def _fuzzy_match(string, like_name):
if len(like_name) <= 1:
return like_name in string
pos = string.find(like_name[0])
if pos >= 0:
return fuzzy_match(string[pos + 1:], like_name[1:])
return _fuzzy_match(string[pos + 1:], like_name[1:])
return False
def match(string, like_name, fuzzy=False):
if fuzzy:
return _fuzzy_match(string, like_name)
else:
return _start_match(string, like_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, x.name))
@@ -455,3 +464,37 @@ def validate_line_column(func):
column, line_len, line, line_string))
return func(self, line, column, *args, **kwargs)
return wrapper
def get_module_names(module, all_scopes, definitions=True, references=False):
"""
Returns a dictionary with name parts as keys and their call paths as
values.
"""
def def_ref_filter(name):
is_def = name.is_definition()
return definitions and is_def or references and not is_def
names = list(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.
def is_module_scope_name(name):
parent_scope = get_parent_scope(name)
# async functions have an extra wrapper. Strip it.
if parent_scope and parent_scope.type == 'async_stmt':
parent_scope = parent_scope.parent
return parent_scope in (module, None)
names = [n for n in names if is_module_scope_name(n)]
return filter(def_ref_filter, names)
def split_search_string(name):
type, _, dotted_names = name.rpartition(' ')
if type == 'def':
type = 'function'
return type, dotted_names.split('.')

View File

@@ -1,13 +1,31 @@
"""
Projects are a way to handle Python projects within Jedi. For simpler plugins
you might not want to deal with projects, but if you want to give the user more
flexibility to define sys paths and Python interpreters for a project,
:class:`.Project` is the perfect way to allow for that.
Projects can be saved to disk and loaded again, to allow project definitions to
be used across repositories.
"""
import os
import errno
import json
import sys
from jedi._compatibility import FileNotFoundError, PermissionError, IsADirectoryError
from jedi._compatibility import FileNotFoundError, PermissionError, \
IsADirectoryError
from jedi import debug
from jedi.api.environment import get_cached_default_environment, create_environment
from jedi.api.exceptions import WrongVersion
from jedi.api.completion import search_in_module
from jedi.api.helpers import split_search_string, get_module_names
from jedi._compatibility import force_unicode
from jedi.inference.imports import load_module_from_path, \
load_namespace_from_path, iter_module_names
from jedi.inference.sys_path import discover_buildout_paths
from jedi.inference.cache import inference_state_as_method_param_cache
from jedi.inference.references import recurse_find_python_folders_and_files, search_in_file_ios
from jedi.file_io import FolderIO
from jedi.common.utils import traverse_parents
_CONFIG_FOLDER = '.jedi'
@@ -16,6 +34,23 @@ _CONTAINS_POTENTIAL_PROJECT = 'setup.py', '.git', '.hg', 'requirements.txt', 'MA
_SERIALIZER_VERSION = 1
def _try_to_skip_duplicates(func):
def wrapper(*args, **kwargs):
found_tree_nodes = []
found_modules = []
for definition in func(*args, **kwargs):
tree_node = definition._name.tree_name
if tree_node is not None and tree_node in found_tree_nodes:
continue
if definition.type == 'module' and definition.module_path is not None:
if definition.module_path in found_modules:
continue
found_modules.append(definition.module_path)
yield definition
found_tree_nodes.append(tree_node)
return wrapper
def _remove_duplicates_from_path(path):
used = set()
for p in path:
@@ -30,6 +65,11 @@ def _force_unicode_list(lst):
class Project(object):
"""
Projects are a simple way to manage Python folders and define how Jedi does
import resolution. It is mostly used as a parameter to :class:`.Script`.
Additionally there are functions to search a whole project.
"""
_environment = None
@staticmethod
@@ -43,6 +83,9 @@ class Project(object):
@classmethod
def load(cls, path):
"""
Loads a project from a specific path. You should not provide the path
to ``.jedi/project.json``, but rather the path to the project folder.
:param path: The path of the directory you want to use as a project.
"""
with open(cls._get_json_path(path)) as f:
@@ -55,18 +98,36 @@ class Project(object):
"The Jedi version of this project seems newer than what we can handle."
)
def save(self):
"""
Saves the project configuration in the project in ``.jedi/project.json``.
"""
data = dict(self.__dict__)
data.pop('_environment', None)
data.pop('_django', None) # TODO make django setting public?
data = {k.lstrip('_'): v for k, v in data.items()}
# TODO when dropping Python 2 use pathlib.Path.mkdir(parents=True, exist_ok=True)
try:
os.makedirs(self._get_config_folder_path(self._path))
except OSError as e:
if e.errno != errno.EEXIST:
raise
with open(self._get_json_path(self._path), 'w') as f:
return json.dump((_SERIALIZER_VERSION, data), f)
def __init__(self, path, **kwargs):
"""
:param path: The base path for this project.
:param python_path: The Python executable path, typically the path of a
virtual environment.
:param load_unsafe_extensions: Loads extensions that are not in the
:param load_unsafe_extensions: Default False, Loads extensions that are not in the
sys path and in the local directories. With this option enabled,
this is potentially unsafe if you clone a git repository and
analyze it's code, because those compiled extensions will be
important and therefore have execution privileges.
:param sys_path: list of str. You can override the sys path if you
want. By default the ``sys.path.`` is generated from the
want. By default the ``sys.path.`` is generated by the
environment (virtualenvs, etc).
:param added_sys_path: list of str. Adds these paths at the end of the
sys path.
@@ -124,7 +185,7 @@ class Project(object):
# 2. Stopping immediately when above self._path
traversed = []
for parent_path in traverse_parents(inference_state.script_path):
if not parent_path.startswith(self._path):
if parent_path == self._path or not parent_path.startswith(self._path):
break
if not add_init_paths \
and os.path.isfile(os.path.join(parent_path, "__init__.py")):
@@ -142,21 +203,6 @@ class Project(object):
path = prefixed + sys_path + suffixed
return list(_force_unicode_list(_remove_duplicates_from_path(path)))
def save(self):
data = dict(self.__dict__)
data.pop('_environment', None)
data.pop('_django', None) # TODO make django setting public?
data = {k.lstrip('_'): v for k, v in data.items()}
# TODO when dropping Python 2 use pathlib.Path.mkdir(parents=True, exist_ok=True)
try:
os.makedirs(self._get_config_folder_path(self._path))
except OSError as e:
if e.errno != errno.EEXIST:
raise
with open(self._get_json_path(self._path), 'w') as f:
return json.dump((_SERIALIZER_VERSION, data), f)
def get_environment(self):
if self._environment is None:
if self._python_path is not None:
@@ -165,6 +211,136 @@ class Project(object):
self._environment = get_cached_default_environment()
return self._environment
def search(self, string, **kwargs):
"""
Searches a name in the whole project. If the project is very big,
at some point Jedi will stop searching. However it's also very much
recommended to not exhaust the generator. Just display the first ten
results to the user.
There are currently three different search patterns:
- ``foo`` to search for a definition foo in any file or a file called
``foo.py`` or ``foo.pyi``.
- ``foo.bar`` to search for the ``foo`` and then an attribute ``bar``
in it.
- ``class foo.bar.Bar`` or ``def foo.bar.baz`` to search for a specific
API type.
:param bool all_scopes: Default False; searches not only for
definitions on the top level of a module level, but also in
functions and classes.
:yields: :class:`.Name`
"""
return self._search(string, **kwargs)
def complete_search(self, string, **kwargs):
"""
Like :meth:`.Script.search`, but completes that string. An empty string
lists all definitions in a project, so be careful with that.
:param bool all_scopes: Default False; searches not only for
definitions on the top level of a module level, but also in
functions and classes.
:yields: :class:`.Completion`
"""
return self._search_func(string, complete=True, **kwargs)
def _search(self, string, all_scopes=False): # Python 2..
return self._search_func(string, all_scopes=all_scopes)
@_try_to_skip_duplicates
def _search_func(self, string, complete=False, all_scopes=False):
# Using a Script is they easiest way to get an empty module context.
from jedi import Script
s = Script('', project=self)
inference_state = s._inference_state
empty_module_context = s._get_module_context()
if inference_state.grammar.version_info < (3, 6) or sys.version_info < (3, 6):
raise NotImplementedError(
"No support for refactorings/search on Python 2/3.5"
)
debug.dbg('Search for string %s, complete=%s', string, complete)
wanted_type, wanted_names = split_search_string(string)
name = wanted_names[0]
stub_folder_name = name + '-stubs'
ios = recurse_find_python_folders_and_files(FolderIO(self._path))
file_ios = []
# 1. Search for modules in the current project
for folder_io, file_io in ios:
if file_io is None:
file_name = folder_io.get_base_name()
if file_name == name or file_name == stub_folder_name:
f = folder_io.get_file_io('__init__.py')
try:
m = load_module_from_path(inference_state, f).as_context()
except FileNotFoundError:
f = folder_io.get_file_io('__init__.pyi')
try:
m = load_module_from_path(inference_state, f).as_context()
except FileNotFoundError:
m = load_namespace_from_path(inference_state, folder_io).as_context()
else:
continue
else:
file_ios.append(file_io)
file_name = os.path.basename(file_io.path)
if file_name in (name + '.py', name + '.pyi'):
m = load_module_from_path(inference_state, file_io).as_context()
else:
continue
debug.dbg('Search of a specific module %s', m)
for x in search_in_module(
inference_state,
m,
names=[m.name],
wanted_type=wanted_type,
wanted_names=wanted_names,
complete=complete,
convert=True,
ignore_imports=True,
):
yield x # Python 2...
# 2. Search for identifiers in the project.
for module_context in search_in_file_ios(inference_state, file_ios, name):
names = get_module_names(module_context.tree_node, all_scopes=all_scopes)
names = [module_context.create_name(n) for n in names]
names = _remove_imports(names)
for x in search_in_module(
inference_state,
module_context,
names=names,
wanted_type=wanted_type,
wanted_names=wanted_names,
complete=complete,
ignore_imports=True,
):
yield x # Python 2...
# 3. Search for modules on sys.path
sys_path = [
p for p in self._get_sys_path(inference_state)
# Exclude folders that are handled by recursing of the Python
# folders.
if not p.startswith(self._path)
]
names = list(iter_module_names(inference_state, empty_module_context, sys_path))
for x in search_in_module(
inference_state,
empty_module_context,
names=names,
wanted_type=wanted_type,
wanted_names=wanted_names,
complete=complete,
convert=True,
):
yield x # Python 2...
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._path)
@@ -188,6 +364,15 @@ def _is_django_path(directory):
def get_default_project(path=None):
"""
If a project is not defined by the user, Jedi tries to define a project by
itself as well as possible. Jedi traverses folders until it finds one of
the following:
1. A ``.jedi/config.json``
2. One of the following files: ``setup.py``, ``.git``, ``.hg``,
``requirements.txt`` and ``MANIFEST.in``.
"""
if path is None:
path = os.getcwd()
@@ -225,3 +410,10 @@ def get_default_project(path=None):
curdir = path if os.path.isdir(path) else os.path.dirname(path)
return Project(curdir)
def _remove_imports(names):
return [
n for n in names
if n.tree_name is None or n.api_type != 'module'
]

View File

@@ -0,0 +1,225 @@
from os.path import dirname, basename, join, relpath
import os
import re
import difflib
from parso import split_lines
from jedi.api.exceptions import RefactoringError
EXPRESSION_PARTS = (
'or_test and_test not_test comparison '
'expr xor_expr and_expr shift_expr arith_expr term factor power atom_expr'
).split()
class ChangedFile(object):
def __init__(self, inference_state, from_path, to_path,
module_node, node_to_str_map):
self._inference_state = inference_state
self._from_path = from_path
self._to_path = to_path
self._module_node = module_node
self._node_to_str_map = node_to_str_map
def get_diff(self):
old_lines = split_lines(self._module_node.get_code(), keepends=True)
new_lines = split_lines(self.get_new_code(), keepends=True)
project_path = self._inference_state.project._path
diff = difflib.unified_diff(
old_lines, new_lines,
fromfile=relpath(self._from_path, project_path),
tofile=relpath(self._to_path, project_path),
)
# Apparently there's a space at the end of the diff - for whatever
# reason.
return ''.join(diff).rstrip(' ')
def get_new_code(self):
return self._inference_state.grammar.refactor(self._module_node, self._node_to_str_map)
def apply(self):
if self._from_path is None:
raise RefactoringError(
'Cannot apply a refactoring on a Script with path=None'
)
with open(self._from_path, 'w', newline='') as f:
f.write(self.get_new_code())
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._from_path)
class Refactoring(object):
def __init__(self, inference_state, file_to_node_changes, renames=()):
self._inference_state = inference_state
self._renames = renames
self._file_to_node_changes = file_to_node_changes
def get_changed_files(self):
"""
Returns a path to ``ChangedFile`` map.
"""
def calculate_to_path(p):
if p is None:
return p
for from_, to in renames:
if p.startswith(from_):
p = to + p[len(from_):]
return p
renames = self.get_renames()
return {
path: ChangedFile(
self._inference_state,
from_path=path,
to_path=calculate_to_path(path),
module_node=next(iter(map_)).get_root_node(),
node_to_str_map=map_
) for path, map_ in sorted(self._file_to_node_changes.items())
}
def get_renames(self):
"""
Files can be renamed in a refactoring.
Returns ``Iterable[Tuple[str, str]]``.
"""
return sorted(self._renames)
def get_diff(self):
text = ''
project_path = self._inference_state.project._path
for from_, to in self.get_renames():
text += 'rename from %s\nrename to %s\n' \
% (relpath(from_, project_path), relpath(to, project_path))
return text + ''.join(f.get_diff() for f in self.get_changed_files().values())
def apply(self):
"""
Applies the whole refactoring to the files, which includes renames.
"""
for f in self.get_changed_files().values():
f.apply()
for old, new in self.get_renames():
os.rename(old, new)
def _calculate_rename(path, new_name):
name = basename(path)
dir_ = dirname(path)
if name in ('__init__.py', '__init__.pyi'):
parent_dir = dirname(dir_)
return dir_, join(parent_dir, new_name)
ending = re.search(r'\.pyi?$', name).group(0)
return path, join(dir_, new_name + ending)
def rename(inference_state, definitions, new_name):
file_renames = set()
file_tree_name_map = {}
if not definitions:
raise RefactoringError("There is no name under the cursor")
for d in definitions:
tree_name = d._name.tree_name
if d.type == 'module' and tree_name is None:
file_renames.add(_calculate_rename(d.module_path, new_name))
else:
# This private access is ok in a way. It's not public to
# protect Jedi users from seeing it.
if tree_name is not None:
fmap = file_tree_name_map.setdefault(d.module_path, {})
fmap[tree_name] = tree_name.prefix + new_name
return Refactoring(inference_state, file_tree_name_map, file_renames)
def inline(inference_state, names):
if not names:
raise RefactoringError("There is no name under the cursor")
if any(n.api_type == 'module' for n in names):
raise RefactoringError("Cannot inline imports or modules")
if any(n.tree_name is None for n in names):
raise RefactoringError("Cannot inline builtins/extensions")
definitions = [n for n in names if n.tree_name.is_definition()]
if len(definitions) == 0:
raise RefactoringError("No definition found to inline")
if len(definitions) > 1:
raise RefactoringError("Cannot inline a name with multiple definitions")
tree_name = definitions[0].tree_name
expr_stmt = tree_name.get_definition()
if expr_stmt.type != 'expr_stmt':
type_ = dict(
funcdef='function',
classdef='class',
).get(expr_stmt.type, expr_stmt.type)
raise RefactoringError("Cannot inline a %s" % type_)
if len(expr_stmt.get_defined_names(include_setitem=True)) > 1:
raise RefactoringError("Cannot inline a statement with multiple definitions")
first_child = expr_stmt.children[1]
if first_child.type == 'annassign' and len(first_child.children) == 4:
first_child = first_child.children[2]
if first_child != '=':
if first_child.type == 'annassign':
raise RefactoringError(
'Cannot inline a statement that is defined by an annotation'
)
else:
raise RefactoringError(
'Cannot inline a statement with "%s"'
% first_child.get_code(include_prefix=False)
)
rhs = expr_stmt.get_rhs()
replace_code = rhs.get_code(include_prefix=False)
references = [n for n in names if not n.tree_name.is_definition()]
file_to_node_changes = {}
for name in references:
tree_name = name.tree_name
path = name.get_root_context().py__file__()
s = replace_code
if rhs.type == 'testlist_star_expr' \
or tree_name.parent.type in EXPRESSION_PARTS \
or tree_name.parent.type == 'trailer' \
and tree_name.parent.get_next_sibling() is not None:
s = '(' + replace_code + ')'
of_path = file_to_node_changes.setdefault(path, {})
n = tree_name
prefix = n.prefix
par = n.parent
if par.type == 'trailer' and par.children[0] == '.':
prefix = par.parent.children[0].prefix
n = par
for some_node in par.parent.children[:par.parent.children.index(par)]:
of_path[some_node] = ''
of_path[n] = prefix + s
path = definitions[0].get_root_context().py__file__()
changes = file_to_node_changes.setdefault(path, {})
changes[expr_stmt] = _remove_indent_of_prefix(expr_stmt.get_first_leaf().prefix)
next_leaf = expr_stmt.get_next_leaf()
# Most of the time we have to remove the newline at the end of the
# statement, but if there's a comment we might not need to.
if next_leaf.prefix.strip(' \t') == '' \
and (next_leaf.type == 'newline' or next_leaf == ';'):
changes[next_leaf] = ''
return Refactoring(inference_state, file_to_node_changes)
def _remove_indent_of_prefix(prefix):
r"""
Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
"""
return ''.join(split_lines(prefix, keepends=True)[:-1])

View File

@@ -0,0 +1,386 @@
from textwrap import dedent
from parso import split_lines
from jedi import debug
from jedi.api.exceptions import RefactoringError
from jedi.api.refactoring import Refactoring, EXPRESSION_PARTS
from jedi.common.utils import indent_block
from jedi.parser_utils import function_is_classmethod, function_is_staticmethod
_EXTRACT_USE_PARENT = EXPRESSION_PARTS + ['trailer']
_DEFINITION_SCOPES = ('suite', 'file_input')
_VARIABLE_EXCTRACTABLE = EXPRESSION_PARTS + \
('atom testlist_star_expr testlist test lambdef lambdef_nocond '
'keyword name number string fstring').split()
def extract_variable(inference_state, path, module_node, name, pos, until_pos):
nodes = _find_nodes(module_node, pos, until_pos)
debug.dbg('Extracting nodes: %s', nodes)
is_expression, message = _is_expression_with_error(nodes)
if not is_expression:
raise RefactoringError(message)
generated_code = name + ' = ' + _expression_nodes_to_string(nodes)
file_to_node_changes = {path: _replace(nodes, name, generated_code, pos)}
return Refactoring(inference_state, file_to_node_changes)
def _is_expression_with_error(nodes):
"""
Returns a tuple (is_expression, error_string).
"""
if any(node.type == 'name' and node.is_definition() for node in nodes):
return False, 'Cannot extract a name that defines something'
if nodes[0].type not in _VARIABLE_EXCTRACTABLE:
return False, 'Cannot extract a "%s"' % nodes[0].type
return True, ''
def _find_nodes(module_node, pos, until_pos):
"""
Looks up a module and tries to find the appropriate amount of nodes that
are in there.
"""
start_node = module_node.get_leaf_for_position(pos, include_prefixes=True)
if until_pos is None:
if start_node.type == 'operator':
next_leaf = start_node.get_next_leaf()
if next_leaf is not None and next_leaf.start_pos == pos:
start_node = next_leaf
if _is_not_extractable_syntax(start_node):
start_node = start_node.parent
while start_node.parent.type in _EXTRACT_USE_PARENT:
start_node = start_node.parent
nodes = [start_node]
else:
# Get the next leaf if we are at the end of a leaf
if start_node.end_pos == pos:
next_leaf = start_node.get_next_leaf()
if next_leaf is not None:
start_node = next_leaf
# Some syntax is not exactable, just use its parent
if _is_not_extractable_syntax(start_node):
start_node = start_node.parent
# Find the end
end_leaf = module_node.get_leaf_for_position(until_pos, include_prefixes=True)
if end_leaf.start_pos > until_pos:
end_leaf = end_leaf.get_previous_leaf()
if end_leaf is None:
raise RefactoringError('Cannot extract anything from that')
parent_node = start_node
while parent_node.end_pos < end_leaf.end_pos:
parent_node = parent_node.parent
nodes = _remove_unwanted_expression_nodes(parent_node, pos, until_pos)
# If the user marks just a return statement, we return the expression
# instead of the whole statement, because the user obviously wants to
# extract that part.
if len(nodes) == 1 and start_node.type in ('return_stmt', 'yield_expr'):
return [nodes[0].children[1]]
return nodes
def _replace(nodes, expression_replacement, extracted, pos,
insert_before_leaf=None, remaining_prefix=None):
# Now try to replace the nodes found with a variable and move the code
# before the current statement.
definition = _get_parent_definition(nodes[0])
if insert_before_leaf is None:
insert_before_leaf = definition.get_first_leaf()
first_node_leaf = nodes[0].get_first_leaf()
lines = split_lines(insert_before_leaf.prefix, keepends=True)
if first_node_leaf is insert_before_leaf:
if remaining_prefix is not None:
# The remaining prefix has already been calculated.
lines[:-1] = remaining_prefix
lines[-1:-1] = [indent_block(extracted, lines[-1]) + '\n']
extracted_prefix = ''.join(lines)
replacement_dct = {}
if first_node_leaf is insert_before_leaf:
replacement_dct[nodes[0]] = extracted_prefix + expression_replacement
else:
if remaining_prefix is None:
p = first_node_leaf.prefix
else:
p = remaining_prefix + _get_indentation(nodes[0])
replacement_dct[nodes[0]] = p + expression_replacement
replacement_dct[insert_before_leaf] = extracted_prefix + insert_before_leaf.value
for node in nodes[1:]:
replacement_dct[node] = ''
return replacement_dct
def _expression_nodes_to_string(nodes):
return ''.join(n.get_code(include_prefix=i != 0) for i, n in enumerate(nodes))
def _suite_nodes_to_string(nodes, pos):
n = nodes[0]
prefix, part_of_code = _split_prefix_at(n.get_first_leaf(), pos[0] - 1)
code = part_of_code + n.get_code(include_prefix=False) \
+ ''.join(n.get_code() for n in nodes[1:])
return prefix, code
def _split_prefix_at(leaf, until_line):
"""
Returns a tuple of the leaf's prefix, split at the until_line
position.
"""
# second means the second returned part
second_line_count = leaf.start_pos[0] - until_line
lines = split_lines(leaf.prefix, keepends=True)
return ''.join(lines[:-second_line_count]), ''.join(lines[-second_line_count:])
def _get_indentation(node):
return split_lines(node.get_first_leaf().prefix)[-1]
def _get_parent_definition(node):
"""
Returns the statement where a node is defined.
"""
while node is not None:
if node.parent.type in _DEFINITION_SCOPES:
return node
node = node.parent
raise NotImplementedError('We should never even get here')
def _remove_unwanted_expression_nodes(parent_node, pos, until_pos):
"""
This function makes it so for `1 * 2 + 3` you can extract `2 + 3`, even
though it is not part of the expression.
"""
typ = parent_node.type
is_suite_part = typ in ('suite', 'file_input')
if typ in EXPRESSION_PARTS or is_suite_part:
nodes = parent_node.children
for i, n in enumerate(nodes):
if n.end_pos > pos:
start_index = i
if n.type == 'operator':
start_index -= 1
break
for i, n in reversed(list(enumerate(nodes))):
if n.start_pos < until_pos:
end_index = i
if n.type == 'operator':
end_index += 1
# Something like `not foo or bar` should not be cut after not
for n in nodes[i:]:
if _is_not_extractable_syntax(n):
end_index += 1
else:
break
break
nodes = nodes[start_index:end_index + 1]
if not is_suite_part:
nodes[0:1] = _remove_unwanted_expression_nodes(nodes[0], pos, until_pos)
nodes[-1:] = _remove_unwanted_expression_nodes(nodes[-1], pos, until_pos)
return nodes
return [parent_node]
def _is_not_extractable_syntax(node):
return node.type == 'operator' \
or node.type == 'keyword' and node.value not in ('None', 'True', 'False')
def extract_function(inference_state, path, module_context, name, pos, until_pos):
nodes = _find_nodes(module_context.tree_node, pos, until_pos)
assert len(nodes)
is_expression, _ = _is_expression_with_error(nodes)
context = module_context.create_context(nodes[0])
is_bound_method = context.is_bound_method()
params, return_variables = list(_find_inputs_and_outputs(module_context, context, nodes))
# Find variables
# Is a class method / method
if context.is_module():
insert_before_leaf = None # Leaf will be determined later
else:
node = _get_code_insertion_node(context.tree_node, is_bound_method)
insert_before_leaf = node.get_first_leaf()
if is_expression:
code_block = 'return ' + _expression_nodes_to_string(nodes) + '\n'
remaining_prefix = None
has_ending_return_stmt = False
else:
has_ending_return_stmt = _is_node_ending_return_stmt(nodes[-1])
if not has_ending_return_stmt:
# Find the actually used variables (of the defined ones). If none are
# used (e.g. if the range covers the whole function), return the last
# defined variable.
return_variables = list(_find_needed_output_variables(
context,
nodes[0].parent,
nodes[-1].end_pos,
return_variables
)) or [return_variables[-1]] if return_variables else []
remaining_prefix, code_block = _suite_nodes_to_string(nodes, pos)
after_leaf = nodes[-1].get_next_leaf()
first, second = _split_prefix_at(after_leaf, until_pos[0])
code_block += first
code_block = dedent(code_block)
if not has_ending_return_stmt:
output_var_str = ', '.join(return_variables)
code_block += 'return ' + output_var_str + '\n'
# Check if we have to raise RefactoringError
_check_for_non_extractables(nodes[:-1] if has_ending_return_stmt else nodes)
decorator = ''
self_param = None
if is_bound_method:
if not function_is_staticmethod(context.tree_node):
function_param_names = context.get_value().get_param_names()
if len(function_param_names):
self_param = function_param_names[0].string_name
params = [p for p in params if p != self_param]
if function_is_classmethod(context.tree_node):
decorator = '@classmethod\n'
else:
code_block += '\n'
function_code = '%sdef %s(%s):\n%s' % (
decorator,
name,
', '.join(params if self_param is None else [self_param] + params),
indent_block(code_block)
)
function_call = '%s(%s)' % (
('' if self_param is None else self_param + '.') + name,
', '.join(params)
)
if is_expression:
replacement = function_call
else:
if has_ending_return_stmt:
replacement = 'return ' + function_call + '\n'
else:
replacement = output_var_str + ' = ' + function_call + '\n'
replacement_dct = _replace(nodes, replacement, function_code, pos,
insert_before_leaf, remaining_prefix)
if not is_expression:
replacement_dct[after_leaf] = second + after_leaf.value
file_to_node_changes = {path: replacement_dct}
return Refactoring(inference_state, file_to_node_changes)
def _check_for_non_extractables(nodes):
for n in nodes:
try:
children = n.children
except AttributeError:
if n.value == 'return':
raise RefactoringError(
'Can only extract return statements if they are at the end.')
if n.value == 'yield':
raise RefactoringError('Cannot extract yield statements.')
else:
_check_for_non_extractables(children)
def _is_name_input(module_context, names, first, last):
for name in names:
if name.api_type == 'param' or not name.parent_context.is_module():
if name.get_root_context() is not module_context:
return True
if name.start_pos is None or not (first <= name.start_pos < last):
return True
return False
def _find_inputs_and_outputs(module_context, context, nodes):
first = nodes[0].start_pos
last = nodes[-1].end_pos
inputs = []
outputs = []
for name in _find_non_global_names(nodes):
if name.is_definition():
if name not in outputs:
outputs.append(name.value)
else:
if name.value not in inputs:
name_definitions = context.goto(name, name.start_pos)
if not name_definitions \
or _is_name_input(module_context, name_definitions, first, last):
inputs.append(name.value)
# Check if outputs are really needed:
return inputs, outputs
def _find_non_global_names(nodes):
for node in nodes:
try:
children = node.children
except AttributeError:
if node.type == 'name':
yield node
else:
# We only want to check foo in foo.bar
if node.type == 'trailer' and node.children[0] == '.':
continue
for x in _find_non_global_names(children): # Python 2...
yield x
def _get_code_insertion_node(node, is_bound_method):
if not is_bound_method or function_is_staticmethod(node):
while node.parent.type != 'file_input':
node = node.parent
while node.parent.type in ('async_funcdef', 'decorated', 'async_stmt'):
node = node.parent
return node
def _find_needed_output_variables(context, search_node, at_least_pos, return_variables):
"""
Searches everything after at_least_pos in a node and checks if any of the
return_variables are used in there and returns those.
"""
for node in search_node.children:
if node.start_pos < at_least_pos:
continue
return_variables = set(return_variables)
for name in _find_non_global_names([node]):
if not name.is_definition() and name.value in return_variables:
return_variables.remove(name.value)
yield name.value
def _is_node_ending_return_stmt(node):
t = node.type
if t == 'simple_stmt':
return _is_node_ending_return_stmt(node.children[0])
return t == 'return_stmt'

View File

@@ -93,17 +93,16 @@ def _get_string_prefix_and_quote(string):
return match.group(1), match.group(2)
def _get_string_quote(string):
return _get_string_prefix_and_quote(string)[1]
def _matches_quote_at_position(code_lines, quote, position):
string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
return string == quote
def get_quote_ending(string, code_lines, position, invert_result=False):
quote = _get_string_quote(string)
_, quote = _get_string_prefix_and_quote(string)
if quote is None:
return ''
# Add a quote only if it's not already there.
if _matches_quote_at_position(code_lines, quote, position) != invert_result:
return ''

View File

@@ -24,3 +24,13 @@ def monkeypatch(obj, attribute_name, new_value):
yield
finally:
setattr(obj, attribute_name, old_value)
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

View File

@@ -7,6 +7,9 @@ class AbstractFolderIO(object):
def __init__(self, path):
self.path = path
def get_base_name(self):
raise NotImplementedError
def list(self):
raise NotImplementedError

View File

@@ -507,22 +507,6 @@ class DirectObjectAccess(object):
obj = self._obj
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
try:
return inspect.signature(obj)
except (RuntimeError, TypeError):

View File

@@ -1,10 +1,13 @@
from __future__ import print_function
import sys
import os
import re
import inspect
from jedi._compatibility import find_module, cast_path, force_unicode, \
iter_modules, all_suffixes
all_suffixes, scandir
from jedi.inference.compiled import access
from jedi import debug
from jedi import parser_utils
@@ -40,13 +43,6 @@ def get_module_info(inference_state, sys_path=None, full_name=None, **kwargs):
sys.path = temp
def list_module_names(inference_state, search_path):
return [
force_unicode(name)
for module_loader, name, is_pkg in iter_modules(search_path)
]
def get_builtin_module_names(inference_state):
return list(map(force_unicode, sys.builtin_module_names))
@@ -84,3 +80,37 @@ def _get_init_path(directory_path):
def safe_literal_eval(inference_state, value):
return parser_utils.safe_literal_eval(value)
def iter_module_names(*args, **kwargs):
return list(_iter_module_names(*args, **kwargs))
return sorted(set(_iter_module_names(*args, **kwargs)))
def _iter_module_names(inference_state, paths):
# Python modules/packages
for path in paths:
try:
dirs = scandir(path)
except OSError:
# The file might not exist or reading it might lead to an error.
debug.warning("Not possible to list directory: %s", path)
continue
for dir_entry in dirs:
name = dir_entry.name
# First Namespaces then modules/stubs
if dir_entry.is_dir():
# pycache is obviously not an interestin namespace. Also the
# name must be a valid identifier.
# TODO use str.isidentifier, once Python 2 is removed
if name != '__pycache__' and not re.search(r'\W|^\d', name):
yield name
else:
if name.endswith('.pyi'): # Stub files
modname = name[:-4]
else:
modname = inspect.getmodulename(name)
if modname and '.' not in modname:
if modname != '__init__':
yield modname

View File

@@ -21,7 +21,7 @@ from jedi.inference.context import CompiledContext, CompiledModuleContext
class CheckAttribute(object):
"""Raises an AttributeError if the attribute X isn't available."""
"""Raises :exc:`AttributeError` if the attribute X is not available."""
def __init__(self, check_name=None):
# Remove the py in front of e.g. py__call__.
self.check_name = check_name

View File

@@ -129,6 +129,9 @@ class AbstractContext(object):
def is_compiled(self):
return False
def is_bound_method(self):
return False
@abstractmethod
def py__name__(self):
raise NotImplementedError
@@ -190,6 +193,9 @@ class ValueContext(AbstractContext):
def is_compiled(self):
return self._value.is_compiled()
def is_bound_method(self):
return self._value.is_bound_method()
def py__name__(self):
return self._value.py__name__()
@@ -347,6 +353,10 @@ class NamespaceContext(TreeContextMixin, ValueContext):
def get_value(self):
return self._value
@property
def string_names(self):
return self._value.string_names
def py__file__(self):
return self._value.py__file__()

View File

@@ -1,7 +1,7 @@
"""
Docstrings are another source of information for functions and classes.
:mod:`jedi.inference.dynamic` tries to find all executions of functions, while
the docstring parsing is much easier. There are three different types of
:mod:`jedi.inference.dynamic_params` 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>`_
@@ -23,7 +23,7 @@ from parso import parse, ParserSyntaxError
from jedi._compatibility import u
from jedi import debug
from jedi.inference.utils import indent_block
from jedi.common.utils import indent_block
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.base_value import iterator_to_value_set, ValueSet, \
NO_VALUES

View File

@@ -0,0 +1,4 @@
"""
It is unfortunately not well documented how stubs and annotations work in Jedi.
If somebody needs an introduction, please let me know.
"""

View File

@@ -300,6 +300,9 @@ class _PseudoTreeNameClass(Value):
def name(self):
return ValueName(self, self._tree_name)
def get_qualified_names(self):
return (self._tree_name.value,)
def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._tree_name.value)

View File

@@ -3,6 +3,7 @@ from jedi.inference.base_value import ValueSet, \
NO_VALUES
from jedi.inference.utils import to_list
from jedi.inference.gradual.stub_value import StubModuleValue
from jedi.inference.gradual.typeshed import try_to_load_stub_cached
def _stub_to_python_value_set(stub_value, ignore_compiled=False):
@@ -87,8 +88,7 @@ def _try_stub_to_python_names(names, prefer_stub_to_compiled=False):
def _load_stub_module(module):
if module.is_stub():
return module
from jedi.inference.gradual.typeshed import _try_to_load_stub_cached
return _try_to_load_stub_cached(
return try_to_load_stub_cached(
module.inference_state,
import_names=module.string_names,
python_value_set=ValueSet([module]),

View File

@@ -2,6 +2,7 @@ import os
import re
from functools import wraps
from jedi import settings
from jedi.file_io import FileIO
from jedi._compatibility import FileNotFoundError, cast_path
from jedi.parser_utils import get_cached_code_lines
@@ -119,7 +120,7 @@ def import_module_decorator(func):
if not prefer_stubs:
return python_value_set
stub = _try_to_load_stub_cached(inference_state, import_names, python_value_set,
stub = try_to_load_stub_cached(inference_state, import_names, python_value_set,
parent_module_value, sys_path)
if stub is not None:
return ValueSet([stub])
@@ -128,7 +129,7 @@ def import_module_decorator(func):
return wrapper
def _try_to_load_stub_cached(inference_state, import_names, *args, **kwargs):
def try_to_load_stub_cached(inference_state, import_names, *args, **kwargs):
if import_names is None:
return None
@@ -155,7 +156,7 @@ def _try_to_load_stub(inference_state, import_names, python_value_set,
"""
if parent_module_value is None and len(import_names) > 1:
try:
parent_module_value = _try_to_load_stub_cached(
parent_module_value = try_to_load_stub_cached(
inference_state, import_names[:-1], NO_VALUES,
parent_module_value=None, sys_path=sys_path)
except KeyError:
@@ -258,11 +259,7 @@ def _load_from_typeshed(inference_state, python_value_set, parent_module_value,
def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, import_names):
try:
stub_module_node = inference_state.parse(
file_io=file_io,
cache=True,
use_latest_grammar=True
)
stub_module_node = parse_stub_module(inference_state, file_io)
except (OSError, IOError): # IOError is Python 2 only
# The file that you're looking for doesn't exist (anymore).
return None
@@ -273,6 +270,16 @@ def _try_to_load_stub_from_file(inference_state, python_value_set, file_io, impo
)
def parse_stub_module(inference_state, file_io):
return inference_state.parse(
file_io=file_io,
cache=True,
diff_cache=settings.fast_parser,
cache_path=settings.cache_directory,
use_latest_grammar=True
)
def create_stub_module(inference_state, python_value_set, stub_module_node, file_io, import_names):
if import_names == ('typing',):
module_cls = TypingModuleWrapper

View File

@@ -8,7 +8,6 @@ from contextlib import contextmanager
from parso.python import tree
from jedi._compatibility import unicode
from jedi.parser_utils import get_parent_scope
def is_stdlib_path(path):
@@ -122,29 +121,6 @@ def get_names_of_node(node):
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 = list(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.
def is_module_scope_name(name):
parent_scope = get_parent_scope(name)
# async functions have an extra wrapper. Strip it.
if parent_scope and parent_scope.type == 'async_stmt':
parent_scope = parent_scope.parent
return parent_scope in (module, None)
names = [n for n in names if is_module_scope_name(n)]
return names
def is_string(value):
if value.inference_state.environment.version_info.major == 2:
str_classes = (unicode, bytes)

View File

@@ -16,9 +16,10 @@ import os
from parso.python import tree
from parso.tree import search_ancestor
from jedi._compatibility import ImplicitNSInfo, force_unicode
from jedi._compatibility import ImplicitNSInfo, force_unicode, FileNotFoundError
from jedi import debug
from jedi import settings
from jedi.file_io import FolderIO
from jedi.parser_utils import get_cached_code_lines
from jedi.inference import sys_path
from jedi.inference import helpers
@@ -28,8 +29,8 @@ from jedi.inference.utils import unite
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import ImportName, SubModuleName
from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference.gradual.typeshed import import_module_decorator
from jedi.inference.value.module import iter_module_names
from jedi.inference.gradual.typeshed import import_module_decorator, \
create_stub_module, parse_stub_module
from jedi.plugins import plugin_manager
@@ -265,24 +266,15 @@ class Importer(object):
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 += [
ImportName(self._module_context, name)
for name in self._inference_state.compiled_subprocess.get_builtin_module_names()
]
if search_path is None:
search_path = self._sys_path_with_modifications(is_completion=True)
for name in iter_module_names(self._inference_state, search_path):
if in_module is None:
n = ImportName(self._module_context, name)
sys_path = self._sys_path_with_modifications(is_completion=True)
else:
n = SubModuleName(in_module.as_context(), name)
names.append(n)
return names
sys_path = search_path
return list(iter_module_names(
self._inference_state, self._module_context, sys_path,
module_cls=ImportName if in_module is None else SubModuleName,
add_builtin_modules=search_path is None and in_module is None,
))
def completion_names(self, inference_state, only_modules=False):
"""
@@ -441,7 +433,7 @@ def _load_python_module(inference_state, file_io,
file_io=file_io,
cache=True,
diff_cache=settings.fast_parser,
cache_path=settings.cache_directory
cache_path=settings.cache_directory,
)
from jedi.inference.value import ModuleValue
@@ -472,25 +464,43 @@ def _load_builtin_module(inference_state, import_names=None, sys_path=None):
return module
def load_module_from_path(inference_state, file_io, base_names=None):
def load_module_from_path(inference_state, file_io, import_names=None, is_package=None):
"""
This should pretty much only be used for get_modules_containing_name. It's
here to ensure that a random path is still properly loaded into the Jedi
module structure.
"""
path = file_io.path
if base_names:
module_name = os.path.basename(path)
module_name = sys_path.remove_python_path_suffix(module_name)
is_package = module_name == '__init__'
if is_package:
import_names = base_names
else:
import_names = base_names + (module_name,)
else:
if import_names is None:
e_sys_path = inference_state.get_sys_path()
import_names, is_package = sys_path.transform_path_to_dotted(e_sys_path, path)
else:
assert isinstance(is_package, bool)
is_stub = file_io.path.endswith('.pyi')
if is_stub:
folder_io = file_io.get_parent_folder()
if folder_io.path.endswith('-stubs'):
folder_io = FolderIO(folder_io.path[:-6])
if file_io.path.endswith('__init__.pyi'):
python_file_io = folder_io.get_file_io('__init__.py')
else:
python_file_io = folder_io.get_file_io(import_names[-1] + '.py')
try:
v = load_module_from_path(
inference_state, python_file_io,
import_names, is_package=is_package
)
values = ValueSet([v])
except FileNotFoundError:
values = NO_VALUES
return create_stub_module(
inference_state, values, parse_stub_module(inference_state, file_io),
file_io, import_names
)
else:
module = _load_python_module(
inference_state, file_io,
import_names=import_names,
@@ -500,6 +510,15 @@ def load_module_from_path(inference_state, file_io, base_names=None):
return module
def load_namespace_from_path(inference_state, folder_io):
import_names, is_package = sys_path.transform_path_to_dotted(
inference_state.get_sys_path(),
folder_io.path
)
from jedi.inference.value.namespace import ImplicitNamespaceValue
return ImplicitNamespaceValue(inference_state, import_names, [folder_io.path])
def follow_error_node_imports_if_possible(context, name):
error_node = tree.search_ancestor(name, 'error_node')
if error_node is not None:
@@ -527,3 +546,18 @@ def follow_error_node_imports_if_possible(context, name):
return Importer(
context.inference_state, names, context.get_root_context(), level).follow()
return None
def iter_module_names(inference_state, module_context, search_path,
module_cls=ImportName, add_builtin_modules=True):
"""
Get the names of all modules in the search_path. This means file names
and not names defined in the files.
"""
# add builtin module names
if add_builtin_modules:
for name in inference_state.compiled_subprocess.get_builtin_module_names():
yield module_cls(module_context, name)
for name in inference_state.compiled_subprocess.iter_module_names(search_path):
yield module_cls(module_context, name)

View File

@@ -3,8 +3,8 @@ 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.inference.cache` this module also makes |jedi| not
thread-safe. Why? ``execution_recursion_decorator`` uses class variables to
Next to the internal ``jedi.inference.cache`` this module also makes |jedi| not
thread-safe, because ``execution_recursion_decorator`` uses class variables to
count the function calls.
.. _settings-recursion:
@@ -34,7 +34,7 @@ from jedi.inference.base_value import NO_VALUES
recursion_limit = 15
"""
Like ``sys.getrecursionlimit()``, just for |jedi|.
Like :func:`sys.getrecursionlimit()`, just for |jedi|.
"""
total_function_execution_limit = 200
"""

View File

@@ -3,6 +3,7 @@ import re
from parso import python_bytes_to_unicode
from jedi.debug import dbg
from jedi.file_io import KnownContentFileIO
from jedi.inference.imports import SubModuleName, load_module_from_path
from jedi.inference.filters import ParserTreeFilter
@@ -192,14 +193,15 @@ def gitignored_lines(folder_io, file_io):
return ignored_paths, ignored_names
def _recurse_find_python_files(folder_io, except_paths):
def recurse_find_python_folders_and_files(folder_io, except_paths=()):
except_paths = set(except_paths)
for root_folder_io, folder_ios, file_ios in folder_io.walk():
# Delete folders that we don't want to iterate over.
for file_io in file_ios:
path = file_io.path
if path.endswith('.py') or path.endswith('.pyi'):
if path not in except_paths:
yield file_io
yield None, file_io
if path.endswith('.gitignore'):
ignored_paths, ignored_names = \
@@ -212,6 +214,14 @@ def _recurse_find_python_files(folder_io, except_paths):
if folder_io.path not in except_paths
and folder_io.get_base_name() not in _IGNORE_FOLDERS
]
for folder_io in folder_ios:
yield folder_io, None
def recurse_find_python_files(folder_io, except_paths=()):
for folder_io, file_io in recurse_find_python_folders_and_files(folder_io, except_paths):
if file_io is not None:
yield file_io
def _find_python_files_in_sys_path(inference_state, module_contexts):
@@ -228,7 +238,7 @@ def _find_python_files_in_sys_path(inference_state, module_contexts):
path = folder_io.path
if not any(path.startswith(p) for p in sys_path) or path in except_paths:
break
for file_io in _recurse_find_python_files(folder_io, except_paths):
for file_io in recurse_find_python_files(folder_io, except_paths):
if file_io.path not in yielded_paths:
yield file_io
except_paths.add(path)
@@ -254,19 +264,28 @@ def get_module_contexts_containing_name(inference_state, module_contexts, name,
if len(name) <= 2:
return
file_io_iterator = _find_python_files_in_sys_path(inference_state, module_contexts)
for x in search_in_file_ios(inference_state, file_io_iterator, name,
limit_reduction=limit_reduction):
yield x # Python 2...
def search_in_file_ios(inference_state, file_io_iterator, name, limit_reduction=1):
parse_limit = _PARSED_FILE_LIMIT / limit_reduction
open_limit = _OPENED_FILE_LIMIT / limit_reduction
file_io_count = 0
parsed_file_count = 0
regex = re.compile(r'\b' + re.escape(name) + r'\b')
for file_io in _find_python_files_in_sys_path(inference_state, module_contexts):
for file_io in file_io_iterator:
file_io_count += 1
m = _check_fs(inference_state, file_io, regex)
if m is not None:
parsed_file_count += 1
yield m
if parsed_file_count >= parse_limit:
dbg('Hit limit of parsed files: %s', parse_limit)
break
if file_io_count >= open_limit:
dbg('Hit limit of opened files: %s', open_limit)
break

View File

@@ -32,7 +32,7 @@ def _iter_nodes_for_param(param_name):
argument = name.parent
if argument.type == 'argument' \
and argument.children[0] == '*' * param_name.star_count:
# No support for Python <= 3.4 here, but they are end-of-life
# No support for Python 2.7 here, but they are end-of-life
# anyway
trailer = search_ancestor(argument, 'trailer')
if trailer is not None: # Make sure we're in a function

View File

@@ -1,4 +1,5 @@
import os
import re
from jedi._compatibility import unicode, force_unicode, all_suffixes
from jedi.inference.cache import inference_state_method_cache
@@ -207,7 +208,7 @@ def _get_buildout_script_paths(search_path):
def remove_python_path_suffix(path):
for suffix in all_suffixes():
for suffix in all_suffixes() + ['.pyi']:
if path.endswith(suffix):
path = path[:-len(suffix)]
break
@@ -254,7 +255,9 @@ def transform_path_to_dotted(sys_path, module_path):
# is very strange and is probably a file that is called
# `.py`.
return
yield tuple(split)
# Stub folders for foo can end with foo-stubs. Just remove
# it.
yield tuple(re.sub(r'-stubs$', '', s) for s in split)
potential_solutions = tuple(iter_potential_solutions())
if not potential_solutions:

View File

@@ -107,19 +107,9 @@ class PushBackIterator(object):
def ignored(*exceptions):
"""
Value manager that ignores all of the specified exceptions. This will
be in the standard library starting with Python 3.4.
be in the standard library starting with Python 3.5.
"""
try:
yield
except exceptions:
pass
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

View File

@@ -202,9 +202,6 @@ class MethodValue(FunctionValue):
class BaseFunctionExecutionContext(ValueContext, TreeContextMixin):
def is_function_execution(self):
return True
def _infer_annotations(self):
raise NotImplementedError

View File

@@ -1,7 +1,5 @@
import re
import os
from jedi import debug
from jedi.inference.cache import inference_state_method_cache
from jedi.inference.names import AbstractNameDefinition, ModuleName
from jedi.inference.filters import GlobalNameFilter, ParserTreeFilter, DictFilter, MergedFilter
@@ -37,32 +35,6 @@ class _ModuleAttributeName(AbstractNameDefinition):
return compiled.get_string_value_set(self.parent_context.inference_state)
def iter_module_names(inference_state, paths):
# Python modules/packages
for n in inference_state.compiled_subprocess.list_module_names(paths):
yield n
for path in paths:
try:
dirs = os.listdir(path)
except OSError:
# The file might not exist or reading it might lead to an error.
debug.warning("Not possible to list directory: %s", path)
continue
for name in dirs:
# Namespaces
if os.path.isdir(os.path.join(path, name)):
# pycache is obviously not an interestin namespace. Also the
# name must be a valid identifier.
# TODO use str.isidentifier, once Python 2 is removed
if name != '__pycache__' and not re.search(r'\W|^\d', name):
yield name
# Stub files
if name.endswith('.pyi'):
if name != '__init__.pyi':
yield name[:-4]
class SubModuleDictMixin(object):
@inference_state_method_cache()
def sub_modules_dict(self):
@@ -72,7 +44,9 @@ class SubModuleDictMixin(object):
"""
names = {}
if self.is_package():
mods = iter_module_names(self.inference_state, self.py__path__())
mods = self.inference_state.compiled_subprocess.iter_module_names(
self.py__path__()
)
for name in mods:
# It's obviously a relative import to the current module.
names[name] = SubModuleName(self.as_context(), name)
@@ -111,20 +85,7 @@ class ModuleMixin(SubModuleDictMixin):
@property
@inference_state_method_cache()
def name(self):
return self._module_name_class(self, self._string_name)
@property
def _string_name(self):
""" This is used for the goto functions. """
# TODO It's ugly that we even use this, the name is usually well known
# ahead so just pass it when create a ModuleValue.
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__)?(\.pyi?|\.so)?$' % sep, self._path)
# Remove PEP 3149 names
return re.sub(r'\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
return self._module_name_class(self, self.string_names[-1])
@inference_state_method_cache()
def _module_attributes_dict(self):
@@ -260,7 +221,7 @@ class ModuleValue(ModuleMixin, TreeValue):
def __repr__(self):
return "<%s: %s@%s-%s is_stub=%s>" % (
self.__class__.__name__, self._string_name,
self.__class__.__name__, self.py__name__(),
self.tree_node.start_pos[0], self.tree_node.end_pos[0],
self.is_stub()
)

View File

@@ -35,6 +35,9 @@ class ImplicitNamespaceValue(Value, SubModuleDictMixin):
def get_filters(self, origin_scope=None):
yield DictFilter(self.sub_modules_dict())
def get_qualified_names(self):
return ()
@property
@inference_state_method_cache()
def name(self):

View File

@@ -1,203 +0,0 @@
"""
THIS is not in active development, please check
https://github.com/davidhalter/jedi/issues/667 first before editing.
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 <api.html>`_ and supports currently the
following functions (sometimes bug-prone):
- rename
- extract variable
- inline variable
"""
import difflib
from parso import python_bytes_to_unicode, split_lines
from jedi.inference 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 new_name: The new name of the script.
:param script: The source Script object.
:return: list of changed lines/changed files
"""
return Refactoring(_rename(script.get_references(), 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 multi-line extracts
dct = {}
if user_stmt:
pos = script._pos
line_index = pos[0] - 1
# Be careful here. 'array_for_pos' does not exist in 'helpers'.
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' + str(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 multi-line 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()
assert len(definitions) == 1
stmt = definitions[0]._definition
references = script.get_references()
inlines = [r for r in references
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 multi-line 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 expression_list[0].type == 'TODO':
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)

View File

@@ -51,22 +51,21 @@ import os
import platform
# ----------------
# completion output settings
# Completion Output Settings
# ----------------
case_insensitive_completion = True
"""
The completion is by default case insensitive.
Completions are 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.
Adds an opening bracket after a function for completions.
"""
# ----------------
# Filesystem cache
# Filesystem Cache
# ----------------
if platform.system().lower() == 'windows':
@@ -83,31 +82,32 @@ 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,
On Linux, if the environment variable ``$XDG_CACHE_HOME`` is set,
``$XDG_CACHE_HOME/jedi`` is used instead of the default one.
"""
# ----------------
# parser
# 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.
Uses Parso's diff parser. If it is enabled, this might cause issues, please
read the warning on :class:`.Script`. This feature makes it possible to only
parse the parts again that have changed, while reusing the rest of the syntax
tree.
"""
_cropped_file_size = 10e6 # 1 Megabyte
"""
Jedi gets extremely slow if the file size exceed a few thousand lines.
To avoid getting stuck completely Jedi crops the file this point.
To avoid getting stuck completely Jedi crops the file at some point.
One megabyte of typical Python code equals about 20'000 lines of code.
"""
# ----------------
# dynamic stuff
# Dynamic Stuff
# ----------------
dynamic_array_additions = True
@@ -135,13 +135,13 @@ auto_import_modules = [
'gi', # This third-party repository (GTK stuff) doesn't really work with jedi
]
"""
Modules that are not analyzed but imported, although they contain Python code.
Modules that will not be analyzed but imported, if they contain Python code.
This improves autocompletion for libraries that use ``setattr`` or
``globals()`` modifications a lot.
"""
# ----------------
# caching validity (time)
# Caching Validity
# ----------------
call_signatures_validity = 3.0

View File

@@ -19,15 +19,14 @@ READLINE_DEBUG = False
def setup_readline(namespace_module=__main__, fuzzy=False):
"""
Install Jedi completer to :mod:`readline`.
This function sets up :mod:`readline` to use Jedi in a Python interactive
shell.
This function setups :mod:`readline` to use Jedi in Python interactive
shell. If you want to use a custom ``PYTHONSTARTUP`` file (typically
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
@@ -38,6 +37,8 @@ def setup_readline(namespace_module=__main__, fuzzy=False):
readline.parse_and_bind("tab: complete")
except ImportError:
print("Readline is not installed either. No tab completion is enabled.")
else:
setup_readline()
This will fallback to the readline completer if Jedi is not installed.
The readline completer will only complete names in the global namespace,
@@ -45,18 +46,18 @@ def setup_readline(namespace_module=__main__, fuzzy=False):
ran<TAB>
will complete to ``range``
will complete to ``range``.
with both Jedi and readline, but::
With Jedi the following code::
range(10).cou<TAB>
will show complete to ``range(10).count`` only with Jedi.
will complete to ``range(10).count``, this does not work with the default
cPython :mod:`readline` completer.
You'll also need to add ``export PYTHONSTARTUP=$HOME/.pythonrc.py`` to
You will 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(

View File

@@ -33,16 +33,16 @@ setup(name='jedi',
keywords='python completion refactoring vim',
long_description=readme,
packages=find_packages(exclude=['test', 'test.*']),
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
install_requires=install_requires,
extras_require={
'testing': [
# Pytest 5 doesn't support Python 2 and Python 3.4 anymore.
# Pytest 5 doesn't support Python 2 anymore.
'pytest>=3.9.0,<5.0.0',
# docopt for sith doctests
'docopt',
# coloroma for colored debug output
'colorama==0.4.1', # Pinned so it works for Python 3.4
'colorama',
],
'qa': [
'flake8==3.7.9',
@@ -60,7 +60,6 @@ setup(name='jedi',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',

View File

@@ -168,7 +168,7 @@ class TestCase(object):
def show_definitions(self):
for completion in self.objects:
print(completion.desc_with_module)
print(completion.full_name)
if completion.module_path is None:
continue
if os.path.abspath(completion.module_path) == os.path.abspath(self.path):

View File

@@ -473,7 +473,7 @@ def test_func():
#? int()
tuple({1})[0]
# python >= 3.4
# python > 2.7
# -----------------
# PEP 3132 Extended Iterable Unpacking (star unpacking)
# -----------------

View File

@@ -242,7 +242,7 @@ def x():
# yield from
# -----------------
# python >= 3.4
# python > 2.7
def yield_from():
yield from iter([1])

View File

@@ -12,6 +12,8 @@ def from_names_goto():
def builtin_test():
#? ['math']
import math
#? ['mmap']
import mmap
# -----------------
# completions within imports

View File

@@ -1,6 +1,6 @@
""" Pep-0484 type hinting """
# python >= 3.4
# python > 2.7
class A():

View File

@@ -283,7 +283,7 @@ def testnewtype2(y):
y
#? []
y.
# python >= 3.4
# python > 2.7
class TestDefaultDict(typing.DefaultDict[str, int]):
def setdud(self):
@@ -311,7 +311,7 @@ for key in x.keys():
for value in x.values():
#? int()
value
# python >= 3.4
# python > 2.7
"""
@@ -341,9 +341,8 @@ typing.Optional[0]
TYPE_VARX = typing.TypeVar('TYPE_VARX')
TYPE_VAR_CONSTRAINTSX = typing.TypeVar('TYPE_VAR_CONSTRAINTSX', str, int)
# TODO there should at least be some results.
#? []
TYPE_VARX.
#? ['__class__']
TYPE_VARX.__clas
#! ["TYPE_VARX = typing.TypeVar('TYPE_VARX')"]
TYPE_VARX

View File

@@ -130,7 +130,7 @@ def test_p(monkeypatch):
#? ['setattr']
monkeypatch.setatt
# python > 3.4
# python > 2.7
#? ['capsysbinary']
def test_p(capsysbin

View File

@@ -359,7 +359,7 @@ class Test(metaclass=Meta):
# Enum
# -----------------
# python >= 3.4
# python > 2.7
import enum
class X(enum.Enum):

View File

@@ -1,4 +1,4 @@
# python >= 3.4
# python > 2.7
from stub_folder import with_stub, stub_only, with_stub_folder, stub_only_folder
# -------------------------

View File

@@ -76,9 +76,11 @@ def pytest_generate_tests(metafunc):
if 'refactor_case' in metafunc.fixturenames:
base_dir = metafunc.config.option.refactor_case_dir
cases = list(refactor.collect_dir_tests(base_dir, test_files))
metafunc.parametrize(
'refactor_case',
refactor.collect_dir_tests(base_dir, test_files))
'refactor_case', cases,
ids=[c.refactor_type + '-' + c.name for c in cases]
)
if 'static_analysis_case' in metafunc.fixturenames:
base_dir = os.path.join(os.path.dirname(__file__), 'static_analysis')

View File

@@ -2,7 +2,7 @@
import sys
sys.path[0:0] = [
'/usr/lib/python3.4/site-packages',
'/usr/lib/python3.8/site-packages',
'/tmp/.buildout/eggs/important_package.egg'
]

116
test/refactor.py Executable file → Normal file
View File

@@ -1,99 +1,103 @@
#!/usr/bin/env python
"""
Refactoring tests work a little bit similar to Black Box tests. But the idea is
here to compare two versions of code. **Note: Refactoring is currently not in
active development (and was never stable), the tests are therefore not really
valuable - just ignore them.**
Refactoring tests work a little bit similar to integration tests. But the idea
is here to compare two versions of code. If you want to add a new test case,
just look at the existing ones in the ``test/refactor`` folder and copy them.
"""
from __future__ import with_statement
import os
import platform
import re
import sys
from parso import split_lines
from functools import reduce
import jedi
from jedi import refactoring
from .helpers import test_dir
class RefactoringCase(object):
def __init__(self, name, source, line_nr, index, path,
new_name, start_line_test, desired):
def __init__(self, name, code, line_nr, index, path, kwargs, type_, desired_result):
self.name = name
self.source = source
self.line_nr = line_nr
self.index = index
self.path = path
self.new_name = new_name
self.start_line_test = start_line_test
self.desired = desired
self._code = code
self._line_nr = line_nr
self._index = index
self._path = path
self._kwargs = kwargs
self.type = type_
self._desired_result = desired_result
def refactor(self):
script = jedi.Script(self.source, self.line_nr, self.index, self.path)
f_name = os.path.basename(self.path)
refactor_func = getattr(refactoring, f_name.replace('.py', ''))
args = (self.new_name,) if self.new_name else ()
return refactor_func(script, *args)
def get_desired_result(self):
def run(self):
refactor_object = self.refactor()
if platform.system().lower() == 'windows' and self.type == 'diff':
# Windows uses backslashes to separate paths.
lines = split_lines(self._desired_result, keepends=True)
for i, line in enumerate(lines):
if re.search(' import_tree/', line):
lines[i] = line.replace('/', '\\')
return ''.join(lines)
return self._desired_result
# try to get the right excerpt of the newfile
f = refactor_object.new_files()[self.path]
lines = f.splitlines()[self.start_line_test:]
@property
def refactor_type(self):
f_name = os.path.basename(self._path)
return f_name.replace('.py', '')
end = self.start_line_test + len(lines)
pop_start = None
for i, l in enumerate(lines):
if l.startswith('# +++'):
end = i
break
elif '#? ' in l:
pop_start = i
lines.pop(pop_start)
self.result = '\n'.join(lines[:end - 1]).strip()
return self.result
def check(self):
return self.run() == self.desired
def refactor(self, environment):
project = jedi.Project(os.path.join(test_dir, 'refactor'))
script = jedi.Script(self._code, path=self._path, project=project, environment=environment)
refactor_func = getattr(script, self.refactor_type)
return refactor_func(self._line_nr, self._index, **self._kwargs)
def __repr__(self):
return '<%s: %s:%s>' % (self.__class__.__name__,
self.name, self.line_nr - 1)
self.name, self._line_nr - 1)
def collect_file_tests(source, path, lines_to_execute):
r = r'^# --- ?([^\n]*)\n((?:(?!\n# \+\+\+).)*)' \
r'\n# \+\+\+((?:(?!\n# ---).)*)'
for match in re.finditer(r, source, re.DOTALL | re.MULTILINE):
def _collect_file_tests(code, path, lines_to_execute):
r = r'^# -{5,} ?([^\n]*)\n((?:(?!\n# \+{5,}).)*\n)' \
r'# \+{5,}\n((?:(?!\n# -{5,}).)*\n)'
match = None
for match in re.finditer(r, code, re.DOTALL | re.MULTILINE):
name = match.group(1).strip()
first = match.group(2).strip()
second = match.group(3).strip()
start_line_test = source[:match.start()].count('\n') + 1
first = match.group(2)
second = match.group(3)
# get the line with the position of the operation
p = re.match(r'((?:(?!#\?).)*)#\? (\d*) ?([^\n]*)', first, re.DOTALL)
p = re.match(r'((?:(?!#\?).)*)#\? (\d*)( error| text|) ?([^\n]*)', first, re.DOTALL)
if p is None:
print("Please add a test start.")
raise Exception("Please add a test start.")
continue
until = p.group(1)
index = int(p.group(2))
new_name = p.group(3)
type_ = p.group(3).strip() or 'diff'
if p.group(4):
kwargs = eval(p.group(4))
else:
kwargs = {}
line_nr = start_line_test + until.count('\n') + 2
line_nr = until.count('\n') + 2
if lines_to_execute and line_nr - 1 not in lines_to_execute:
continue
yield RefactoringCase(name, source, line_nr, index, path,
new_name, start_line_test, second)
yield RefactoringCase(name, first, line_nr, index, path, kwargs, type_, second)
if match is None:
raise Exception("Didn't match any test")
if match.end() != len(code):
raise Exception("Didn't match until the end of the file in %s" % path)
def collect_dir_tests(base_dir, test_files):
if sys.version_info[0] == 2:
return
for f_name in os.listdir(base_dir):
files_to_execute = [a for a in test_files.items() if a[0] in f_name]
lines_to_execute = reduce(lambda x, y: x + y[1], files_to_execute, [])
if f_name.endswith(".py") and (not test_files or files_to_execute):
path = os.path.join(base_dir, f_name)
with open(path) as f:
source = f.read()
for case in collect_file_tests(source, path, lines_to_execute):
with open(path, newline='') as f:
code = f.read()
for case in _collect_file_tests(code, path, lines_to_execute):
yield case

View File

@@ -1,47 +0,0 @@
# --- simple
def test():
#? 35 a
return test(100, (30 + b, c) + 1)
# +++
def test():
a = (30 + b, c) + 1
return test(100, a)
# --- simple #2
def test():
#? 25 a
return test(100, (30 + b, c) + 1)
# +++
def test():
a = 30 + b
return test(100, (a, c) + 1)
# --- multiline
def test():
#? 30 x
return test(1, (30 + b, c)
+ 1)
# +++
def test():
x = ((30 + b, c)
+ 1)
return test(1, x
)
# --- multiline #2
def test():
#? 25 x
return test(1, (30 + b, c)
+ 1)
# +++
def test():
x = 30 + b
return test(1, (x, c)
+ 1)

View File

@@ -0,0 +1,447 @@
# -------------------------------------------------- in-module-1
glob = 3
#? 11 text {'new_name': 'a'}
test(100, (glob.a + b, c) + 1)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
glob = 3
#? 11 text {'new_name': 'a'}
def a(b):
return glob.a + b
test(100, (a(b), c) + 1)
# -------------------------------------------------- in-module-2
#? 0 text {'new_name': 'ab'}
100 + 1 * 2
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 0 text {'new_name': 'ab'}
def ab():
return 100 + 1 * 2
ab()
# -------------------------------------------------- in-function-1
def f(x):
#? 11 text {'new_name': 'ab'}
return x + 1 * 2
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def ab(x):
return x + 1 * 2
def f(x):
#? 11 text {'new_name': 'ab'}
return ab(x)
# -------------------------------------------------- in-function-with-dec
@classmethod
def f(x):
#? 11 text {'new_name': 'ab'}
return x + 1 * 2
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def ab(x):
return x + 1 * 2
@classmethod
def f(x):
#? 11 text {'new_name': 'ab'}
return ab(x)
# -------------------------------------------------- in-method-1
class X:
def z(self): pass
def f(x, b):
#? 11 text {'new_name': 'ab'}
return x + b * 2
# ++++++++++++++++++++++++++++++++++++++++++++++++++
class X:
def z(self): pass
def ab(x, b):
return x + b * 2
def f(x, b):
#? 11 text {'new_name': 'ab'}
return x.ab(b)
# -------------------------------------------------- in-method-2
glob1 = 1
class X:
def g(self): pass
def f(self, b, c):
#? 11 text {'new_name': 'ab'}
return self.g() or self.f(b) ^ glob1 & b
# ++++++++++++++++++++++++++++++++++++++++++++++++++
glob1 = 1
class X:
def g(self): pass
def ab(self, b):
return self.g() or self.f(b) ^ glob1 & b
def f(self, b, c):
#? 11 text {'new_name': 'ab'}
return self.ab(b)
# -------------------------------------------------- in-method-order
class X:
def f(self, b, c):
#? 18 text {'new_name': 'b'}
return b | self.a
# ++++++++++++++++++++++++++++++++++++++++++++++++++
class X:
def b(self, b):
return b | self.a
def f(self, b, c):
#? 18 text {'new_name': 'b'}
return self.b(b)
# -------------------------------------------------- in-classmethod-1
class X:
@classmethod
def f(x):
#? 16 text {'new_name': 'ab'}
return 25
# ++++++++++++++++++++++++++++++++++++++++++++++++++
class X:
@classmethod
def ab(x):
return 25
@classmethod
def f(x):
#? 16 text {'new_name': 'ab'}
return x.ab()
# -------------------------------------------------- in-staticmethod-1
class X(int):
@staticmethod
def f(x):
#? 16 text {'new_name': 'ab'}
return 25 | 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def ab():
return 25 | 3
class X(int):
@staticmethod
def f(x):
#? 16 text {'new_name': 'ab'}
return ab()
# -------------------------------------------------- in-class-1
class Ya():
a = 3
#? 11 text {'new_name': 'f'}
c = a + 2
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def f(a):
return a + 2
class Ya():
a = 3
#? 11 text {'new_name': 'f'}
c = f(a)
# -------------------------------------------------- in-closure
def x(z):
def y(x):
#? 15 text {'new_name': 'f'}
return -x * z
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def f(x, z):
return -x * z
def x(z):
def y(x):
#? 15 text {'new_name': 'f'}
return f(x, z)
# -------------------------------------------------- with-range-1
#? 0 text {'new_name': 'a', 'until_line': 4}
v1 = 3
v2 = 2
x = test(v1 + v2 * v3)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 0 text {'new_name': 'a', 'until_line': 4}
def a(test, v3):
v1 = 3
v2 = 2
x = test(v1 + v2 * v3)
return x
x = a(test, v3)
# -------------------------------------------------- with-range-2
#? 2 text {'new_name': 'a', 'until_line': 6, 'until_column': 4}
#foo
v1 = 3
v2 = 2
x, y = test(v1 + v2 * v3)
#raaaa
y
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 2 text {'new_name': 'a', 'until_line': 6, 'until_column': 4}
def a(test, v3):
#foo
v1 = 3
v2 = 2
x, y = test(v1 + v2 * v3)
#raaaa
return y
y = a(test, v3)
y
# -------------------------------------------------- with-range-3
#foo
#? 2 text {'new_name': 'a', 'until_line': 5, 'until_column': 4}
v1 = 3
v2 = 2
x, y = test(v1 + v2 * v3)
#raaaa
y
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#foo
#? 2 text {'new_name': 'a', 'until_line': 5, 'until_column': 4}
def a(test, v3):
v1 = 3
v2 = 2
x, y = test(v1 + v2 * v3)
return y
y = a(test, v3)
#raaaa
y
# -------------------------------------------------- with-range-func-1
import os
# comment1
@dec
# comment2
def x(v1):
#foo
#? 2 text {'new_name': 'a', 'until_line': 9, 'until_column': 5}
v2 = 2
if 1:
x, y = os.listdir(v1 + v2 * v3)
#bar
return x, y
# ++++++++++++++++++++++++++++++++++++++++++++++++++
import os
# comment1
def a(v1, v3):
v2 = 2
if 1:
x, y = os.listdir(v1 + v2 * v3)
return x, y
@dec
# comment2
def x(v1):
#foo
#? 2 text {'new_name': 'a', 'until_line': 9, 'until_column': 5}
x, y = a(v1, v3)
#bar
return x, y
# -------------------------------------------------- with-range-func-2
import os
# comment1
# comment2
def x(v1):
#? 2 text {'new_name': 'a', 'until_line': 10, 'until_column': 0}
#foo
v2 = 2
if 1:
x, y = os.listdir(v1 + v2 * v3)
#bar
return y
x
# ++++++++++++++++++++++++++++++++++++++++++++++++++
import os
# comment1
# comment2
def a(v1, v3):
#foo
v2 = 2
if 1:
x, y = os.listdir(v1 + v2 * v3)
#bar
return y
def x(v1):
#? 2 text {'new_name': 'a', 'until_line': 10, 'until_column': 0}
y = a(v1, v3)
return y
x
# -------------------------------------------------- with-range-func-3
def x(v1):
#? 2 text {'new_name': 'func', 'until_line': 6, 'until_column': 4}
#foo
v2 = 2
x = v1 * 2
y = 3
#bar
return x
x
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def func(v1):
#foo
v2 = 2
x = v1 * 2
return x
def x(v1):
#? 2 text {'new_name': 'func', 'until_line': 6, 'until_column': 4}
x = func(v1)
y = 3
#bar
return x
x
# -------------------------------------------------- in-class-range-1
class X1:
#? 11 text {'new_name': 'f', 'until_line': 4}
a = 3
c = a + 2
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def f():
a = 3
c = a + 2
return c
class X1:
#? 11 text {'new_name': 'f', 'until_line': 4}
c = f()
# -------------------------------------------------- in-method-range-1
glob1 = 1
class X:
# ha
def g(self): pass
# haha
def f(self, b, c):
#? 11 text {'new_name': 'ab', 'until_line': 12, 'until_column': 28}
#foo
local1 = 3
local2 = 4
x= self.g() or self.f(b) ^ glob1 & b is local1
# bar
# ++++++++++++++++++++++++++++++++++++++++++++++++++
glob1 = 1
class X:
# ha
def g(self): pass
# haha
def ab(self, b):
#foo
local1 = 3
local2 = 4
x= self.g() or self.f(b) ^ glob1 & b is local1
return x
def f(self, b, c):
#? 11 text {'new_name': 'ab', 'until_line': 12, 'until_column': 28}
x = self.ab(b)
# bar
# -------------------------------------------------- in-method-range-2
glob1 = 1
class X:
# comment
def f(self, b, c):
#? 11 text {'new_name': 'ab', 'until_line': 11, 'until_column': 10}
#foo
local1 = 3
local2 = 4
return local1 * glob1 * b
# bar
# ++++++++++++++++++++++++++++++++++++++++++++++++++
glob1 = 1
class X:
# comment
def ab(self, b):
#foo
local1 = 3
local2 = 4
return local1 * glob1 * b
# bar
def f(self, b, c):
#? 11 text {'new_name': 'ab', 'until_line': 11, 'until_column': 10}
return self.ab(b)
# -------------------------------------------------- in-method-range-3
glob1 = 1
class X:
def f(self, b, c):
local1, local2 = 3, 4
#foo
#? 11 text {'new_name': 'ab', 'until_line': 7, 'until_column': 29}
return local1 & glob1 & b
# bar
local2
# ++++++++++++++++++++++++++++++++++++++++++++++++++
glob1 = 1
class X:
def ab(self, local1, b):
return local1 & glob1 & b
def f(self, b, c):
local1, local2 = 3, 4
#foo
#? 11 text {'new_name': 'ab', 'until_line': 7, 'until_column': 29}
return self.ab(local1, b)
# bar
local2
# -------------------------------------------------- in-method-no-param
glob1 = 1
class X:
def f():
#? 11 text {'new_name': 'ab', 'until_line': 5, 'until_column': 22}
return glob1 + 2
# ++++++++++++++++++++++++++++++++++++++++++++++++++
glob1 = 1
class X:
def ab():
return glob1 + 2
def f():
#? 11 text {'new_name': 'ab', 'until_line': 5, 'until_column': 22}
return ab()
# -------------------------------------------------- random-return-1
def x():
#? 0 error {'new_name': 'ab', 'until_line': 5, 'until_column': 10}
if x:
return 1
return 1
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Can only extract return statements if they are at the end.
# -------------------------------------------------- random-return-2
def x():
#? 0 error {'new_name': 'ab', 'until_line': 5, 'until_column': 10}
#
return
pass
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Can only extract return statements if they are at the end.
# -------------------------------------------------- random-yield-1
def x():
#? 0 error {'new_name': 'ab', 'until_line': 5, 'until_column': 10}
#
if (yield 1):
return
pass
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract yield statements.
# -------------------------------------------------- random-yield-2
def x():
#? 0 error {'new_name': 'ab', 'until_line': 4, 'until_column': 10}
#
try:
yield
finally:
pass
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract yield statements.

View File

@@ -0,0 +1,251 @@
# -------------------------------------------------- simple-1
def test():
#? 35 text {'new_name': 'a'}
return test(100, (30 + b, c) + 1)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def test():
#? 35 text {'new_name': 'a'}
a = (30 + b, c) + 1
return test(100, a)
# -------------------------------------------------- simple-2
def test():
#? 25 text {'new_name': 'a'}
return test(100, (30 + b, c) + 1)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def test():
#? 25 text {'new_name': 'a'}
a = 30 + b
return test(100, (a, c) + 1)
# -------------------------------------------------- simple-3
#? 13 text {'new_name': 'zzx.x'}
test(100, {1 |1: 2 + 3})
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 13 text {'new_name': 'zzx.x'}
zzx.x = 1 |1
test(100, {zzx.x: 2 + 3})
# -------------------------------------------------- multiline-1
def test():
#? 30 text {'new_name': 'x'}
return test(1, (30 + b, c)
+ 1)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def test():
#? 30 text {'new_name': 'x'}
x = (30 + b, c)
+ 1
return test(1, x)
# -------------------------------------------------- multiline-2
def test():
#? 25 text {'new_name': 'x'}
return test(1, (30 + b, c)
+ 1)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
def test():
#? 25 text {'new_name': 'x'}
x = 30 + b
return test(1, (x, c)
+ 1)
# -------------------------------------------------- for-param-error-1
#? 10 error {'new_name': 'x'}
def test(p1):
return
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract a name that defines something
# -------------------------------------------------- for-param-error-2
#? 12 error {'new_name': 'x'}
def test(p1= 3):
return
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract a "param"
# -------------------------------------------------- for-param-1
#? 12 text {'new_name': 'x'}
def test(p1=20):
return
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 12 text {'new_name': 'x'}
x = 20
def test(p1=x):
return
# -------------------------------------------------- for-something
#? 12 text {'new_name': 'x'}
def test(p1=20):
return
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 12 text {'new_name': 'x'}
x = 20
def test(p1=x):
return
# -------------------------------------------------- class-inheritance-1
#? 12 text {'new_name': 'x'}
class Foo(foo.Bar):
pass
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 12 text {'new_name': 'x'}
x = foo.Bar
class Foo(x):
pass
# -------------------------------------------------- class-inheritance-2
#? 16 text {'new_name': 'x'}
class Foo(foo.Bar):
pass
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 16 text {'new_name': 'x'}
x = foo.Bar
class Foo(x):
pass
# -------------------------------------------------- keyword-pass
#? 12 error {'new_name': 'x'}
def x(): pass
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract a "simple_stmt"
# -------------------------------------------------- keyword-continue
#? 5 error {'new_name': 'x'}
continue
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract a "simple_stmt"
# -------------------------------------------------- keyword-None
if 1:
#? 4 text {'new_name': 'x'}
None
# ++++++++++++++++++++++++++++++++++++++++++++++++++
if 1:
#? 4 text {'new_name': 'x'}
x = None
x
# -------------------------------------------------- with-tuple
#? 4 text {'new_name': 'x'}
x + 1, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 4 text {'new_name': 'x'}
x = x + 1
x, 3
# -------------------------------------------------- range-1
#? 4 text {'new_name': 'x', 'until_column': 9}
y + 1, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 4 text {'new_name': 'x', 'until_column': 9}
x = y + 1, 3
x
# -------------------------------------------------- range-2
#? 1 text {'new_name': 'x', 'until_column': 3}
y + 1, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 1 text {'new_name': 'x', 'until_column': 3}
x = y + 1
x, 3
# -------------------------------------------------- range-3
#? 1 text {'new_name': 'x', 'until_column': 6}
y + 1, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 1 text {'new_name': 'x', 'until_column': 6}
x = y + 1
x, 3
# -------------------------------------------------- range-4
#? 1 text {'new_name': 'x', 'until_column': 1}
y + 1, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 1 text {'new_name': 'x', 'until_column': 1}
x = y
x + 1, 3
# -------------------------------------------------- addition-1
#? 4 text {'new_name': 'x', 'until_column': 9}
z = y + 1 + 2+ 3, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 4 text {'new_name': 'x', 'until_column': 9}
x = y + 1
z = x + 2+ 3, 3
# -------------------------------------------------- addition-2
#? 8 text {'new_name': 'x', 'until_column': 12}
z = y +1 + 2+ 3, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 8 text {'new_name': 'x', 'until_column': 12}
x = 1 + 2
z = y +x+ 3, 3
# -------------------------------------------------- addition-3
#? 10 text {'new_name': 'x', 'until_column': 14}
z = y + 1 + 2+ 3, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 10 text {'new_name': 'x', 'until_column': 14}
x = 1 + 2+ 3
z = y + x, 3
# -------------------------------------------------- addition-4
#? 13 text {'new_name': 'x', 'until_column': 17}
z = y + (1 + 2)+ 3, 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 13 text {'new_name': 'x', 'until_column': 17}
x = (1 + 2)+ 3
z = y + x, 3
# -------------------------------------------------- mult-add-1
#? 8 text {'new_name': 'x', 'until_column': 11}
z = foo(y+1*2+3, 3)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 8 text {'new_name': 'x', 'until_column': 11}
x = y+1
z = foo(x*2+3, 3)
# -------------------------------------------------- mult-add-2
#? 12 text {'new_name': 'x', 'until_column': 15}
z = foo(y+1*2+3)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 12 text {'new_name': 'x', 'until_column': 15}
x = 2+3
z = foo(y+1*x)
# -------------------------------------------------- mult-add-3
#? 9 text {'new_name': 'x', 'until_column': 13}
z = (y+1*2+3)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 9 text {'new_name': 'x', 'until_column': 13}
x = (y+1*2+3)
z = x
# -------------------------------------------------- extract-weird-1
#? 0 error {'new_name': 'x', 'until_column': 7}
foo = 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract a "expr_stmt"
# -------------------------------------------------- extract-weird-2
#? 0 error {'new_name': 'x', 'until_column': 5}
def x():
foo = 3
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract a "funcdef"
# -------------------------------------------------- extract-weird-3
def x():
#? 4 error {'new_name': 'x', 'until_column': 8}
if 1:
pass
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract a "if_stmt"
# -------------------------------------------------- extract-weird-4
#? 4 error {'new_name': 'x', 'until_column': 7}
x = foo = 4
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot extract a name that defines something
# -------------------------------------------------- keyword-None
#? 4 text {'new_name': 'x', 'until_column': 7}
yy = not foo or bar
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 4 text {'new_name': 'x', 'until_column': 7}
x = not foo
yy = x or bar
# -------------------------------------------------- augassign
yy = ()
#? 6 text {'new_name': 'x', 'until_column': 10}
yy += 3, 4
# ++++++++++++++++++++++++++++++++++++++++++++++++++
yy = ()
#? 6 text {'new_name': 'x', 'until_column': 10}
x = 3, 4
yy += x
# -------------------------------------------------- if-else
#? 9 text {'new_name': 'x', 'until_column': 22}
yy = foo(a if y else b)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 9 text {'new_name': 'x', 'until_column': 22}
x = a if y else b
yy = foo(x)
# -------------------------------------------------- lambda
#? 8 text {'new_name': 'x', 'until_column': 17}
y = foo(lambda x: 3, 5)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
#? 8 text {'new_name': 'x', 'until_column': 17}
x = lambda x: 3
y = foo(x, 5)

View File

@@ -0,0 +1 @@
inline_var = 5 + 3

View File

@@ -0,0 +1,2 @@
def pkgx():
pass

View File

@@ -0,0 +1 @@
def pkgx() -> int: ...

View File

@@ -0,0 +1 @@
from . import pkgx

View File

@@ -0,0 +1 @@
from .. import pkgx

View File

@@ -0,0 +1 @@
foobar = 3

View File

@@ -1,18 +1,243 @@
# --- simple
# -------------------------------------------------- no-name-error
#? 0 error
1
# ++++++++++++++++++++++++++++++++++++++++++++++++++
There is no name under the cursor
# -------------------------------------------------- multi-equal-error
def test():
#? 4 error
a = b = 3
return test(100, a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline a statement with multiple definitions
# -------------------------------------------------- no-definition-error
#? 5 error
test(a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
No definition found to inline
# -------------------------------------------------- multi-names-error
#? 0 error
a, b[1] = 3
test(a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline a statement with multiple definitions
# -------------------------------------------------- addition-error
#? 0 error
a = 2
a += 3
test(a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline a name with multiple definitions
# -------------------------------------------------- only-addition-error
#? 0 error
a += 3
test(a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline a statement with "+="
# -------------------------------------------------- with-annotation
foobarb: int = 1
#? 5
test(foobarb)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,4 +1,3 @@
-foobarb: int = 1
#? 5
-test(foobarb)
+test(1)
# -------------------------------------------------- only-annotation-error
a: int
#? 5 error
test(a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline a statement that is defined by an annotation
# -------------------------------------------------- builtin
import math
#? 7 error
math.cos
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline builtins/extensions
# -------------------------------------------------- module-error
from import_tree import inline_mod
#? 11 error
test(inline_mod)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline imports or modules
# -------------------------------------------------- module-works
from import_tree import inline_mod
#? 22
test(x, inline_mod. inline_var.conjugate)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- import_tree/inline_mod.py
+++ import_tree/inline_mod.py
@@ -1,2 +1 @@
-inline_var = 5 + 3
--- inline.py
+++ inline.py
@@ -1,4 +1,4 @@
from import_tree import inline_mod
#? 22
-test(x, inline_mod. inline_var.conjugate)
+test(x, (5 + 3).conjugate)
# -------------------------------------------------- class
class A: pass
#? 5 error
test(A)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline a class
# -------------------------------------------------- function
def foo(a):
return a + 1
#? 5 error
test(foo(1))
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline a function
# -------------------------------------------------- for-stmt
for x in []:
#? 9 error
test(x)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
Cannot inline a for_stmt
# -------------------------------------------------- simple
def test():
#? 4
a = (30 + b, c) + 1
return test(100, a)
# +++
def test():
return test(100, (30 + b, c) + 1)
# --- simple
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,5 +1,4 @@
def test():
#? 4
- a = (30 + b, c) + 1
- return test(100, a)
+ return test(100, (30 + b, c) + 1)
# -------------------------------------------------- tuple
if 1:
#? 4
a = 1, 2
return test(100, a)
# +++
if 1:
return test(100, (1, 2))
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,5 +1,4 @@
if 1:
#? 4
- a = 1, 2
- return test(100, a)
+ return test(100, (1, 2))
# -------------------------------------------------- multiplication-add-parens1
a = 1+2
#? 11
test(100 * a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,4 +1,3 @@
-a = 1+2
#? 11
-test(100 * a)
+test(100 * (1+2))
# -------------------------------------------------- multiplication-add-parens2
a = 1+2
#? 11
(x, 100 * a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,4 +1,3 @@
-a = 1+2
#? 11
-(x, 100 * a)
+(x, 100 * (1+2))
# -------------------------------------------------- multiplication-add-parens3
x
a = 1+2
#? 9
(100 ** a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,5 +1,4 @@
x
-a = 1+2
#? 9
-(100 ** a)
+(100 ** (1+2))
# -------------------------------------------------- no-add-parens1
x
a = 1+2
#? 5
test(a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,5 +1,4 @@
x
-a = 1+2
#? 5
-test(a)
+test(1+2)
# -------------------------------------------------- no-add-parens2
a = 1+2
#? 9
test(3, a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,4 +1,3 @@
-a = 1+2
#? 9
-test(3, a)
+test(3, 1+2)
# -------------------------------------------------- no-add-parens3
a = 1|2
#? 5
(3, a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,4 +1,3 @@
-a = 1|2
#? 5
-(3, a)
+(3, 1|2)
# -------------------------------------------------- comment
a = 1 and 2 # foo
#? 9
(3, 3 * a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,4 +1,4 @@
-a = 1 and 2 # foo
+ # foo
#? 9
-(3, 3 * a)
+(3, 3 * (1 and 2))
# -------------------------------------------------- semicolon
a = 1, 2 ; b = 3
#? 9
(3, 3 == a)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,4 +1,4 @@
-a = 1, 2 ; b = 3
+ b = 3
#? 9
-(3, 3 == a)
+(3, 3 == (1, 2))
# -------------------------------------------------- no-tree-name
a = 1 + 2
#? 0
a.conjugate
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- inline.py
+++ inline.py
@@ -1,4 +1,3 @@
-a = 1 + 2
#? 0
-a.conjugate
+(1 + 2).conjugate

View File

@@ -3,15 +3,233 @@ Test coverage for renaming is mostly being done by testing
`Script.get_references`.
"""
# --- simple
# -------------------------------------------------- no-name
#? 0 error {'new_name': 'blabla'}
1
# ++++++++++++++++++++++++++++++++++++++++++++++++++
There is no name under the cursor
# -------------------------------------------------- simple
def test1():
#? 7 blabla
#? 7 {'new_name': 'blabla'}
test1()
AssertionError
return test1, test1.not_existing
# +++
def blabla():
blabla()
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- rename.py
+++ rename.py
@@ -1,6 +1,6 @@
-def test1():
+def blabla():
#? 7 {'new_name': 'blabla'}
- test1()
+ blabla()
AssertionError
return blabla, blabla.not_existing
- return test1, test1.not_existing
+ return blabla, blabla.not_existing
# -------------------------------------------------- var-not-found
undefined_var
#? 0 {'new_name': 'lala'}
undefined_var
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- rename.py
+++ rename.py
@@ -1,4 +1,4 @@
undefined_var
#? 0 {'new_name': 'lala'}
-undefined_var
+lala
# -------------------------------------------------- different-scopes
def x():
#? 7 {'new_name': 'v'}
some_var = 3
some_var
def y():
some_var = 3
some_var
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- rename.py
+++ rename.py
@@ -1,7 +1,7 @@
def x():
#? 7 {'new_name': 'v'}
- some_var = 3
- some_var
+ v = 3
+ v
def y():
some_var = 3
some_var
# -------------------------------------------------- keyword-param1
#? 22 {'new_name': 'lala'}
def mykeywordparam1(param1):
str(param1)
mykeywordparam1(1)
mykeywordparam1(param1=3)
mykeywordparam1(x, param1=2)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- rename.py
+++ rename.py
@@ -1,7 +1,7 @@
#? 22 {'new_name': 'lala'}
-def mykeywordparam1(param1):
- str(param1)
+def mykeywordparam1(lala):
+ str(lala)
mykeywordparam1(1)
-mykeywordparam1(param1=3)
-mykeywordparam1(x, param1=2)
+mykeywordparam1(lala=3)
+mykeywordparam1(x, lala=2)
# -------------------------------------------------- keyword-param2
def mykeywordparam2(param1):
str(param1)
mykeywordparam2(1)
mykeywordparam2(param1=3)
#? 22 {'new_name': 'lala'}
mykeywordparam2(x, param1=2)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- rename.py
+++ rename.py
@@ -1,7 +1,7 @@
-def mykeywordparam2(param1):
- str(param1)
+def mykeywordparam2(lala):
+ str(lala)
mykeywordparam2(1)
-mykeywordparam2(param1=3)
+mykeywordparam2(lala=3)
#? 22 {'new_name': 'lala'}
-mykeywordparam2(x, param1=2)
+mykeywordparam2(x, lala=2)
# -------------------------------------------------- import
from import_tree.some_mod import foobar
#? 0 {'new_name': 'renamed'}
foobar
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- import_tree/some_mod.py
+++ import_tree/some_mod.py
@@ -1,2 +1,2 @@
-foobar = 3
+renamed = 3
--- rename.py
+++ rename.py
@@ -1,4 +1,4 @@
-from import_tree.some_mod import foobar
+from import_tree.some_mod import renamed
#? 0 {'new_name': 'renamed'}
-foobar
+renamed
# -------------------------------------------------- module
from import_tree import some_mod
#? 0 {'new_name': 'renamedm'}
some_mod
# ++++++++++++++++++++++++++++++++++++++++++++++++++
rename from import_tree/some_mod.py
rename to import_tree/renamedm.py
--- rename.py
+++ rename.py
@@ -1,4 +1,4 @@
-from import_tree import some_mod
+from import_tree import renamedm
#? 0 {'new_name': 'renamedm'}
-some_mod
+renamedm
# -------------------------------------------------- import-not-found
#? 20 {'new_name': 'lala'}
import undefined_import
haha( undefined_import)
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- rename.py
+++ rename.py
@@ -1,4 +1,4 @@
#? 20 {'new_name': 'lala'}
-import undefined_import
-haha( undefined_import)
+import lala
+haha( lala)
# -------------------------------------------------- in-package-with-stub
#? 31 {'new_name': 'renamedm'}
from import_tree.pkgx import pkgx
# ++++++++++++++++++++++++++++++++++++++++++++++++++
--- import_tree/pkgx/__init__.py
+++ import_tree/pkgx/__init__.py
@@ -1,3 +1,3 @@
-def pkgx():
+def renamedm():
pass
--- import_tree/pkgx/__init__.pyi
+++ import_tree/pkgx/__init__.pyi
@@ -1,2 +1,2 @@
-def pkgx() -> int: ...
+def renamedm() -> int: ...
--- import_tree/pkgx/mod.pyi
+++ import_tree/pkgx/mod.pyi
@@ -1,2 +1,2 @@
-from . import pkgx
+from . import renamedm
--- rename.py
+++ rename.py
@@ -1,3 +1,3 @@
#? 31 {'new_name': 'renamedm'}
-from import_tree.pkgx import pkgx
+from import_tree.pkgx import renamedm
# -------------------------------------------------- package-with-stub
#? 18 {'new_name': 'renamedp'}
from import_tree.pkgx
# ++++++++++++++++++++++++++++++++++++++++++++++++++
rename from import_tree/pkgx
rename to import_tree/renamedp
--- import_tree/pkgx/mod2.py
+++ import_tree/renamedp/mod2.py
@@ -1,2 +1,2 @@
-from .. import pkgx
+from .. import renamedp
--- rename.py
+++ rename.py
@@ -1,3 +1,3 @@
#? 18 {'new_name': 'renamedp'}
-from import_tree.pkgx
+from import_tree.renamedp
# -------------------------------------------------- weird-package-mix
if random_undefined_variable:
from import_tree.pkgx import pkgx
else:
from import_tree import pkgx
#? 4 {'new_name': 'rename'}
pkgx
# ++++++++++++++++++++++++++++++++++++++++++++++++++
rename from import_tree/pkgx
rename to import_tree/rename
--- import_tree/pkgx/__init__.py
+++ import_tree/rename/__init__.py
@@ -1,3 +1,3 @@
-def pkgx():
+def rename():
pass
--- import_tree/pkgx/__init__.pyi
+++ import_tree/rename/__init__.pyi
@@ -1,2 +1,2 @@
-def pkgx() -> int: ...
+def rename() -> int: ...
--- import_tree/pkgx/mod.pyi
+++ import_tree/rename/mod.pyi
@@ -1,2 +1,2 @@
-from . import pkgx
+from . import rename
--- import_tree/pkgx/mod2.py
+++ import_tree/rename/mod2.py
@@ -1,2 +1,2 @@
-from .. import pkgx
+from .. import rename
--- rename.py
+++ rename.py
@@ -1,7 +1,7 @@
if random_undefined_variable:
- from import_tree.pkgx import pkgx
+ from import_tree.rename import rename
else:
- from import_tree import pkgx
+ from import_tree import rename
#? 4 {'new_name': 'rename'}
-pkgx
+rename

View File

@@ -1,14 +1,11 @@
#!/usr/bin/env python
"""
|jedi| is mostly being tested by what I would call "Blackbox Tests". These
tests are just testing the interface and do input/output testing. This makes a
lot of sense for |jedi|. Jedi supports so many different code structures, that
it is just stupid to write 200'000 unittests in the manner of
``regression.py``. Also, it is impossible to do doctests/unittests on most of
the internal data structures. That's why |jedi| uses mostly these kind of
tests.
|jedi| is mostly being tested by what I would call "integration tests". These
tests are testing type inference with the public API. This makes a
lot of sense for |jedi|. Also, it is hard to write doctests/unittests for
the internal data structures.
There are different kind of tests:
There are different kinds of tests:
- completions / inference ``#?``
- goto: ``#!``
@@ -18,29 +15,31 @@ How to run tests?
+++++++++++++++++
Jedi uses pytest_ to run unit and integration tests. To run tests,
simply run ``pytest``. You can also use tox_ to run tests for
multiple Python versions.
simply run ``pytest``.
.. _pytest: http://pytest.org
.. _tox: http://testrun.org/tox
Integration test cases are located in ``test/completion`` directory
and each test case is indicated by either the comment ``#?`` (completions /
inference), ``#!`` (goto), or ``#<`` (references).
Most integration test cases are located in the ``test/completion`` directory
and each test case starts with one of these comments:
- ``#?`` (completions / inference)
- ``#!`` (goto)
- ``#<`` (references)
There is also support for third party libraries. In a normal test run they are
not being executed, you have to provide a ``--thirdparty`` option.
In addition to standard `-k` and `-m` options in pytest, you can use the
`-T` (`--test-files`) option to specify integration test cases to run.
In addition to pytest's ``-k`` and ``-m`` options, you can use the
``-T`` (``--test-files`) option to specify which test cases should run.
It takes the format of ``FILE_NAME[:LINE[,LINE[,...]]]`` where
``FILE_NAME`` is a file in ``test/completion`` and ``LINE`` is a line
number of the test comment. Here is some recipes:
number of the test comment. Here are some examples:
Run tests only in ``basic.py`` and ``imports.py``::
Run tests only in ``completion/basic.py`` and ``completion/imports.py``::
pytest test/test_integration.py -T basic.py -T imports.py
Run test at line 4, 6, and 8 in ``basic.py``::
Run test at line 4, 6, and 8 in ``completion/basic.py``::
pytest test/test_integration.py -T basic.py:4,6,8
@@ -57,38 +56,30 @@ that you can start by running ``./run.py``. The above example could be run by::
./run.py basic 4 6 8 50-80
The advantage of this runner is simplicity and more customized error reports.
Using both runners will help you to have a quicker overview of what's
happening.
Auto-Completion Tests
+++++++++++++++++++++
Auto-Completion
Uses a comment to specify a test on the next line. The comment defines the
expected completions. The comment always begins with `#?`. The last row
symbolizes the cursor. For example::
#? ['upper']
a = 'foo'; a.upp
Inference Tests
+++++++++++++++
Uses comments to specify a test in the next line. The comment says which
results are expected. The comment always begins with `#?`. The last row
symbolizes the cursor.
For example::
#? ['real']
a = 3; a.rea
Because it follows ``a.rea`` and a is an ``int``, which has a ``real``
property.
Inference
+++++++++
Inference tests use the same symbols like completion tests. This is
possible because the completion tests are defined with a list::
Inference tests look very simliar. The difference is that inference tests don't
use brackets::
#? int()
ab = 3; ab
Goto
++++
Goto Tests
++++++++++
Tests look like this::
Goto Tests look like this::
abc = 1
#! ['abc=1']
@@ -100,13 +91,13 @@ describes the position of the test (otherwise it's just the end of line)::
#! 2 ['abc=1']
abc
References
++++++++++
Reference Tests
+++++++++++++++
Tests look like this::
abc = 1
#< abc@1,0 abc@3,0
#< (1,0), (3,0)
abc
"""
import os
@@ -124,7 +115,7 @@ import pytest
import jedi
from jedi import debug
from jedi._compatibility import unicode, is_py3
from jedi.api.classes import Definition
from jedi.api.classes import Name
from jedi.api.completion import get_user_context
from jedi import parser_utils
from jedi.api.environment import get_default_environment, get_system_environment
@@ -227,7 +218,7 @@ class IntegrationTestCase(BaseTestCase):
def comparison(definition):
suffix = '()' if definition.type == 'instance' else ''
return definition.desc_with_module + suffix
return definition.full_name + suffix
def definition(correct, correct_start, path):
should_be = set()
@@ -244,7 +235,7 @@ class IntegrationTestCase(BaseTestCase):
raise Exception('Could not resolve %s on line %s'
% (match.string, self.line_nr - 1))
should_be |= set(Definition(inference_state, r.name) for r in results)
should_be |= set(Name(inference_state, r.name) for r in results)
debug.dbg('Finished getting types', color='YELLOW')
# Because the objects have different ids, `repr`, then compare.
@@ -411,7 +402,7 @@ def collect_dir_tests(base_dir, test_files, check_thirdparty=False):
path = os.path.join(base_dir, f_name)
if is_py3:
with open(path, encoding='utf-8') as f:
with open(path, encoding='utf-8', newline='') as f:
source = f.read()
else:
with open(path) as f:
@@ -440,7 +431,7 @@ Options:
--pdb Enable pdb debugging on fail.
-d, --debug Enable text output debugging (please install ``colorama``).
--thirdparty Also run thirdparty tests (in ``completion/thirdparty``).
--env <dotted> A Python version, like 2.7, 3.4, etc.
--env <dotted> A Python version, like 2.7, 3.8, etc.
"""
if __name__ == '__main__':
import docopt

View File

@@ -141,7 +141,7 @@ def test_infer_on_generator(Script):
def test_goto_definition_not_multiple(Script):
"""
There should be only one Definition result if it leads back to the same
There should be only one result if it leads back to the same
origin (e.g. instance method)
"""

View File

@@ -304,7 +304,7 @@ def test_builtins(Script):
def test_signature_is_definition(Script):
"""
Through inheritance, a signature is a sub class of Definition.
Through inheritance, a signature is a sub class of Name.
Check if the attributes match.
"""
s = """class Spam(): pass\nSpam"""
@@ -316,7 +316,8 @@ def test_signature_is_definition(Script):
# Now compare all the attributes that a Signature must also have.
for attr_name in dir(definition):
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer',
'params', 'get_signatures', 'execute', 'goto']
'params', 'get_signatures', 'execute', 'goto',
'desc_with_module']
if attr_name.startswith('_') or attr_name in dont_scan:
continue

View File

@@ -23,7 +23,7 @@ def test_basedefinition_type(Script, get_names):
"""
Return a list of definitions for parametrized tests.
:rtype: [jedi.api_classes.BaseDefinition]
:rtype: [jedi.api_classes.BaseName]
"""
source = dedent("""
import sys
@@ -193,7 +193,7 @@ def test_hashlib_params(Script, environment):
if environment.version_info < (3,):
pytest.skip()
script = Script(source='from hashlib import sha256')
script = Script('from hashlib import sha256')
c, = script.complete()
sig, = c.get_signatures()
assert [p.name for p in sig.params] == ['arg']
@@ -278,7 +278,7 @@ def test_parent_on_function(Script):
code = 'def spam():\n pass'
def_, = Script(code).goto(line=1, column=len('def spam'))
parent = def_.parent()
assert parent.name == ''
assert parent.name == '__main__'
assert parent.type == 'module'
@@ -328,7 +328,7 @@ def test_parent_on_closure(Script):
assert foo.parent().name == 'inner'
assert foo.parent().parent().name == 'bar'
assert foo.parent().parent().parent().name == 'Foo'
assert foo.parent().parent().parent().parent().name == ''
assert foo.parent().parent().parent().parent().name == '__main__'
assert inner_func.parent().name == 'bar'
assert inner_func.parent().parent().name == 'Foo'
@@ -344,7 +344,7 @@ def test_parent_on_comprehension(Script):
assert [name.name for name in ns] == ['spam', 'i']
assert ns[0].parent().name == ''
assert ns[0].parent().name == '__main__'
assert ns[0].parent().type == 'module'
assert ns[1].parent().name == 'spam'
assert ns[1].parent().type == 'function'
@@ -375,7 +375,7 @@ def test_type_II(Script):
"""
This tests the BaseDefinition.goto function, not the jedi
This tests the BaseName.goto function, not the jedi
function. They are not really different in functionality, but really
different as an implementation.
"""

View File

@@ -6,7 +6,7 @@ from textwrap import dedent
import pytest
from ..helpers import root_dir
from jedi.api.helpers import start_match, fuzzy_match
from jedi.api.helpers import _start_match, _fuzzy_match
from jedi._compatibility import scandir
@@ -92,11 +92,11 @@ def test_complete_expanduser(Script):
non_dots = [p for p in possibilities if not p.name.startswith('.') and len(p.name) > 1]
item = non_dots[0]
line = "'~%s%s'" % (os.sep, item.name)
s = Script(line, line=1, column=len(line)-1)
s = Script(line)
expected_name = item.name
if item.is_dir():
expected_name += os.path.sep
assert expected_name in [c.name for c in s.completions()]
assert expected_name in [c.name for c in s.complete(column=len(line)-1)]
def test_fake_subnodes(Script):
@@ -312,6 +312,10 @@ def test_file_path_completions(Script, file, code, column, expected):
assert [c.complete for c in comps] == expected
def test_file_path_should_have_completions(Script):
assert Script('r"').complete() # See GH #1503
_dict_keys_completion_tests = [
('ints[', 5, ['1', '50', Ellipsis]),
('ints[]', 5, ['1', '50', Ellipsis]),
@@ -399,15 +403,15 @@ def test_dict_keys_completions(Script, added_code, column, expected, skip_pre_py
def test_start_match():
assert start_match('Condition', 'C')
assert _start_match('Condition', 'C')
def test_fuzzy_match():
assert fuzzy_match('Condition', 'i')
assert not fuzzy_match('Condition', 'p')
assert fuzzy_match('Condition', 'ii')
assert not fuzzy_match('Condition', 'Ciito')
assert fuzzy_match('Condition', 'Cdiio')
assert _fuzzy_match('Condition', 'i')
assert not _fuzzy_match('Condition', 'p')
assert _fuzzy_match('Condition', 'ii')
assert not _fuzzy_match('Condition', 'Ciito')
assert _fuzzy_match('Condition', 'Cdiio')
def test_ellipsis_completion(Script):
@@ -446,7 +450,7 @@ def test_completion_cache(Script, module_injector):
@pytest.mark.parametrize('module', ['typing', 'os'])
def test_module_completions(Script, module):
for c in Script('import {module}; {module}.'.format(module=module)).completions():
for c in Script('import {module}; {module}.'.format(module=module)).complete():
# Just make sure that there are no errors
c.type
c.docstring()

View File

@@ -87,7 +87,7 @@ def test_version_info(Script):
sys.version_info"""))
c, = s.completions()
c, = s.complete()
assert c.docstring() == 'sys.version_info\n\nVersion information as a named tuple.'

View File

@@ -27,7 +27,7 @@ def test_find_system_environments():
@pytest.mark.parametrize(
'version',
['2.7', '3.4', '3.5', '3.6', '3.7']
['2.7', '3.5', '3.6', '3.7']
)
def test_versions(version):
try:
@@ -118,9 +118,10 @@ def test_create_environment_executable():
assert environment.executable == sys.executable
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
def test_get_default_environment_from_env_does_not_use_safe(tmpdir, monkeypatch):
fake_python = os.path.join(str(tmpdir), 'fake_python')
with open(fake_python, 'w') as f:
with open(fake_python, 'w', newline='') as f:
f.write('')
def _get_subprocess(self):

View File

@@ -1,5 +1,5 @@
"""
Tests for :attr:`.BaseDefinition.full_name`.
Tests for :attr:`.BaseName.full_name`.
There are three kinds of test:
@@ -121,3 +121,10 @@ def test_param_name(Script):
name, = Script('class X:\n def foo(bar): bar''').goto()
assert name.type == 'param'
assert name.full_name is None
def test_variable_in_func(Script):
names = Script('def f(): x = 3').get_names(all_scopes=True)
x = names[-1]
assert x.name == 'x'
assert x.full_name == '__main__.f.x'

View File

@@ -123,23 +123,17 @@ def _assert_interpreter_complete(source, namespace, completions,
def test_complete_raw_function():
from os.path import join
_assert_interpreter_complete('join("").up',
locals(),
['upper'])
_assert_interpreter_complete('join("").up', locals(), ['upper'])
def test_complete_raw_function_different_name():
from os.path import join as pjoin
_assert_interpreter_complete('pjoin("").up',
locals(),
['upper'])
_assert_interpreter_complete('pjoin("").up', locals(), ['upper'])
def test_complete_raw_module():
import os
_assert_interpreter_complete('os.path.join("a").up',
locals(),
['upper'])
_assert_interpreter_complete('os.path.join("a").up', locals(), ['upper'])
def test_complete_raw_instance():
@@ -148,31 +142,19 @@ def test_complete_raw_instance():
completions = ['time', 'timetz', 'timetuple']
if is_py3:
completions += ['timestamp']
_assert_interpreter_complete('(dt - dt).ti',
locals(),
completions)
_assert_interpreter_complete('(dt - dt).ti', locals(), completions)
def test_list():
array = ['haha', 1]
_assert_interpreter_complete('array[0].uppe',
locals(),
['upper'])
_assert_interpreter_complete('array[0].real',
locals(),
[])
_assert_interpreter_complete('array[0].uppe', locals(), ['upper'])
_assert_interpreter_complete('array[0].real', locals(), [])
# something different, no index given, still just return the right
_assert_interpreter_complete('array[int].real',
locals(),
['real'])
_assert_interpreter_complete('array[int()].real',
locals(),
['real'])
_assert_interpreter_complete('array[int].real', locals(), ['real'])
_assert_interpreter_complete('array[int()].real', locals(), ['real'])
# inexistent index
_assert_interpreter_complete('array[2].upper',
locals(),
['upper'])
_assert_interpreter_complete('array[2].upper', locals(), ['upper'])
def test_getattr():
@@ -186,9 +168,7 @@ def test_slice():
class Foo1:
bar = []
baz = 'xbarx'
_assert_interpreter_complete('getattr(Foo1, baz[1:-1]).append',
locals(),
['append'])
_assert_interpreter_complete('getattr(Foo1, baz[1:-1]).append', locals(), ['append'])
def test_getitem_side_effects():
@@ -698,5 +678,5 @@ def bar():
def test_string_annotation(annotations, result, code):
x = lambda foo: 1
x.__annotations__ = annotations
defs = jedi.Interpreter(code or 'x()', [locals()]).goto_definitions()
defs = jedi.Interpreter(code or 'x()', [locals()]).infer()
assert [d.name for d in defs] == result

View File

@@ -1,6 +1,9 @@
import os
import sys
from ..helpers import get_example_dir, set_cwd, root_dir
import pytest
from ..helpers import get_example_dir, set_cwd, root_dir, test_dir
from jedi import Interpreter
from jedi.api import Project, get_default_project
@@ -38,3 +41,113 @@ def test_load_save_project(tmpdir):
loaded = Project.load(tmpdir.strpath)
assert loaded.added_sys_path == ['/foo']
@pytest.mark.parametrize(
'string, full_names, kwargs', [
('test_load_save_project', ['test_api.test_project.test_load_save_project'], {}),
('test_load_savep', [], dict(complete=True)),
('test_load_save_p', ['test_api.test_project.test_load_save_project'],
dict(complete=True)),
('test_load_save_p', ['test_api.test_project.test_load_save_project'],
dict(complete=True, all_scopes=True)),
('some_search_test_var', [], {}),
('some_search_test_var', ['test_api.test_project.test_search.some_search_test_var'],
dict(all_scopes=True)),
('some_search_test_var', ['test_api.test_project.test_search.some_search_test_var'],
dict(complete=True, all_scopes=True)),
('sample_int', ['helpers.sample_int'], {}),
('sample_int', ['helpers.sample_int'], dict(all_scopes=True)),
('sample_int.real', ['stub:builtins.int.real'], {}),
('class sample_int.real', [], {}),
('foo sample_int.real', [], {}),
('def sample_int.real', ['stub:builtins.int.real'], {}),
('function sample_int.real', ['stub:builtins.int.real'], {}),
# With modules
('test_project.test_search', ['test_api.test_project.test_search'], {}),
('test_project.test_searc', ['test_api.test_project.test_search'], dict(complete=True)),
('test_api.test_project.test_search', ['test_api.test_project.test_search'], {}),
('test_api.test_project.test_sear', ['test_api.test_project.test_search'],
dict(complete=True)),
# With namespace
('implicit_namespace_package.ns1.pkg',
['examples.implicit_namespace_package.ns1.pkg'], {}),
('implicit_namespace_package.ns1.pkg.ns1_file',
['examples.implicit_namespace_package.ns1.pkg.ns1_file'], {}),
('examples.implicit_namespace_package.ns1.pkg.ns1_file',
['examples.implicit_namespace_package.ns1.pkg.ns1_file'], {}),
('implicit_namespace_package.ns1.pkg.',
['examples.implicit_namespace_package.ns1.pkg.ns1_file'],
dict(complete=True)),
('implicit_namespace_package.',
['examples.implicit_namespace_package.ns1',
'examples.implicit_namespace_package.ns2'],
dict(complete=True)),
# With stubs
('with_python.module', ['examples.stub_packages.with_python.module'], {}),
('with_python.modul', ['examples.stub_packages.with_python.module'],
dict(complete=True)),
('no_python.foo', ['stub:examples.stub_packages.no_python.foo'], {}),
('no_python.fo', ['stub:examples.stub_packages.no_python.foo'],
dict(complete=True)),
('with_python-stubs.module', [], {}),
('no_python-stubs.foo', [], {}),
# Both locations are given, because they live in separate folders (one
# suffixed with -stubs.
('with_python', ['examples.stub_packages.with_python'], {}),
('no_python', ['stub:examples.stub_packages.no_python'], {}),
# Completion stubs
('stub_only', ['stub:completion.stub_folder.stub_only',
'stub:examples.stub_packages.with_python.stub_only'], {}),
('with_stub', ['completion.stub_folder.with_stub'], {}),
('with_stub.in_with_stub_both',
['completion.stub_folder.with_stub.in_with_stub_both'], {}),
('with_stub.in_with_stub_python',
['completion.stub_folder.with_stub.in_with_stub_python'], {}),
('with_stub.in_with_stub_stub',
['stub:completion.stub_folder.with_stub.in_with_stub_stub'], {}),
# Completion stubs: Folder
('with_stub_folder', ['completion.stub_folder.with_stub_folder'], {}),
('with_stub_folder.nested_with_stub',
['completion.stub_folder.with_stub_folder.nested_with_stub'], {}),
('nested_with_stub',
['completion.stub_folder.stub_only_folder.nested_with_stub',
'completion.stub_folder.with_stub_folder.nested_with_stub'], {}),
# On sys path
('sys.path', ['stub:sys.path'], {}),
('json.dumps', ['json.dumps'], {}), # stdlib + stub
('multiprocessing', ['multiprocessing'], {}),
('multiprocessin', ['multiprocessing'], dict(complete=True)),
]
)
@pytest.mark.skipif(sys.version_info < (3, 6), reason="Ignore Python 2, because EOL")
def test_search(string, full_names, kwargs, skip_pre_python36):
some_search_test_var = 1.0
project = Project(test_dir)
if kwargs.pop('complete', False) is True:
defs = project.complete_search(string, **kwargs)
else:
defs = project.search(string, **kwargs)
assert [('stub:' if d.is_stub() else '') + d.full_name for d in defs] == full_names
@pytest.mark.parametrize(
'string, completions, all_scopes', [
('SomeCl', ['ass'], False),
('twic', [], False),
('twic', ['e', 'e'], True),
('test_load_save_p', ['roject'], False),
]
)
@pytest.mark.skipif(sys.version_info < (3, 6), reason="Ignore Python 2, because EOL")
def test_complete_search(Script, string, completions, all_scopes, skip_pre_python36):
project = Project(test_dir)
defs = project.complete_search(string, all_scopes=all_scopes)
assert [d.complete for d in defs] == completions

Some files were not shown because too many files have changed in this diff Show More