182 Commits

Author SHA1 Message Date
Dave Halter
005f69390c Write the CHANGELOG for 0.15.1 2019-08-13 00:18:45 +02:00
Matthias Bussonnier
ecca190462 Remove forgotten debug/print in filename completion. (#1380)
This is in the latest 0.15, and when forwarding path completions to
jedi, print a lot of stuff on the screen.
2019-08-12 12:37:21 +02:00
Dave Halter
5d0d09bb7d staticmethod and a few other cases might not have properly returned its signatures 2019-08-12 09:37:59 +02:00
Dave Halter
972cae4859 Remove reference to a file that doesn't exist anymore 2019-08-12 00:24:35 +02:00
Dave Halter
77bc2d548a Bump version to make it clear that it's a different one than the current one 2019-08-12 00:22:48 +02:00
Dave Halter
35e5cf2c2a A small Changelog improvement 2019-08-11 20:49:49 +02:00
Dave Halter
c6f0ecd223 Cleanup Changelog for the next release 2019-08-11 20:37:50 +02:00
Dave Halter
f727e4e661 Make it possible to access functions that were inherited, see #1347 2019-08-11 20:34:21 +02:00
Dave Halter
1ad4003740 Messed up a Windows test 2019-08-11 20:12:33 +02:00
Dave Halter
1108ad9994 Again a small windows issue fixed. 2019-08-11 20:01:12 +02:00
Dave Halter
f7f9b1e5ec Need to escape the path backslash for windows slashes 2019-08-11 19:56:57 +02:00
Dave Halter
c3d40949b1 Make it possible to access properties again
This time we catch all exceptions and try to avoid issues for the user.

This is only happening when working with an Interpreter. I don't feel this is
necessary otherwise.

See #1299
2019-08-11 16:24:19 +02:00
Dave Halter
a7accf4171 A small compatibility fix 2019-08-11 01:54:26 +02:00
Dave Halter
ab80646b86 Fix an issue with type vars that might have been a problem for other things as well 2019-08-11 01:28:09 +02:00
Dave Halter
3d0ac09fc9 Don't add quotes after paths if they are already there 2019-08-10 18:37:10 +02:00
Dave Halter
0a84678a60 A small speed optimization that helps a lot with sys.version_info >= (3, 0) patterns in typeshed 2019-08-10 15:31:36 +02:00
Dave Halter
4a5c992b1a Remove an unnecessary isinstance usage 2019-08-10 14:41:47 +02:00
Dave Halter
04b7c99753 Make CompiledValue lazy
This definitely reduces debug output and it might be slightly faster, because some values are never asked for
2019-08-10 14:36:40 +02:00
Dave Halter
499408657b A python 2 fix 2019-08-08 17:07:54 +02:00
Dave Halter
4ec3fb6e12 Fix an error that occured because of some refactorings 2019-08-08 11:03:27 +02:00
Dave Halter
463cbb1595 Fix one more os.path.join issue 2019-08-08 09:31:13 +02:00
Dave Halter
03608151e8 Fix more issues with os.path path completion 2019-08-08 01:48:25 +02:00
Dave Halter
822394663c Make join detection much easier 2019-08-08 01:04:08 +02:00
Dave Halter
52517f78b1 Fix some remaining issues with file path completions 2019-08-07 23:00:27 +02:00
Dave Halter
a191b7b458 A few more tests for path completions (join) 2019-08-07 21:11:48 +02:00
Dave Halter
e68273c0ff Fix quote completions for os.path.join path completions 2019-08-07 20:55:12 +02:00
Dave Halter
aeff5faa3d Fix first param argument of os.path.join file completions 2019-08-07 20:39:47 +02:00
Dave Halter
0fd3757a51 Fix arglist/trailer issues 2019-08-07 10:16:05 +02:00
Dave Halter
1b064c1078 in os.path.join completions, directories should not end in a slash 2019-08-07 01:37:58 +02:00
Dave Halter
5726c29385 Make some file path completions in os.path.join work 2019-08-07 01:34:46 +02:00
Dave Halter
7c1c4981fb Fix os.path.join static value gathering 2019-08-06 22:48:28 +02:00
Dave Halter
81488bcd20 os.path.sep should always have a clear value 2019-08-06 19:57:16 +02:00
Dave Halter
99008eef43 Fix string name completion for stuff like dirname and abspath 2019-08-06 19:38:16 +02:00
Dave Halter
3a9dc0ca2e Fix bytes issue with file path adding 2019-08-06 01:08:57 +02:00
Dave Halter
98a550e352 Python 2 compatibility 2019-08-06 00:42:02 +02:00
Dave Halter
4b8505b78d Make __file__ return the correct value 2019-08-06 00:30:31 +02:00
Dave Halter
b7c2bacbd2 Fix string additions when used in certain ways 2019-08-05 10:11:36 +02:00
Dave Halter
8108122347 Make string additions work for file path completion
With this most simple cases of file path completions should be working now, fixes #493
2019-08-05 01:43:50 +02:00
Dave Halter
45dada9552 Fix interpeter project path 2019-08-05 00:43:37 +02:00
Dave Halter
38e0cbc1d2 Fix the REPL completer for file path completions 2019-08-04 23:08:25 +02:00
Dave Halter
e008a515e3 Fix a few more file name completion cases 2019-08-04 22:43:23 +02:00
Dave Halter
fd1e6afd07 A first iteration for file path completions 2019-08-04 13:50:23 +02:00
Dave Halter
9dd088f3db Fix a test failure 2019-08-03 14:58:57 +02:00
Dave Halter
8e1417e3ce Add Definition.execute, fixes #1076 2019-08-03 02:01:30 +02:00
Dave Halter
97526aa320 Add tests to show that #516 is not working, yet 2019-08-02 22:31:26 +02:00
Dave Halter
16e0351897 List possible Definition.type in its docstring, fixes #1069. 2019-08-02 21:16:58 +02:00
Dave Halter
c0c7c949fd Start writing the Changelog for 0.15.0 2019-08-02 17:17:25 +02:00
Dave Halter
b8bc4060dd 3.8-dev should not be allowed to fail 2019-08-02 16:15:16 +02:00
Dave Halter
c737e3ee40 Skip more Python 2 tests 2019-08-02 15:54:10 +02:00
Dave Halter
4c3d4508e9 Skipping of tests was done the wrong way again 2019-08-02 15:50:06 +02:00
Dave Halter
70bcc9405f Skip the right tests 2019-08-02 15:25:20 +02:00
Dave Halter
6a82f60901 Parameter.kind is not avaialble in Python 3.5 2019-08-02 13:49:01 +02:00
Dave Halter
814998253a Fix Python 2 test issues 2019-08-02 13:44:04 +02:00
Dave Halter
a22c6da89f Add a few docstrings to make some things clearer 2019-08-02 13:16:18 +02:00
Dave Halter
876a6a5c22 Add ParamDefinition.kind, fixes #1361 2019-08-02 13:11:41 +02:00
Dave Halter
642e8f2aa6 Make it possible to format a param to a string, fixes #1074 2019-08-02 12:17:58 +02:00
Dave Halter
a64ef2759c Add another test for signature annotations 2019-08-02 12:17:58 +02:00
Dave Halter
d58bbce24f Add Signature.to_string() with proper tests, fixes #779, fixes #780 2019-08-02 12:17:13 +02:00
Dave Halter
ca6a7215e2 Test infer_default 2019-08-02 10:41:04 +02:00
Dave Halter
93b7548f1a Use a helper to create definitions 2019-08-02 10:30:23 +02:00
Dave Halter
24db05841b Add a execute_annotation option to infer_annotation 2019-08-02 10:24:15 +02:00
Dave Halter
375d1d57fb Test infer_annotation 2019-08-02 10:00:17 +02:00
Dave Halter
c2e50e1d0d Make it possible for users to infer annotations/defaults
Fixes #1039
2019-08-01 18:27:37 +02:00
Dave Halter
7988c1d11b A first iteration of adding signatures to the API, fixes #1139 2019-08-01 17:48:10 +02:00
Dave Halter
8ab2a5320e Fix a caching issue 2019-08-01 02:10:46 +02:00
Dave Halter
b5a62825ce Forgot the right resolve_stars parameters in one place 2019-07-31 23:05:24 +02:00
Dave Halter
ec70815318 Cache getting resolved param names 2019-07-31 22:54:29 +02:00
Dave Halter
a739c17a6f Turn around resolve_stars, it shouldn't by default be resolved 2019-07-31 18:51:31 +02:00
Dave Halter
ab5f4b6774 Remove a class that is not needed anymore 2019-07-31 18:44:57 +02:00
Dave Halter
a5a544cb09 Revert "Use __str__ instead of to_string"
This reverts commit 1151700114.
2019-07-31 18:39:17 +02:00
Dave Halter
7d2374ed81 Fix the last remaining issues with function signature 2019-07-31 18:29:41 +02:00
Dave Halter
97b642a3e1 overloaded_functions should be private 2019-07-31 00:11:08 +02:00
Dave Halter
1151700114 Use __str__ instead of to_string 2019-07-31 00:07:38 +02:00
Dave Halter
75f654b944 Better repr for CallSignature 2019-07-30 23:55:58 +02:00
Dave Halter
bb852c3e85 Fix some minor signature issues 2019-07-30 23:48:54 +02:00
Dave Halter
1fbb69b35a Remove the unused function signature_matches 2019-07-30 10:01:50 +02:00
Dave Halter
0352c3250a Fix signatures for __init__ calls when used with supers, fixes #1163 2019-07-30 01:44:53 +02:00
Dave Halter
268f828963 Fix some issues for args resolving in method calls 2019-07-30 01:28:51 +02:00
Dave Halter
21508a8c79 Remove a bit of code that i sprobably unused 2019-07-30 00:38:42 +02:00
Dave Halter
f9de26f72c Move get_signatures from Function to FunctionMixin 2019-07-29 20:17:03 +02:00
Dave Halter
22580f771c Merge the signature changes
Fixes include
- Better @wraps(func) understanding
- *args, **kwargs in call signatures is now resolved as well as possible

Fixes #503, fixes #1058
Also look at #906, #634, #1163
2019-07-29 00:31:08 +02:00
Dave Halter
9b338f69a6 Add a comment about wraps 2019-07-29 00:28:12 +02:00
Dave Halter
fa0424cfd6 Fix signatures for wraps, see #1058 2019-07-29 00:13:05 +02:00
Dave Halter
f6808a96e0 Skip pre python 3.5 2019-07-28 20:40:32 +02:00
Dave Halter
02bd7e5bc7 Some small args adaptions 2019-07-28 20:22:28 +02:00
Dave Halter
e8e3e8c111 Deal better with non-functions 2019-07-28 19:52:48 +02:00
Dave Halter
c8588191f9 Some more small fixes 2019-07-28 18:09:08 +02:00
Dave Halter
97e7f608df Don't return multiple used names for signatures 2019-07-28 17:51:40 +02:00
Dave Halter
fae2c8c060 Move args resolving to a different file 2019-07-28 17:41:28 +02:00
Dave Halter
b4f2d82867 A new approach of getting arguments 2019-07-28 17:31:17 +02:00
Dave Halter
6a480780f8 Some more tests 2019-07-26 14:51:30 +02:00
Dave Halter
41dc5382fa Make nesting of *args/**kwargs possible to understand. 2019-07-26 14:42:20 +02:00
Dave Halter
ba160e72ab Some more signature progress 2019-07-26 14:29:33 +02:00
Dave Halter
0703a69369 Some progress in signature understanding 2019-07-26 12:11:45 +02:00
Dave Halter
c490d37c2d Start getting signature inferring working 2019-07-26 02:54:50 +02:00
Dave Halter
84219236a7 Remove an import 2019-07-25 14:15:52 +02:00
Dave Halter
57fd995727 Small refactoring 2019-07-25 14:15:26 +02:00
Dave Halter
a803d687e2 Skipped Python 2 Interpreter tests the wrong way 2019-07-24 13:44:26 +02:00
Dave Halter
c7927fb141 Remove a paragraph in docs that was arguing that stubs and generics (and other things) were not properly supported, fixes #1012 2019-07-24 13:41:33 +02:00
Dave Halter
05d9602032 Fix partial signatures for MixedObject
Now a MixedObject return the signatures of its CompiledObject all the time, fixes #1371
2019-07-24 12:58:20 +02:00
Dave Halter
e76120da06 Fix partial signatures, fixes #1371 2019-07-24 02:28:49 +02:00
Dave Halter
25bbecc269 Make sure with a test that the staticmethod signature is also correct 2019-07-24 01:15:48 +02:00
Dave Halter
08bb9cfae7 Fix classmethod signature, fixes #498 2019-07-24 01:06:49 +02:00
Dave Halter
703b747a31 Deal with annotation on *args and **kwargs correctly, fixes #980 2019-07-23 23:56:30 +02:00
Dave Halter
ff149b74e0 Use LazyContextWrapper more 2019-07-23 13:59:08 +02:00
Dave Halter
3d08eb92d5 Very small refactoring 2019-07-23 13:08:57 +02:00
Johannes-Maria Frank
02d16ac55c Fix for failing assertion on native modules Issue #1354 (#1370) 2019-07-23 13:02:08 +02:00
Dave Halter
18eb7622ba Skip numpydoc tests for Python 2 2019-07-22 00:49:40 +02:00
Dave Halter
13dd173664 Remove code that didn't mean anything 2019-07-22 00:39:19 +02:00
Dave Halter
73c078ec7a Fix docstrings for wrapped functions, fixes #906 2019-07-21 12:19:22 +02:00
Dave Halter
cdf50e2a69 Fix an isue about dict ordering in Python before 3.6. 2019-07-19 12:54:22 +02:00
Dave Halter
2b0b29f921 Make it clearer when get_param is used. 2019-07-19 11:57:55 +02:00
Dave Halter
0dc60fb535 A small dataclass refactoring 2019-07-19 11:44:11 +02:00
Dave Halter
5722a3458e Evaluate annotations for dataclasses when infer is called on param 2019-07-19 11:42:08 +02:00
Dave Halter
93c52f615a Get inheritance of dataclass right 2019-07-19 11:35:13 +02:00
Dave Halter
050d686a27 A first working iteration of dataclass signatures, fixes #1213 2019-07-19 02:01:36 +02:00
Dave Halter
7156ddf607 Remove an unused function 2019-07-19 01:32:27 +02:00
Dave Halter
1cccc832b6 Dataclass progress 2019-07-19 01:27:37 +02:00
Dave Halter
fd4eca5e03 Add enum changes to changelog 2019-07-18 12:19:21 +02:00
Dave Halter
1d9b9cff47 Fix a recursion error about getting metaclasses 2019-07-18 12:02:27 +02:00
Dave Halter
f4fe113c0f One test about recursion issues only applied to Python 2 2019-07-18 12:00:47 +02:00
Dave Halter
c7fc715535 Use class filters in instances differently so metaclass plugins work, fixes #1090 2019-07-18 11:20:54 +02:00
Dave Halter
eeea88046e First step in working with metaclasses in plugins, see #1090. 2019-07-18 11:20:28 +02:00
Dave Halter
dea887d27d Refactor the plugin registry 2019-07-16 12:48:54 +02:00
Dave Halter
8329e2e969 Remove classes from plugins and use decorators instead 2019-07-16 10:23:19 +02:00
Dave Halter
60415033b4 Prepare the v0.14.1 release 2019-07-13 16:00:27 +02:00
Dave Halter
a06d760f45 Use fixture names everywhere 2019-07-10 23:26:59 -07:00
Dave Halter
b7687fcfb7 Cleanup a test file 2019-07-10 23:23:18 -07:00
Dave Halter
0ec86d5034 Use parametrize instead of TestCase 2019-07-10 23:22:10 -07:00
Dave Halter
cef23f44cd Remove a TestCase class usage 2019-07-10 19:32:19 -07:00
Dave Halter
e889a4923e Use pytest.mark.parametrize for something instad of a class 2019-07-10 19:04:12 -07:00
Dave Halter
114aba462c Use the names fixture even more 2019-07-10 19:00:24 -07:00
Dave Halter
26c7cec7b5 Use the names fixture more 2019-07-10 18:39:33 -07:00
Dave Halter
3e3a33ab79 A small rename 2019-07-10 18:38:24 -07:00
Dave Halter
7f386e0e68 Refactor names tests 2019-07-10 18:34:40 -07:00
Dave Halter
82d970d2b8 A small refactoring 2019-07-10 18:24:21 -07:00
Dave Halter
3ed9e836cc Make sure __wrapped__ works properly when using an Interpreter, fixes #1353 2019-07-10 16:12:57 -07:00
Dave Halter
f984e8d6ef Small refactoring 2019-07-10 15:38:41 -07:00
Dave Halter
670cf4d394 Make API param names appear without leading double underscores, fixes #1357 again 2019-07-10 12:10:12 -07:00
Dave Halter
e85fba844c Fix some call signature tests 2019-07-09 00:46:53 -07:00
Dave Halter
ee5557ddf6 Make expected index work in Python 3 2019-07-09 00:37:33 -07:00
Dave Halter
42f72b219b Test both closing brackets and non-closing brackets for CallSignature.index 2019-07-09 00:16:53 -07:00
Dave Halter
374721b789 Fix a case with errors 2019-07-09 00:04:53 -07:00
Dave Halter
01cec186ae Move some code around 2019-07-08 22:52:04 -07:00
Dave Halter
3fb89f9f9b Fix some kwargs cases 2019-07-08 22:38:22 -07:00
Dave Halter
a0b4e76c1a Fix some *args issues 2019-07-08 17:03:45 -07:00
Dave Halter
97bf83aa03 Deal better with some error nodes 2019-07-08 14:26:11 -07:00
Dave Halter
ca7658cab7 Delete unused code 2019-07-08 13:29:46 -07:00
Dave Halter
dd78f4cfbf Fix some error node handling for call signatures 2019-07-08 13:22:07 -07:00
Dave Halter
08019075c3 Fix CallSignature index for a looot of cases, fixes #1364,#1363 2019-07-08 12:40:58 -07:00
Dave Halter
943617a94f Use recursion rather than other stuff 2019-07-05 23:51:24 -07:00
Dave Halter
d579c0ad57 Even more refactorings 2019-07-05 15:24:39 -07:00
Dave Halter
76c6104415 small name refactoring 2019-07-05 14:35:48 -07:00
Dave Halter
ef9d803ce3 Refactor some call details 2019-07-05 14:30:59 -07:00
Dave Halter
a26cb42d07 Disable a test for Python 2 2019-07-04 09:31:22 -07:00
Dave Halter
6b9b2836ba Fix pow() signature, fixes #1357
This commit changes how params starting with __ are viewed as positional only params
2019-07-04 00:29:57 -07:00
Dave Halter
abdb8de89d Merge branch 'master' of github.com:davidhalter/jedi 2019-07-03 23:49:18 -07:00
Dave Halter
ac492ef598 Fix signature to_string 2019-07-03 23:44:58 -07:00
Dave Halter
947bfe7b78 Fix an issue with keyword params, fixes #1356 2019-07-03 22:35:46 -07:00
Dave Halter
be6c90d135 Simplify some test code for param defaults, see #1356 2019-07-03 19:43:32 -07:00
Dave Halter
8cb059deda Merge branch 'function_signature_in_interpreter' of https://github.com/linupi/jedi 2019-07-03 19:11:36 -07:00
Arnaud Limbourg
0f4da5c1cf Update link to YouCompleteMe
The source repository changed.
2019-07-03 14:20:31 -07:00
Dave Halter
de138e9114 Improve a bit of dataclasses support, so at least the attributes can be seen
see #1213
2019-07-03 09:21:57 -07:00
Linus Pithan
15bb9b29a2 adding test_kwarg_defaults to point out missing default value of kwargs in certain cases 2019-07-02 11:52:57 +02:00
Dave Halter
4c132d94b9 Make sure in tests that pep 0526 variables are also able to be used when using self, see #933 2019-07-01 23:34:28 -07:00
mwchase
925fc89447 Get typing.NewType working (#1344)
Squashed from the following commits:

* Update pep0484.py

(I don't think I want to know why the cursor jumped to the beginning of the line with every keystroke in GitHub's online editor. Change was entered backwards.)

* Added test for inline use of NewType. Currently assuming that wrapped instances should get the underlying type.

* Altered tests per https://github.com/davidhalter/jedi/issues/1015#issuecomment-356131566

* Add NewTypeFunction to typing evaluation module

* Update AUTHORS.txt

* Add a new test, and a speculative justification

For now, address only the second comment

* Copy code from third comment on the PR

From inspection, I *believe* I understand what this code is doing, and as such, I believe this should cause the new test I added in response to the second comment to fail, because that test is based on faulty assumptions.

* Explicitly discard the key from the tuple

* Update pep0484_typing.py

* Test for the wrapped type, not the wrapper "type"

* Change the return value from calling a NewType
2019-07-01 22:42:59 -07:00
Dave Halter
cb95dbc707 Cannot use pytest 5 yet 2019-07-01 22:30:59 -07:00
Dave Halter
1e3b6a201d Fix filters for classes and functions 2019-07-01 22:24:29 -07:00
Dave Halter
3829ef4785 Fix some small things to get more tests passing 2019-07-01 21:52:03 -07:00
Dave Halter
b382f06be0 A better repr for Definition 2019-07-01 19:40:06 -07:00
Dave Halter
94faceb57c Merge branch 'master' of github.com:davidhalter/jedi 2019-06-30 22:14:02 -07:00
Dave Halter
a9ff58683e Fix ClassVar filter for instances 2019-06-26 22:56:30 +02:00
Dave Halter
fafd6b2ac6 Keyword completions are no longer possible directly after a number, fixes #1085 2019-06-26 15:04:46 +02:00
Nelson, Karl E
344a03e6b2 Fix for EmptyCompiledName 2019-06-24 23:51:19 +02:00
Dave Halter
265abe1d08 Fix super call goto for multiple inheritance, fixes #1311 2019-06-24 09:53:56 +02:00
Dave Halter
ebdae87821 goto should always goto definitions, fixes #1304 2019-06-24 01:25:26 +02:00
Dave Halter
56ec79d62a Fix star imports checks, fixes #1235 2019-06-22 16:45:56 +02:00
Dave Halter
c413b486fb Actually import IsADirectoryError 2019-06-22 15:43:11 +02:00
Dave Halter
cb0a0d228a Add 3.8 to supported versions 2019-06-22 14:45:22 +02:00
Dave Halter
3ae4a154f9 Fix project search if a directory is called manage.py, fixes #1314 2019-06-22 14:04:32 +02:00
Dave Halter
aa2dc6be09 Return annotations for compiled objects now help to infer
However only if it's a type, if it's a string, it doesn't work, yet

Fixes #1347
2019-06-22 00:15:20 +02:00
Nathaniel J. Smith
a62ba86d7b Update parso requirement
Fixes #1348
2019-06-21 10:12:34 +02:00
72 changed files with 2881 additions and 770 deletions

View File

@@ -3,8 +3,6 @@ omit =
jedi/_compatibility.py
jedi/evaluate/compiled/subprocess/__main__.py
jedi/__main__.py
# Is statically analyzed and not actually executed.
jedi/evaluate/jedi_typing.py
# For now this is not being used.
jedi/refactoring.py

View File

@@ -15,9 +15,6 @@ env:
- JEDI_TEST_ENVIRONMENT=37
matrix:
allow_failures:
- python: pypy
- python: 3.8-dev
include:
- python: 3.6
env:

View File

@@ -51,5 +51,7 @@ 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>
Note: (@user) means a github user name.

View File

@@ -3,6 +3,40 @@
Changelog
---------
0.15.1 (2019-08-13)
+++++++++++++++++++
- Small bugfix and removal of a print statement
0.15.0 (2019-08-11)
+++++++++++++++++++
- Added file path completions, there's a **new ``Completion.type``** ``path``,
now. Example: ``'/ho`` -> ``'/home/``
- ``*args``/``**kwargs`` resolving. If possible Jedi replaces the parameters
with the actual alternatives.
- Better support for enums/dataclasses
- When using Interpreter, properties are now executed, since a lot of people
have complained about this. Discussion in #1299, #1347.
New APIs:
- ``Definition.get_signatures() -> List[Signature]``. Signatures are similar to
``CallSignature``. ``Definition.params`` is therefore deprecated.
- ``Signature.to_string()`` to format call signatures.
- ``Signature.params -> List[ParamDefinition]``, ParamDefinition has the
following additional attributes ``infer_default()``, ``infer_annotation()``,
``to_string()``, and ``kind``.
- ``Definition.execute() -> List[Definition]``, makes it possible to infer
return values of functions.
0.14.1 (2019-07-13)
+++++++++++++++++++
- CallSignature.index should now be working a lot better
- A couple of smaller bugfixes
0.14.0 (2019-06-20)
+++++++++++++++++++

View File

@@ -203,7 +203,7 @@ Acknowledgements
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
.. _youcompleteme: https://github.com/ycm-core/YouCompleteMe
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
.. _completor.vim: https://github.com/maralla/completor.vim
.. _Jedi.el: https://github.com/tkf/emacs-jedi

View File

@@ -105,6 +105,11 @@ def Script(environment):
return partial(jedi.Script, environment=environment)
@pytest.fixture(scope='session')
def names(environment):
return partial(jedi.names, environment=environment)
@pytest.fixture(scope='session')
def has_typing(environment):
if environment.version_info >= (3, 5, 0):
@@ -127,3 +132,27 @@ def skip_python2(environment):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
@pytest.fixture()
def skip_pre_python38(environment):
if environment.version_info < (3, 8):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
@pytest.fixture()
def skip_pre_python37(environment):
if environment.version_info < (3, 7):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()
@pytest.fixture()
def skip_pre_python35(environment):
if environment.version_info < (3, 5):
# This if is just needed to avoid that tests ever skip way more than
# they should for all Python versions.
pytest.skip()

View File

@@ -140,15 +140,8 @@ Most of the features in PEP-0484 are supported including the typing module
(for Python < 3.5 you have to do ``pip install typing`` to use these),
and forward references.
Things that are missing (and this is not an exhaustive list; some of these
are planned, others might be hard to implement and provide little worth):
You can also use stub files.
- annotating functions with comments: https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
- understanding ``typing.cast()``
- stub files: https://www.python.org/dev/peps/pep-0484/#stub-files
- ``typing.Callable``
- ``typing.TypeVar``
- User defined generic types: https://www.python.org/dev/peps/pep-0484/#user-defined-generic-types
**Sphinx style**

View File

@@ -33,7 +33,7 @@ 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.14.0'
__version__ = '0.15.1'
from jedi.api import Script, Interpreter, set_debug_function, \
preload_module, names
@@ -42,3 +42,6 @@ from jedi.api.environment import find_virtualenvs, find_system_environments, \
get_default_environment, InvalidPythonEnvironment, create_environment, \
get_system_environment
from jedi.api.exceptions import InternalError
# Finally load the internal plugins. This is only internal.
from jedi.plugins import registry
del registry

View File

@@ -361,9 +361,9 @@ except NameError:
FileNotFoundError = IOError
try:
NotADirectoryError = NotADirectoryError
IsADirectoryError = IsADirectoryError
except NameError:
NotADirectoryError = IOError
IsADirectoryError = IOError
try:
PermissionError = PermissionError

View File

@@ -27,7 +27,7 @@ from jedi.api import interpreter
from jedi.api import helpers
from jedi.api.completion import Completion
from jedi.api.environment import InterpreterEnvironment
from jedi.api.project import get_default_project
from jedi.api.project import get_default_project, Project
from jedi.evaluate import Evaluator
from jedi.evaluate import imports
from jedi.evaluate import usages
@@ -144,7 +144,6 @@ class Script(object):
'(0-%d) for line %d (%r).' % (
column, line_len, line, line_string))
self._pos = line, column
self._path = path
cache.clear_time_caches()
debug.reset_time()
@@ -365,19 +364,18 @@ class Script(object):
:rtype: list of :class:`classes.CallSignature`
"""
call_signature_details = \
helpers.get_call_signature_details(self._module_node, self._pos)
if call_signature_details is None:
call_details = helpers.get_call_signature_details(self._module_node, self._pos)
if call_details is None:
return []
context = self._evaluator.create_context(
self._get_module(),
call_signature_details.bracket_leaf
call_details.bracket_leaf
)
definitions = helpers.cache_call_signatures(
self._evaluator,
context,
call_signature_details.bracket_leaf,
call_details.bracket_leaf,
self._code_lines,
self._pos
)
@@ -385,10 +383,7 @@ class Script(object):
# TODO here we use stubs instead of the actual contexts. We should use
# the signatures from stubs, but the actual contexts, probably?!
return [classes.CallSignature(self._evaluator, signature,
call_signature_details.bracket_leaf.start_pos,
call_signature_details.call_index,
call_signature_details.keyword_name_str)
return [classes.CallSignature(self._evaluator, signature, call_details)
for signature in definitions.get_signatures()]
def _analysis(self):
@@ -441,6 +436,7 @@ class Interpreter(Script):
>>> print(script.completions()[0].name)
upper
"""
_allow_descriptor_getattr_default = True
def __init__(self, source, namespaces, **kwds):
"""
@@ -468,8 +464,10 @@ 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, **kwds)
super(Interpreter, self).__init__(source, environment=environment,
_project=Project(os.getcwd()), **kwds)
self.namespaces = namespaces
self._evaluator.allow_descriptor_getattr = self._allow_descriptor_getattr_default
def _get_module(self):
return interpreter.MixedModuleContext(

View File

@@ -4,10 +4,9 @@ These classes are the much bigger part of the whole API, because they contain
the interesting information about completion and goto operations.
"""
import re
import sys
import warnings
from parso.python.tree import search_ancestor
from jedi import settings
from jedi import debug
from jedi.evaluate.utils import unite
@@ -38,6 +37,10 @@ def defined_names(evaluator, context):
return [Definition(evaluator, n) for n in _sort_names_by_start_pos(names)]
def _contexts_to_definitions(contexts):
return [Definition(c.evaluator, c.name) for c in contexts]
class BaseDefinition(object):
_mapping = {
'posixpath': 'os.path',
@@ -150,6 +153,9 @@ class BaseDefinition(object):
>>> defs[3]
'function'
Valid values for are ``module``, ``class``, ``instance``, ``function``,
``param``, ``path`` and ``keyword``.
"""
tree_name = self._name.tree_name
resolve = False
@@ -330,6 +336,8 @@ class BaseDefinition(object):
@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.
"""
@@ -337,7 +345,10 @@ class BaseDefinition(object):
# with overloading.
for context in self._name.infer():
for signature in context.get_signatures():
return [Definition(self._evaluator, n) for n in signature.get_param_names()]
return [
Definition(self._evaluator, n)
for n in signature.get_param_names(resolve_stars=True)
]
if self.type == 'function' or self.type == 'class':
# Fallback, if no signatures were defined (which is probably by
@@ -358,9 +369,10 @@ class BaseDefinition(object):
return Definition(self._evaluator, context.name)
def __repr__(self):
return "<%s full_name=%r, description=%r>" % (
return "<%s %sname=%r, description=%r>" % (
self.__class__.__name__,
self.full_name,
'full_' if self.full_name else '',
self.full_name or self.name,
self.description,
)
@@ -383,6 +395,12 @@ class BaseDefinition(object):
start_index = max(index - before, 0)
return ''.join(lines[start_index:index + after + 1])
def get_signatures(self):
return [Signature(self._evaluator, s) for s in self._name.infer().get_signatures()]
def execute(self):
return _contexts_to_definitions(self._name.infer().execute_evaluated())
class Completion(BaseDefinition):
"""
@@ -529,17 +547,13 @@ class Definition(BaseDefinition):
"""
typ = self.type
tree_name = self._name.tree_name
if typ == 'param':
return typ + ' ' + self._name.to_string()
if typ in ('function', 'class', 'module', 'instance') or tree_name is None:
if typ == 'function':
# For the description we want a short and a pythonic way.
typ = 'def'
return typ + ' ' + self._name.string_name
elif typ == 'param':
code = search_ancestor(tree_name, 'param').get_code(
include_prefix=False,
include_comma=False
)
return typ + ' ' + code
definition = tree_name.get_definition() or tree_name
# Remove the prefix, because that's not what we want for get_code
@@ -601,17 +615,38 @@ class Definition(BaseDefinition):
return hash((self._name.start_pos, self.module_path, self.name, self._evaluator))
class CallSignature(Definition):
class Signature(Definition):
"""
`CallSignature` objects is the return value of `Script.function_definition`.
`Signature` objects is the return value of `Script.function_definition`.
It knows what functions you are currently in. e.g. `isinstance(` would
return the `isinstance` function. without `(` it would return nothing.
"""
def __init__(self, evaluator, signature, bracket_start_pos, index, key_name_str):
super(CallSignature, self).__init__(evaluator, signature.name)
self._index = index
self._key_name_str = key_name_str
self._bracket_start_pos = bracket_start_pos
def __init__(self, evaluator, signature):
super(Signature, self).__init__(evaluator, signature.name)
self._signature = signature
@property
def params(self):
"""
:return list of ParamDefinition:
"""
return [ParamDefinition(self._evaluator, n)
for n in self._signature.get_param_names(resolve_stars=True)]
def to_string(self):
return self._signature.to_string()
class CallSignature(Signature):
"""
`CallSignature` objects is the return value of `Script.call_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.
"""
def __init__(self, evaluator, signature, call_details):
super(CallSignature, self).__init__(evaluator, signature)
self._call_details = call_details
self._signature = signature
@property
@@ -620,53 +655,60 @@ class CallSignature(Definition):
The Param index of the current call.
Returns None if the index cannot be found in the curent call.
"""
if self._key_name_str is not None:
for i, param in enumerate(self.params):
if self._key_name_str == param.name:
return i
if self.params:
param_name = self.params[-1]._name
if param_name.tree_name is not None:
if param_name.tree_name.get_definition().star_count == 2:
return i
return None
if self._index >= len(self.params):
for i, param in enumerate(self.params):
tree_name = param._name.tree_name
if tree_name is not None:
# *args case
if tree_name.get_definition().star_count == 1:
return i
return None
return self._index
@property
def params(self):
return [Definition(self._evaluator, n) for n in self._signature.get_param_names()]
return self._call_details.calculate_index(
self._signature.get_param_names(resolve_stars=True)
)
@property
def bracket_start(self):
"""
The indent of the bracket that is responsible for the last function
call.
The line/column of the bracket that is responsible for the last
function call.
"""
return self._bracket_start_pos
@property
def _params_str(self):
return ', '.join([p.description[6:]
for p in self.params])
return self._call_details.bracket_leaf.start_pos
def __repr__(self):
return '<%s: %s index=%r params=[%s]>' % (
return '<%s: index=%r %s>' % (
type(self).__name__,
self._name.string_name,
self._index,
self._params_str,
self.index,
self._signature.to_string(),
)
class ParamDefinition(Definition):
def infer_default(self):
"""
:return list of Definition:
"""
return _contexts_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.
"""
return _contexts_to_definitions(self._name.infer_annotation(**kwargs))
def to_string(self):
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`.
No support for Python < 3.4 anymore.
"""
if sys.version_info < (3, 5):
raise NotImplementedError(
'Python 2 is end-of-life, the new feature is not available for it'
)
return self._name.get_kind()
def _format_signatures(context):
return '\n'.join(
signature.to_string()

View File

@@ -1,3 +1,5 @@
import re
from parso.python.token import PythonTokenTypes
from parso.python import tree
from parso.tree import search_ancestor, Leaf
@@ -7,12 +9,13 @@ from jedi import debug
from jedi import settings
from jedi.api import classes
from jedi.api import helpers
from jedi.evaluate import imports
from jedi.api import keywords
from jedi.api.file_name import file_name_completions
from jedi.evaluate import imports
from jedi.evaluate.helpers import evaluate_call_of_leaf, parse_dotted_names
from jedi.evaluate.filters import get_global_filters
from jedi.evaluate.gradual.conversion import convert_contexts
from jedi.parser_utils import get_statement_of_position
from jedi.parser_utils import get_statement_of_position, cut_value_at_position
def get_call_signature_param_names(call_signatures):
@@ -82,7 +85,7 @@ def get_flow_scope_node(module_node, position):
class Completion:
def __init__(self, evaluator, module, code_lines, position, call_signatures_method):
def __init__(self, evaluator, module, code_lines, position, call_signatures_callback):
self._evaluator = evaluator
self._module_context = module
self._module_node = module.tree_node
@@ -92,11 +95,23 @@ class Completion:
self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
# The actual cursor position is not what we need to calculate
# everything. We want the start of the name we're on.
self._original_position = position
self._position = position[0], position[1] - len(self._like_name)
self._call_signatures_method = call_signatures_method
self._call_signatures_callback = call_signatures_callback
def completions(self):
completion_names = self._get_context_completions()
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
string, start_leaf = _extract_string_while_in_string(leaf, self._position)
if string is not None:
completions = list(file_name_completions(
self._evaluator, self._module_context, start_leaf, string,
self._like_name, self._call_signatures_callback,
self._code_lines, self._original_position
))
if completions:
return completions
completion_names = self._get_context_completions(leaf)
completions = filter_names(self._evaluator, completion_names,
self.stack, self._like_name)
@@ -105,7 +120,7 @@ class Completion:
x.name.startswith('_'),
x.name.lower()))
def _get_context_completions(self):
def _get_context_completions(self, leaf):
"""
Analyzes the context that a completion is made in and decides what to
return.
@@ -121,19 +136,20 @@ class Completion:
"""
grammar = self._evaluator.grammar
self.stack = stack = None
try:
self.stack = stack = helpers.get_stack_at_position(
grammar, self._code_lines, self._module_node, self._position
grammar, self._code_lines, leaf, self._position
)
except helpers.OnErrorLeaf as e:
self.stack = stack = None
if e.error_leaf.value == '.':
value = e.error_leaf.value
if value == '.':
# After ErrorLeaf's that are dots, we will not do any
# completions since this probably just confuses the user.
return []
# If we don't have a context, just use global completion.
# If we don't have a context, just use global completion.
return self._global_completions()
allowed_transitions = \
@@ -171,7 +187,10 @@ class Completion:
elif type_ == 'for_stmt':
allowed_transitions.append('else')
completion_names = list(self._get_keyword_completion_names(allowed_transitions))
completion_names = []
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
if not current_line or current_line[-1] in ' \t.;':
completion_names += self._get_keyword_completion_names(allowed_transitions)
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
PythonTokenTypes.INDENT)):
@@ -207,7 +226,7 @@ class Completion:
completion_names += self._get_class_context_completions(is_function=False)
if 'trailer' in nonterminals:
call_signatures = self._call_signatures_method()
call_signatures = self._call_signatures_callback()
completion_names += get_call_signature_param_names(call_signatures)
return completion_names
@@ -286,3 +305,22 @@ class Completion:
# TODO we should probably check here for properties
if (name.api_type == 'function') == is_function:
yield name
def _extract_string_while_in_string(leaf, position):
if leaf.type == 'string':
match = re.match(r'^\w*(\'{3}|"{3}|\'|")', leaf.value)
quote = match.group(1)
if leaf.line == position[0] and position[1] < leaf.column + match.end():
return None, None
if leaf.end_pos[0] == position[0] and position[1] > leaf.end_pos[1] - len(quote):
return None, None
return cut_value_at_position(leaf, position)[match.end():], leaf
leaves = []
while leaf is not None and leaf.line == position[0]:
if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value):
return ''.join(l.get_code() for l in leaves), leaf
leaves.insert(0, leaf)
leaf = leaf.get_previous_leaf()
return None, None

161
jedi/api/file_name.py Normal file
View File

@@ -0,0 +1,161 @@
import os
from jedi._compatibility import FileNotFoundError, force_unicode
from jedi.evaluate.names import AbstractArbitraryName
from jedi.api import classes
from jedi.evaluate.helpers import get_str_or_none
from jedi.parser_utils import get_string_quote
def file_name_completions(evaluator, module_context, start_leaf, string,
like_name, call_signatures_callback, code_lines, position):
# First we want to find out what can actually be changed as a name.
like_name_length = len(os.path.basename(string) + like_name)
addition = _get_string_additions(module_context, start_leaf)
if addition is None:
return
string = addition + string
# Here we use basename again, because if strings are added like
# `'foo' + 'bar`, it should complete to `foobar/`.
must_start_with = os.path.basename(string) + like_name
string = os.path.dirname(string)
sigs = call_signatures_callback()
is_in_os_path_join = sigs and all(s.full_name == 'os.path.join' for s in sigs)
if is_in_os_path_join:
to_be_added = _add_os_path_join(module_context, start_leaf, sigs[0].bracket_start)
if to_be_added is None:
is_in_os_path_join = False
else:
string = to_be_added + string
base_path = os.path.join(evaluator.project._path, string)
try:
listed = os.listdir(base_path)
except FileNotFoundError:
return
for name in listed:
if name.startswith(must_start_with):
path_for_name = os.path.join(base_path, name)
if is_in_os_path_join or not os.path.isdir(path_for_name):
if start_leaf.type == 'string':
quote = get_string_quote(start_leaf)
else:
assert start_leaf.type == 'error_leaf'
quote = start_leaf.value
potential_other_quote = \
code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
# Add a quote if it's not already there.
if quote != potential_other_quote:
name += quote
else:
name += os.path.sep
yield classes.Completion(
evaluator,
FileName(evaluator, name[len(must_start_with) - like_name_length:]),
stack=None,
like_name_length=like_name_length
)
def _get_string_additions(module_context, start_leaf):
def iterate_nodes():
node = addition.parent
was_addition = True
for child_node in reversed(node.children[:node.children.index(addition)]):
if was_addition:
was_addition = False
yield child_node
continue
if child_node != '+':
break
was_addition = True
addition = start_leaf.get_previous_leaf()
if addition != '+':
return ''
context = module_context.create_context(start_leaf)
return _add_strings(context, reversed(list(iterate_nodes())))
def _add_strings(context, nodes, add_slash=False):
string = ''
first = True
for child_node in nodes:
contexts = context.eval_node(child_node)
if len(contexts) != 1:
return None
c, = contexts
s = get_str_or_none(c)
if s is None:
return None
if not first and add_slash:
string += os.path.sep
string += force_unicode(s)
first = False
return string
class FileName(AbstractArbitraryName):
api_type = u'path'
is_context_name = False
def _add_os_path_join(module_context, start_leaf, bracket_start):
def check(maybe_bracket, nodes):
if maybe_bracket.start_pos != bracket_start:
return None
if not nodes:
return ''
context = module_context.create_context(nodes[0])
return _add_strings(context, nodes, add_slash=True) or ''
if start_leaf.type == 'error_leaf':
# Unfinished string literal, like `join('`
context_node = start_leaf.parent
index = context_node.children.index(start_leaf)
if index > 0:
error_node = context_node.children[index - 1]
if error_node.type == 'error_node' and len(error_node.children) >= 2:
index = -2
if error_node.children[-1].type == 'arglist':
arglist_nodes = error_node.children[-1].children
index -= 1
else:
arglist_nodes = []
return check(error_node.children[index + 1], arglist_nodes[::2])
return None
# Maybe an arglist or some weird error case. Therefore checked below.
searched_node_child = start_leaf
while searched_node_child.parent is not None \
and searched_node_child.parent.type not in ('arglist', 'trailer', 'error_node'):
searched_node_child = searched_node_child.parent
if searched_node_child.get_first_leaf() is not start_leaf:
return None
searched_node = searched_node_child.parent
if searched_node is None:
return None
index = searched_node.children.index(searched_node_child)
arglist_nodes = searched_node.children[:index]
if searched_node.type == 'arglist':
trailer = searched_node.parent
if trailer.type == 'error_node':
trailer_index = trailer.children.index(searched_node)
assert trailer_index >= 2
assert trailer.children[trailer_index - 1] == '('
return check(trailer.children[trailer_index - 1], arglist_nodes[::2])
elif trailer.type == 'trailer':
return check(trailer.children[0], arglist_nodes[::2])
elif searched_node.type == 'trailer':
return check(searched_node.children[0], [])
elif searched_node.type == 'error_node':
# Stuff like `join(""`
return check(arglist_nodes[-1], [])

View File

@@ -8,7 +8,7 @@ from textwrap import dedent
from parso.python.parser import Parser
from parso.python import tree
from jedi._compatibility import u
from jedi._compatibility import u, Parameter
from jedi.evaluate.base_context import NO_CONTEXTS
from jedi.evaluate.syntax_tree import eval_atom
from jedi.evaluate.helpers import evaluate_call_of_leaf
@@ -54,8 +54,7 @@ class OnErrorLeaf(Exception):
return self.args[0]
def _get_code_for_stack(code_lines, module_node, position):
leaf = module_node.get_leaf_for_position(position, include_prefixes=True)
def _get_code_for_stack(code_lines, leaf, position):
# It might happen that we're on whitespace or on a comment. This means
# that we would not get the right leaf.
if leaf.start_pos >= position:
@@ -95,7 +94,7 @@ def _get_code_for_stack(code_lines, module_node, position):
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
def get_stack_at_position(grammar, code_lines, module_node, pos):
def get_stack_at_position(grammar, code_lines, leaf, pos):
"""
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
"""
@@ -119,7 +118,7 @@ def get_stack_at_position(grammar, code_lines, module_node, pos):
yield token
# The code might be indedented, just remove it.
code = dedent(_get_code_for_stack(code_lines, module_node, pos))
code = dedent(_get_code_for_stack(code_lines, leaf, pos))
# We use a word to tell Jedi when we have reached the start of the
# completion.
# Use Z as a prefix because it's not part of a number suffix.
@@ -159,10 +158,139 @@ def evaluate_goto_definition(evaluator, context, leaf):
return definitions
CallSignatureDetails = namedtuple(
'CallSignatureDetails',
['bracket_leaf', 'call_index', 'keyword_name_str']
)
class CallDetails(object):
def __init__(self, bracket_leaf, children, position):
['bracket_leaf', 'call_index', 'keyword_name_str']
self.bracket_leaf = bracket_leaf
self._children = children
self._position = position
@property
def index(self):
return _get_index_and_key(self._children, self._position)[0]
@property
def keyword_name_str(self):
return _get_index_and_key(self._children, self._position)[1]
def calculate_index(self, param_names):
positional_count = 0
used_names = set()
star_count = -1
args = list(_iter_arguments(self._children, self._position))
if not args:
if param_names:
return 0
else:
return None
is_kwarg = False
for i, (star_count, key_start, had_equal) in enumerate(args):
is_kwarg |= had_equal | (star_count == 2)
if star_count:
pass # For now do nothing, we don't know what's in there here.
else:
if i + 1 != len(args): # Not last
if had_equal:
used_names.add(key_start)
else:
positional_count += 1
for i, param_name in enumerate(param_names):
kind = param_name.get_kind()
if not is_kwarg:
if kind == Parameter.VAR_POSITIONAL:
return i
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.POSITIONAL_ONLY):
if i == positional_count:
return i
if key_start is not None and not star_count == 1 or star_count == 2:
if param_name.string_name not in used_names \
and (kind == Parameter.KEYWORD_ONLY
or kind == Parameter.POSITIONAL_OR_KEYWORD
and positional_count <= i):
if star_count:
return i
if had_equal:
if param_name.string_name == key_start:
return i
else:
if param_name.string_name.startswith(key_start):
return i
if kind == Parameter.VAR_KEYWORD:
return i
return None
def _iter_arguments(nodes, position):
def remove_after_pos(name):
if name.type != 'name':
return None
return name.value[:position[1] - name.start_pos[1]]
# Returns Generator[Tuple[star_count, Optional[key_start: str], had_equal]]
nodes_before = [c for c in nodes if c.start_pos < position]
if nodes_before[-1].type == 'arglist':
for x in _iter_arguments(nodes_before[-1].children, position):
yield x # Python 2 :(
return
previous_node_yielded = False
stars_seen = 0
for i, node in enumerate(nodes_before):
if node.type == 'argument':
previous_node_yielded = True
first = node.children[0]
second = node.children[1]
if second == '=':
if second.start_pos < position:
yield 0, first.value, True
else:
yield 0, remove_after_pos(first), False
elif first in ('*', '**'):
yield len(first.value), remove_after_pos(second), False
else:
# Must be a Comprehension
first_leaf = node.get_first_leaf()
if first_leaf.type == 'name' and first_leaf.start_pos >= position:
yield 0, remove_after_pos(first_leaf), False
else:
yield 0, None, False
stars_seen = 0
elif node.type in ('testlist', 'testlist_star_expr'): # testlist is Python 2
for n in node.children[::2]:
if n.type == 'star_expr':
stars_seen = 1
n = n.children[1]
yield stars_seen, remove_after_pos(n), False
stars_seen = 0
# The count of children is even if there's a comma at the end.
previous_node_yielded = bool(len(node.children) % 2)
elif isinstance(node, tree.PythonLeaf) and node.value == ',':
if not previous_node_yielded:
yield stars_seen, '', False
stars_seen = 0
previous_node_yielded = False
elif isinstance(node, tree.PythonLeaf) and node.value in ('*', '**'):
stars_seen = len(node.value)
elif node == '=' and nodes_before[-1]:
previous_node_yielded = True
before = nodes_before[i - 1]
if before.type == 'name':
yield 0, before.value, True
else:
yield 0, None, False
# Just ignore the star that is probably a syntax error.
stars_seen = 0
if not previous_node_yielded:
if nodes_before[-1].type == 'name':
yield stars_seen, remove_after_pos(nodes_before[-1]), False
else:
yield stars_seen, '', False
def _get_index_and_key(nodes, position):
@@ -171,23 +299,22 @@ def _get_index_and_key(nodes, position):
"""
nodes_before = [c for c in nodes if c.start_pos < position]
if nodes_before[-1].type == 'arglist':
nodes_before = [c for c in nodes_before[-1].children if c.start_pos < position]
return _get_index_and_key(nodes_before[-1].children, position)
key_str = None
if nodes_before:
last = nodes_before[-1]
if last.type == 'argument' and last.children[1] == '=' \
and last.children[1].end_pos <= position:
# Checked if the argument
key_str = last.children[0].value
elif last == '=':
key_str = nodes_before[-2].value
last = nodes_before[-1]
if last.type == 'argument' and last.children[1] == '=' \
and last.children[1].end_pos <= position:
# Checked if the argument
key_str = last.children[0].value
elif last == '=':
key_str = nodes_before[-2].value
return nodes_before.count(','), key_str
def _get_call_signature_details_from_error_node(node, position):
def _get_call_signature_details_from_error_node(node, additional_children, position):
for index, element in reversed(list(enumerate(node.children))):
# `index > 0` means that it's a trailer and not an atom.
if element == '(' and element.end_pos <= position and index > 0:
@@ -198,10 +325,7 @@ def _get_call_signature_details_from_error_node(node, position):
if name is None:
continue
if name.type == 'name' or name.parent.type in ('trailer', 'atom'):
return CallSignatureDetails(
element,
*_get_index_and_key(children, position)
)
return CallDetails(element, children + additional_children, position)
def get_call_signature_details(module, position):
@@ -213,6 +337,7 @@ def get_call_signature_details(module, position):
return None
if leaf == ')':
# TODO is this ok?
if leaf.end_pos == position:
leaf = leaf.get_next_leaf()
@@ -225,18 +350,25 @@ def get_call_signature_details(module, position):
# makes it feel strange to have a call signature.
return None
for n in node.children[::-1]:
if n.start_pos < position and n.type == 'error_node':
result = _get_call_signature_details_from_error_node(n, position)
if result is not None:
return result
additional_children = []
for n in reversed(node.children):
if n.start_pos < position:
if n.type == 'error_node':
result = _get_call_signature_details_from_error_node(
n, additional_children, position
)
if result is not None:
return result
additional_children[0:0] = n.children
continue
additional_children.insert(0, n)
if node.type == 'trailer' and node.children[0] == '(':
leaf = node.get_previous_leaf()
if leaf is None:
return None
return CallSignatureDetails(
node.children[0], *_get_index_and_key(node.children, position))
return CallDetails(node.children[0], node.children, position)
node = node.parent

View File

@@ -1,7 +1,7 @@
import pydoc
from jedi.evaluate.utils import ignored
from jedi.evaluate.names import AbstractNameDefinition
from jedi.evaluate.names import AbstractArbitraryName
try:
from pydoc_data import topics as pydoc_topics
@@ -19,14 +19,8 @@ def get_operator(evaluator, string, pos):
return Keyword(evaluator, string, pos)
class KeywordName(AbstractNameDefinition):
class KeywordName(AbstractArbitraryName):
api_type = u'keyword'
is_context_name = False
def __init__(self, evaluator, name):
self.evaluator = evaluator
self.string_name = name
self.parent_context = evaluator.builtins_module
def infer(self):
return [Keyword(self.evaluator, self.string_name, (0, 0))]

View File

@@ -1,7 +1,7 @@
import os
import json
from jedi._compatibility import FileNotFoundError, NotADirectoryError, PermissionError
from jedi._compatibility import FileNotFoundError, PermissionError, IsADirectoryError
from jedi.api.environment import SameEnvironment, \
get_cached_default_environment
from jedi.api.exceptions import WrongVersion
@@ -153,7 +153,7 @@ def _is_django_path(directory):
try:
with open(os.path.join(directory, 'manage.py'), 'rb') as f:
return b"DJANGO_SETTINGS_MODULE" in f.read()
except (FileNotFoundError, NotADirectoryError, PermissionError):
except (FileNotFoundError, IsADirectoryError, PermissionError):
return False
return False
@@ -169,7 +169,7 @@ def get_default_project(path=None):
for dir in traverse_parents(check, include_current=True):
try:
return Project.load(dir)
except (FileNotFoundError, NotADirectoryError, PermissionError):
except (FileNotFoundError, IsADirectoryError, PermissionError):
pass
if first_no_init_file is None:

View File

@@ -62,8 +62,6 @@ I need to mention now that lazy evaluation is really good because it
only *evaluates* what needs to be *evaluated*. All the statements and modules
that are not used are just being ignored.
"""
from functools import partial
from parso.python import tree
import parso
from parso import python_bytes_to_unicode
@@ -84,14 +82,7 @@ from jedi.evaluate.context import ClassContext, FunctionContext, \
from jedi.evaluate.context.iterable import CompForContext
from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
eval_node, check_tuple_assignments
def _execute(context, arguments):
debug.dbg('execute: %s %s', context, arguments)
with debug.increase_indent_cm():
context_set = context.py__call__(arguments=arguments)
debug.dbg('execute result: %s in %s', context_set, context)
return context_set
from jedi.plugins import plugin_manager
class Evaluator(object):
@@ -115,28 +106,26 @@ class Evaluator(object):
self.is_analysis = False
self.project = project
self.access_cache = {}
self.allow_descriptor_getattr = False
self.reset_recursion_limitations()
self.allow_different_encoding = True
# Plugin API
from jedi.plugins import plugin_manager
plugin_callbacks = plugin_manager.get_callbacks(self)
self.execute = plugin_callbacks.decorate('execute', callback=_execute)
self._import_module = partial(
plugin_callbacks.decorate(
'import_module',
callback=imports.import_module
),
self,
)
def import_module(self, import_names, parent_module_context=None,
sys_path=None, prefer_stubs=True):
if sys_path is None:
sys_path = self.get_sys_path()
return self._import_module(import_names, parent_module_context,
sys_path, prefer_stubs=prefer_stubs)
return imports.import_module(self, import_names, parent_module_context,
sys_path, prefer_stubs=prefer_stubs)
@staticmethod
@plugin_manager.decorate()
def execute(context, arguments):
debug.dbg('execute: %s %s', context, arguments)
with debug.increase_indent_cm():
context_set = context.py__call__(arguments=arguments)
debug.dbg('execute result: %s in %s', context_set, context)
return context_set
@property
@evaluator_function_cache()
@@ -318,11 +307,11 @@ class Evaluator(object):
return [TreeNameDefinition(context, name)]
elif type_ == 'param':
return [ParamName(context, name)]
elif type_ in ('funcdef', 'classdef'):
return [TreeNameDefinition(context, name)]
elif type_ in ('import_from', 'import_name'):
module_names = imports.infer_import(context, name, is_goto=True)
return module_names
else:
return [TreeNameDefinition(context, name)]
else:
contexts = self._follow_error_node_imports_if_possible(context, name)
if contexts is not None:

View File

@@ -11,6 +11,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
from jedi.evaluate.names import ParamName, TreeNameDefinition
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet, ContextualizedNode
from jedi.evaluate.context import iterable
from jedi.evaluate.cache import evaluator_as_method_param_cache
from jedi.evaluate.param import get_executed_params_and_issues, ExecutedParam
@@ -35,7 +36,7 @@ class ParamIssue(Exception):
pass
def repack_with_argument_clinic(string, keep_arguments_param=False):
def repack_with_argument_clinic(string, keep_arguments_param=False, keep_callback_param=False):
"""
Transforms a function or method with arguments to the signature that is
given as an argument clinic notation.
@@ -54,6 +55,8 @@ def repack_with_argument_clinic(string, keep_arguments_param=False):
arguments = kwargs['arguments']
else:
arguments = kwargs.pop('arguments')
if not keep_arguments_param:
kwargs.pop('callback', None)
try:
args += tuple(_iterate_argument_clinic(
context.evaluator,
@@ -208,6 +211,11 @@ class TreeArguments(AbstractArguments):
self._evaluator = evaluator
self.trailer = trailer # Can be None, e.g. in a class definition.
@classmethod
@evaluator_as_method_param_cache()
def create_cached(cls, *args, **kwargs):
return cls(*args, **kwargs)
def unpack(self, funcdef=None):
named_args = []
for star_count, el in unpack_arglist(self.argument_node):

View File

@@ -241,7 +241,7 @@ class _ContextWrapperBase(HelperContextMixin):
return cls(*args, **kwargs)
def __getattr__(self, name):
assert name != '_wrapped_context'
assert name != '_wrapped_context', 'Problem with _get_wrapped_context'
return getattr(self._wrapped_context, name)

View File

@@ -1,7 +1,7 @@
from jedi._compatibility import unicode
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
CompiledObjectFilter, CompiledContextName, create_from_access_path
from jedi.evaluate.base_context import ContextWrapper
from jedi.evaluate.base_context import ContextWrapper, LazyContextWrapper
def builtin_from_name(evaluator, string):
@@ -16,9 +16,9 @@ def builtin_from_name(evaluator, string):
return context
class CompiledValue(ContextWrapper):
def __init__(self, instance, compiled_obj):
super(CompiledValue, self).__init__(instance)
class CompiledValue(LazyContextWrapper):
def __init__(self, compiled_obj):
self.evaluator = compiled_obj.evaluator
self._compiled_obj = compiled_obj
def __getattribute__(self, name):
@@ -27,6 +27,11 @@ class CompiledValue(ContextWrapper):
return getattr(self._compiled_obj, name)
return super(CompiledValue, self).__getattribute__(name)
def _get_wrapped_context(self):
instance, = builtin_from_name(
self.evaluator, self._compiled_obj.name.string_name).execute_evaluated()
return instance
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._compiled_obj)
@@ -41,8 +46,7 @@ def create_simple_object(evaluator, obj):
evaluator,
evaluator.compiled_subprocess.create_simple_object(obj)
)
instance, = builtin_from_name(evaluator, compiled_obj.name.string_name).execute_evaluated()
return CompiledValue(instance, compiled_obj)
return CompiledValue(compiled_obj)
def get_string_context_set(evaluator):

View File

@@ -84,9 +84,14 @@ def safe_getattr(obj, name, default=_sentinel):
raise
return default
else:
if type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
if isinstance(attr, ALLOWED_DESCRIPTOR_ACCESS):
# In case of descriptors that have get methods we cannot return
# it's value, because that would mean code execution.
# Since it's an isinstance call, code execution is still possible,
# but this is not really a security feature, but much more of a
# safety feature. Code execution is basically always possible when
# a module is imported. This is here so people don't shoot
# themselves in the foot.
return getattr(obj, name)
return attr
@@ -175,6 +180,18 @@ def _force_unicode_decorator(func):
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
def get_api_type(obj):
if inspect.isclass(obj):
return u'class'
elif inspect.ismodule(obj):
return u'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return u'function'
# Everything else...
return u'instance'
class DirectObjectAccess(object):
def __init__(self, evaluator, obj):
self._evaluator = evaluator
@@ -328,12 +345,16 @@ class DirectObjectAccess(object):
def getattr_paths(self, name, default=_sentinel):
try:
return_obj = getattr(self._obj, name)
except AttributeError:
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
except Exception as e:
if default is _sentinel:
raise
if isinstance(e, AttributeError):
# Happens e.g. in properties of
# PyQt4.QtGui.QStyleOptionComboBox.currentText
# -> just set it to None
raise
# Just in case anything happens, return an AttributeError. It
# should not crash.
raise AttributeError
return_obj = default
access = self._create_access(return_obj)
if inspect.ismodule(return_obj):
@@ -352,16 +373,7 @@ class DirectObjectAccess(object):
raise ValueError("Object is type %s and not simple" % type(self._obj))
def get_api_type(self):
obj = self._obj
if self.is_class():
return u'class'
elif inspect.ismodule(obj):
return u'module'
elif inspect.isbuiltin(obj) or inspect.ismethod(obj) \
or inspect.ismethoddescriptor(obj) or inspect.isfunction(obj):
return u'function'
# Everything else...
return u'instance'
return get_api_type(self._obj)
def get_access_path_tuples(self):
accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()]
@@ -412,7 +424,7 @@ class DirectObjectAccess(object):
name=p.name,
has_default=p.default is not p.empty,
default=self._create_access_path(p.default),
default_string=str(p.default),
default_string=repr(p.default),
has_annotation=p.annotation is not p.empty,
annotation=self._create_access_path(p.annotation),
annotation_string=str(p.default),
@@ -448,6 +460,17 @@ class DirectObjectAccess(object):
# the signature. In that case we just want a simple escape for now.
raise ValueError
def get_return_annotation(self):
try:
o = self._obj.__annotations__.get('return')
except AttributeError:
return None
if o is None:
return None
return self._create_access_path(o)
def negate(self):
return self._create_access_path(-self._obj)

View File

@@ -46,6 +46,11 @@ class CompiledObject(Context):
self.access_handle = access_handle
def py__call__(self, arguments):
return_annotation = self.access_handle.get_return_annotation()
if return_annotation is not None:
# TODO the return annotation may also be a string.
return create_from_access_path(self.evaluator, return_annotation).execute_annotation()
try:
self.access_handle.getattr_paths(u'__call__')
except AttributeError:
@@ -122,7 +127,10 @@ class CompiledObject(Context):
signature_params = self.access_handle.get_signature_params()
except ValueError: # Has no signature
params_str, ret = self._parse_function_doc()
tokens = params_str.split(',')
if not params_str:
tokens = []
else:
tokens = params_str.split(',')
if self.access_handle.ismethoddescriptor():
tokens.insert(0, 'self')
for p in tokens:
@@ -256,6 +264,9 @@ class CompiledObject(Context):
def negate(self):
return create_from_access_path(self.evaluator, self.access_handle.negate())
def get_metaclasses(self):
return NO_CONTEXTS
class CompiledName(AbstractNameDefinition):
def __init__(self, evaluator, parent_context, name):
@@ -276,7 +287,11 @@ class CompiledName(AbstractNameDefinition):
@property
def api_type(self):
return next(iter(self.infer())).api_type
api = self.infer()
# If we can't find the type, assume it is an instance variable
if not api:
return "instance"
return next(iter(api)).api_type
@underscore_memoization
def infer(self):
@@ -285,9 +300,7 @@ class CompiledName(AbstractNameDefinition):
)])
class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
api_type = u'param'
class SignatureParamName(ParamNameInterface, AbstractNameDefinition):
def __init__(self, compiled_obj, signature_param):
self.parent_context = compiled_obj.parent_context
self._signature_param = signature_param
@@ -297,7 +310,7 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
return self._signature_param.name
def to_string(self):
s = self.string_name
s = self._kind_string() + self.string_name
if self._signature_param.has_annotation:
s += ': ' + self._signature_param.annotation_string
if self._signature_param.has_default:
@@ -307,9 +320,6 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
def get_kind(self):
return getattr(Parameter, self._signature_param.kind_name)
def is_keyword_param(self):
return self._signature_param
def infer(self):
p = self._signature_param
evaluator = self.parent_context.evaluator
@@ -322,9 +332,7 @@ class SignatureParamName(AbstractNameDefinition, ParamNameInterface):
return contexts
class UnresolvableParamName(AbstractNameDefinition, ParamNameInterface):
api_type = u'param'
class UnresolvableParamName(ParamNameInterface, AbstractNameDefinition):
def __init__(self, compiled_obj, name, default):
self.parent_context = compiled_obj.parent_context
self.string_name = name
@@ -369,14 +377,14 @@ class CompiledObjectFilter(AbstractFilter):
def __init__(self, evaluator, compiled_object, is_instance=False):
self._evaluator = evaluator
self._compiled_object = compiled_object
self.compiled_object = compiled_object
self.is_instance = is_instance
def get(self, name):
return self._get(
name,
lambda: self._compiled_object.access_handle.is_allowed_getattr(name),
lambda: self._compiled_object.access_handle.dir(),
lambda: self.compiled_object.access_handle.is_allowed_getattr(name),
lambda: self.compiled_object.access_handle.dir(),
check_has_attribute=True
)
@@ -391,7 +399,7 @@ class CompiledObjectFilter(AbstractFilter):
# Always use unicode objects in Python 2 from here.
name = force_unicode(name)
if is_descriptor or not has_attribute:
if (is_descriptor and not self._evaluator.allow_descriptor_getattr) or not has_attribute:
return [self._get_cached_name(name, is_empty=True)]
if self.is_instance and name not in dir_callback():
@@ -408,7 +416,7 @@ class CompiledObjectFilter(AbstractFilter):
def values(self):
from jedi.evaluate.compiled import builtin_from_name
names = []
needs_type_completions, dir_infos = self._compiled_object.access_handle.get_dir_infos()
needs_type_completions, dir_infos = self.compiled_object.access_handle.get_dir_infos()
for name in dir_infos:
names += self._get(
name,
@@ -423,10 +431,10 @@ class CompiledObjectFilter(AbstractFilter):
return names
def _create_name(self, name):
return self.name_class(self._evaluator, self._compiled_object, name)
return self.name_class(self._evaluator, self.compiled_object, name)
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self._compiled_object)
return "<%s: %s>" % (self.__class__.__name__, self.compiled_object)
docstr_defaults = {

View File

@@ -4,6 +4,7 @@ Used only for REPL Completion.
import inspect
import os
import sys
from jedi.parser_utils import get_cached_code_lines
@@ -17,7 +18,7 @@ from jedi.evaluate.context import ModuleContext
from jedi.evaluate.cache import evaluator_function_cache
from jedi.evaluate.compiled.getattr_static import getattr_static
from jedi.evaluate.compiled.access import compiled_objects_cache, \
ALLOWED_GETITEM_TYPES
ALLOWED_GETITEM_TYPES, get_api_type
from jedi.evaluate.compiled.context import create_cached_compiled_object
from jedi.evaluate.gradual.conversion import to_stub
@@ -49,6 +50,11 @@ class MixedObject(ContextWrapper):
def get_filters(self, *args, **kwargs):
yield MixedObjectFilter(self.evaluator, self)
def get_signatures(self):
# Prefer `inspect.signature` over somehow analyzing Python code. It
# should be very precise, especially for stuff like `partial`.
return self.compiled_object.get_signatures()
def py__call__(self, arguments):
return (to_stub(self._wrapped_context) or self._wrapped_context).py__call__(arguments)
@@ -131,6 +137,9 @@ def _load_module(evaluator, path):
def _get_object_to_check(python_object):
"""Check if inspect.getfile has a chance to find the source."""
if sys.version_info[0] > 2:
python_object = inspect.unwrap(python_object)
if (inspect.ismodule(python_object) or
inspect.isclass(python_object) or
inspect.ismethod(python_object) or
@@ -146,10 +155,8 @@ def _get_object_to_check(python_object):
raise TypeError # Prevents computation of `repr` within inspect.
def _find_syntax_node_name(evaluator, access_handle):
# TODO accessing this is bad, but it probably doesn't matter that much,
# because we're working with interpreteters only here.
python_object = access_handle.access._obj
def _find_syntax_node_name(evaluator, python_object):
original_object = python_object
try:
python_object = _get_object_to_check(python_object)
path = inspect.getsourcefile(python_object)
@@ -213,7 +220,13 @@ def _find_syntax_node_name(evaluator, access_handle):
# completions at some points but will lead to mostly correct type
# inference, because people tend to define a public name in a module only
# once.
return module_node, names[-1].parent, file_io, code_lines
tree_node = names[-1].parent
if tree_node.type == 'funcdef' and get_api_type(original_object) == 'instance':
# If an instance is given and we're landing on a function (e.g.
# partial in 3.5), something is completely wrong and we should not
# return that.
return None
return module_node, tree_node, file_io, code_lines
@compiled_objects_cache('mixed_cache')
@@ -224,10 +237,12 @@ def _create(evaluator, access_handle, parent_context, *args):
parent_context=parent_context and parent_context.compiled_object
)
result = _find_syntax_node_name(evaluator, access_handle)
# TODO accessing this is bad, but it probably doesn't matter that much,
# because we're working with interpreteters only here.
python_object = access_handle.access._obj
result = _find_syntax_node_name(evaluator, python_object)
if result is None:
# TODO Care about generics from stuff like `[1]` and don't return like this.
python_object = access_handle.access._obj
if type(python_object) in (dict, list, tuple):
return ContextSet({compiled_object})
@@ -251,7 +266,11 @@ def _create(evaluator, access_handle, parent_context, *args):
if name is not None:
evaluator.module_cache.add(string_names, ContextSet([module_context]))
else:
assert parent_context.tree_node.get_root_node() == module_node
if parent_context.tree_node.get_root_node() != module_node:
# This happens e.g. when __module__ is wrong, or when using
# TypeVar('foo'), where Jedi uses 'foo' as the name and
# Python's TypeVar('foo').__module__ will be typing.
return ContextSet({compiled_object})
module_context = parent_context.get_root_context()
tree_contexts = ContextSet({

View File

@@ -0,0 +1,15 @@
'''
Decorators are not really contexts, however we need some wrappers to improve
docstrings and other things around decorators.
'''
from jedi.evaluate.base_context import ContextWrapper
class Decoratee(ContextWrapper):
def __init__(self, wrapped_context, original_context):
self._wrapped_context = wrapped_context
self._original_context = original_context
def py__doc__(self):
return self._original_context.py__doc__()

View File

@@ -64,9 +64,10 @@ class FunctionMixin(object):
origin_scope=origin_scope
)
else:
scope = self.py__class__()
for filter in scope.get_filters(search_global=False, origin_scope=origin_scope):
yield filter
cls = self.py__class__()
for instance in cls.execute_evaluated():
for filter in instance.get_filters(search_global=False, origin_scope=origin_scope):
yield filter
def py__get__(self, instance, class_context):
from jedi.evaluate.context.instance import BoundMethod
@@ -99,6 +100,9 @@ class FunctionMixin(object):
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
def get_signatures(self):
return [TreeSignature(f) for f in self.get_signature_functions()]
class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndClassBase)):
"""
@@ -146,8 +150,8 @@ class FunctionContext(use_metaclass(CachedMetaClass, FunctionMixin, FunctionAndC
def get_default_param_context(self):
return self.parent_context
def get_signatures(self):
return [TreeSignature(self)]
def get_signature_functions(self):
return [self]
class MethodContext(FunctionContext):
@@ -370,14 +374,14 @@ class FunctionExecutionContext(TreeContext):
class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
def __init__(self, function, overloaded_functions):
super(OverloadedFunctionContext, self).__init__(function)
self.overloaded_functions = overloaded_functions
self._overloaded_functions = overloaded_functions
def py__call__(self, arguments):
debug.dbg("Execute overloaded function %s", self._wrapped_context, color='BLUE')
function_executions = []
context_set = NO_CONTEXTS
matched = False
for f in self.overloaded_functions:
for f in self._overloaded_functions:
function_execution = f.get_function_execution(arguments)
function_executions.append(function_execution)
if function_execution.matches_signature():
@@ -392,39 +396,8 @@ class OverloadedFunctionContext(FunctionMixin, ContextWrapper):
return NO_CONTEXTS
return ContextSet.from_sets(fe.infer() for fe in function_executions)
def get_signatures(self):
return [TreeSignature(f) for f in self.overloaded_functions]
def signature_matches(function_context, arguments):
unpacked_arguments = arguments.unpack()
key_args = {}
for param_node in function_context.tree_node.get_params():
while True:
key, argument = next(unpacked_arguments, (None, None))
if key is None or argument is None:
break
key_args[key] = argument
if argument is None:
argument = key_args.pop(param_node.name.value, None)
if argument is None:
# This signature has an parameter more than arguments were given.
return bool(param_node.star_count == 1)
if param_node.annotation is not None:
if param_node.star_count == 2:
return False # TODO allow this
annotation_contexts = function_context.evaluator.eval_element(
function_context.get_default_param_context(),
param_node.annotation
)
argument_contexts = argument.infer().py__class__()
if not any(c1.is_sub_class_of(c2)
for c1 in argument_contexts
for c2 in annotation_contexts):
return False
return True
def get_signature_functions(self):
return self._overloaded_functions
def _find_overload_functions(context, tree_node):

View File

@@ -3,6 +3,7 @@ from abc import abstractproperty
from jedi import debug
from jedi import settings
from jedi.evaluate import compiled
from jedi.evaluate.compiled.context import CompiledObjectFilter
from jedi.evaluate.helpers import contexts_from_qualified_names
from jedi.evaluate.filters import AbstractFilter
from jedi.evaluate.names import ContextName, TreeNameDefinition
@@ -12,7 +13,7 @@ from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.arguments import AnonymousArguments, \
ValuesArguments, TreeArgumentsWrapper
from jedi.evaluate.context.function import FunctionExecutionContext, \
from jedi.evaluate.context.function import \
FunctionContext, FunctionMixin, OverloadedFunctionContext
from jedi.evaluate.context.klass import ClassContext, apply_py__get__, \
ClassFilter
@@ -136,11 +137,19 @@ class AbstractInstanceContext(Context):
# compiled objects to search for self variables.
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
for cls in class_context.py__mro__():
if isinstance(cls, compiled.CompiledObject):
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
class_filters = class_context.get_filters(
search_global=False,
origin_scope=origin_scope,
is_instance=True,
)
for f in class_filters:
if isinstance(f, ClassFilter):
yield InstanceClassFilter(self.evaluator, self, f)
elif isinstance(f, CompiledObjectFilter):
yield CompiledInstanceClassFilter(self.evaluator, self, f)
else:
yield InstanceClassFilter(self.evaluator, self, cls, origin_scope)
# Propably from the metaclass.
yield f
def py__getitem__(self, index_context_set, contextualized_node):
names = self.get_function_slot_names(u'__getitem__')
@@ -223,8 +232,8 @@ class AbstractInstanceContext(Context):
return class_context
def get_signatures(self):
init_funcs = self.py__getattribute__('__call__')
return [sig.bind(self) for sig in init_funcs.get_signatures()]
call_funcs = self.py__getattribute__('__call__').py__get__(self, self.class_context)
return [s.bind(self) for s in call_funcs.get_signatures()]
def __repr__(self):
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
@@ -328,7 +337,6 @@ class CompiledInstanceName(compiled.CompiledName):
name.string_name
)
self._instance = instance
self._class = klass
self._class_member_name = name
@iterator_to_context_set
@@ -343,11 +351,10 @@ class CompiledInstanceName(compiled.CompiledName):
class CompiledInstanceClassFilter(AbstractFilter):
name_class = CompiledInstanceName
def __init__(self, evaluator, instance, klass):
def __init__(self, evaluator, instance, f):
self._evaluator = evaluator
self._instance = instance
self._class = klass
self._class_filter = next(klass.get_filters(is_instance=True))
self._class_filter = f
def get(self, name):
return self._convert(self._class_filter.get(name))
@@ -356,8 +363,9 @@ class CompiledInstanceClassFilter(AbstractFilter):
return self._convert(self._class_filter.values())
def _convert(self, names):
klass = self._class_filter.compiled_object
return [
CompiledInstanceName(self._evaluator, self._instance, self._class, n)
CompiledInstanceName(self._evaluator, self._instance, klass, n)
for n in names
]
@@ -382,15 +390,6 @@ class BoundMethod(FunctionMixin, ContextWrapper):
def get_function_execution(self, arguments=None):
arguments = self._get_arguments(arguments)
if isinstance(self._wrapped_context, compiled.CompiledObject):
# This is kind of weird, because it's coming from a compiled object
# and we're not sure if we want that in the future.
# TODO remove?!
return FunctionExecutionContext(
self.evaluator, self.parent_context, self, arguments
)
return super(BoundMethod, self).get_function_execution(arguments)
def py__call__(self, arguments):
@@ -400,8 +399,14 @@ class BoundMethod(FunctionMixin, ContextWrapper):
function_execution = self.get_function_execution(arguments)
return function_execution.infer()
def get_signature_functions(self):
return [
BoundMethod(self.instance, f)
for f in self._wrapped_context.get_signature_functions()
]
def get_signatures(self):
return [sig.bind(self) for sig in self._wrapped_context.get_signatures()]
return [sig.bind(self) for sig in super(BoundMethod, self).get_signatures()]
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_context)
@@ -454,26 +459,21 @@ class InstanceClassFilter(AbstractFilter):
resulting names in LazyINstanceClassName. The idea is that the class name
filtering can be very flexible and always be reflected in instances.
"""
def __init__(self, evaluator, context, class_context, origin_scope):
self._instance = context
self._class_context = class_context
self._class_filter = next(class_context.get_filters(
search_global=False,
origin_scope=origin_scope,
is_instance=True,
))
def __init__(self, evaluator, instance, class_filter):
self._instance = instance
self._class_filter = class_filter
def get(self, name):
return self._convert(self._class_filter.get(name))
return self._convert(self._class_filter.get(name, from_instance=True))
def values(self):
return self._convert(self._class_filter.values())
return self._convert(self._class_filter.values(from_instance=True))
def _convert(self, names):
return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names]
return [LazyInstanceClassName(self._instance, self._class_filter.context, n) for n in names]
def __repr__(self):
return '<%s for %s>' % (self.__class__.__name__, self._class_context)
return '<%s for %s>' % (self.__class__.__name__, self._class_filter.context)
class SelfAttributeFilter(ClassFilter):
@@ -503,7 +503,7 @@ class SelfAttributeFilter(ClassFilter):
if trailer.type == 'trailer' \
and len(trailer.parent.children) == 2 \
and trailer.children[0] == '.':
if name.is_definition() and self._access_possible(name):
if name.is_definition() and self._access_possible(name, from_instance=True):
# TODO filter non-self assignments.
yield name

View File

@@ -20,6 +20,8 @@ It is important to note that:
1. Array modfications work only in the current module.
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
"""
import sys
from jedi import debug
from jedi import settings
from jedi._compatibility import force_unicode, is_py3
@@ -35,8 +37,8 @@ from jedi.evaluate.utils import safe_property, to_list
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.filters import ParserTreeFilter, LazyAttributeOverwrite, \
publish_method
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, \
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin
from jedi.evaluate.base_context import ContextSet, Context, NO_CONTEXTS, \
TreeContext, ContextualizedNode, iterate_contexts, HelperContextMixin, _sentinel
from jedi.parser_utils import get_sync_comp_fors
@@ -44,6 +46,21 @@ class IterableMixin(object):
def py__stop_iteration_returns(self):
return ContextSet([compiled.builtin_from_name(self.evaluator, u'None')])
# At the moment, safe values are simple values like "foo", 1 and not
# lists/dicts. Therefore as a small speed optimization we can just do the
# default instead of resolving the lazy wrapped contexts, that are just
# doing this in the end as well.
# This mostly speeds up patterns like `sys.version_info >= (3, 0)` in
# typeshed.
if sys.version_info[0] == 2:
# Python 2...........
def get_safe_value(self, default=_sentinel):
if default is _sentinel:
raise ValueError("There exists no safe value for context %s" % self)
return default
else:
get_safe_value = Context.get_safe_value
class GeneratorBase(LazyAttributeOverwrite, IterableMixin):
array_type = None

View File

@@ -45,10 +45,11 @@ from jedi.evaluate import compiled
from jedi.evaluate.lazy_context import LazyKnownContexts
from jedi.evaluate.filters import ParserTreeFilter
from jedi.evaluate.names import TreeNameDefinition, ContextName
from jedi.evaluate.arguments import unpack_arglist
from jedi.evaluate.arguments import unpack_arglist, ValuesArguments
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
NO_CONTEXTS
from jedi.evaluate.context.function import FunctionAndClassBase
from jedi.plugins import plugin_manager
def apply_py__get__(context, instance, class_context):
@@ -109,21 +110,37 @@ class ClassFilter(ParserTreeFilter):
node = get_cached_parent_scope(self._used_names, node)
return False
def _access_possible(self, name):
def _access_possible(self, name, from_instance=False):
# Filter for ClassVar variables
# TODO this is not properly done, yet. It just checks for the string
# ClassVar in the annotation, which can be quite imprecise. If we
# wanted to do this correct, we would have to resolve the ClassVar.
if not from_instance:
expr_stmt = name.get_definition()
if expr_stmt is not None and expr_stmt.type == 'expr_stmt':
annassign = expr_stmt.children[1]
if annassign.type == 'annassign':
# TODO this is not proper matching
if 'ClassVar' not in annassign.children[1].get_code():
return False
# Filter for name mangling of private variables like __foo
return not name.value.startswith('__') or name.value.endswith('__') \
or self._equals_origin_scope()
def _filter(self, names):
def _filter(self, names, from_instance=False):
names = super(ClassFilter, self)._filter(names)
return [name for name in names if self._access_possible(name)]
return [name for name in names if self._access_possible(name, from_instance)]
class ClassMixin(object):
def is_class(self):
return True
def py__call__(self, arguments):
def py__call__(self, arguments=None):
from jedi.evaluate.context import TreeInstance
if arguments is None:
arguments = ValuesArguments([])
return ContextSet([TreeInstance(self.evaluator, self.parent_context, self, arguments)])
def py__class__(self):
@@ -177,13 +194,13 @@ class ClassMixin(object):
def get_filters(self, search_global=False, until_position=None,
origin_scope=None, is_instance=False):
metaclasses = self.get_metaclasses()
if metaclasses:
for f in self.get_metaclass_filters(metaclasses):
yield f
if search_global:
yield ParserTreeFilter(
self.evaluator,
context=self,
until_position=until_position,
origin_scope=origin_scope
)
yield self.get_global_filter(until_position, origin_scope)
else:
for cls in self.py__mro__():
if isinstance(cls, compiled.CompiledObject):
@@ -198,8 +215,26 @@ class ClassMixin(object):
if not is_instance:
from jedi.evaluate.compiled import builtin_from_name
type_ = builtin_from_name(self.evaluator, u'type')
assert isinstance(type_, ClassContext)
if type_ != self:
yield next(type_.get_filters())
for instance in type_.py__call__():
instance_filters = instance.get_filters()
# Filter out self filters
next(instance_filters)
next(instance_filters)
yield next(instance_filters)
def get_signatures(self):
init_funcs = self.py__call__().py__getattribute__('__init__')
return [sig.bind(self) for sig in init_funcs.get_signatures()]
def get_global_filter(self, until_position=None, origin_scope=None):
return ParserTreeFilter(
self.evaluator,
context=self,
until_position=until_position,
origin_scope=origin_scope
)
class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBase)):
@@ -227,12 +262,17 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
found.append(type_var)
return found
@evaluator_method_cache(default=())
def py__bases__(self):
def _get_bases_arguments(self):
arglist = self.tree_node.get_super_arglist()
if arglist:
from jedi.evaluate import arguments
args = arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
return arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
return None
@evaluator_method_cache(default=())
def py__bases__(self):
args = self._get_bases_arguments()
if args is not None:
lst = [value for key, value in args.unpack() if key is None]
if lst:
return lst
@@ -280,6 +320,25 @@ class ClassContext(use_metaclass(CachedMetaClass, ClassMixin, FunctionAndClassBa
)])
return ContextSet({self})
def get_signatures(self):
init_funcs = self.py__getattribute__('__init__')
return [sig.bind(self) for sig in init_funcs.get_signatures()]
@plugin_manager.decorate()
def get_metaclass_filters(self, metaclass):
debug.dbg('Unprocessed metaclass %s', metaclass)
return []
@evaluator_method_cache(default=NO_CONTEXTS)
def get_metaclasses(self):
args = self._get_bases_arguments()
if args is not None:
m = [value for key, value in args.unpack() if key == 'metaclass']
metaclasses = ContextSet.from_sets(lazy_context.infer() for lazy_context in m)
metaclasses = ContextSet(m for m in metaclasses if m.is_class())
if metaclasses:
return metaclasses
for lazy_base in self.py__bases__():
for context in lazy_base.infer():
if context.is_class():
contexts = context.get_metaclasses()
if contexts:
return contexts
return NO_CONTEXTS

View File

@@ -9,6 +9,8 @@ from jedi.evaluate import compiled
from jedi.evaluate.base_context import TreeContext
from jedi.evaluate.names import SubModuleName
from jedi.evaluate.helpers import contexts_from_qualified_names
from jedi.evaluate.compiled import create_simple_object
from jedi.evaluate.base_context import ContextSet
class _ModuleAttributeName(AbstractNameDefinition):
@@ -17,11 +19,20 @@ class _ModuleAttributeName(AbstractNameDefinition):
"""
api_type = u'instance'
def __init__(self, parent_module, string_name):
def __init__(self, parent_module, string_name, string_value=None):
self.parent_context = parent_module
self.string_name = string_name
self._string_value = string_value
def infer(self):
if self._string_value is not None:
s = self._string_value
if self.parent_context.evaluator.environment.version_info.major == 2 \
and not isinstance(s, bytes):
s = s.encode('utf-8')
return ContextSet([
create_simple_object(self.parent_context.evaluator, s)
])
return compiled.get_string_context_set(self.parent_context.evaluator)
@@ -132,9 +143,13 @@ class ModuleMixin(SubModuleDictMixin):
@evaluator_method_cache()
def _module_attributes_dict(self):
names = ['__file__', '__package__', '__doc__', '__name__']
names = ['__package__', '__doc__', '__name__']
# All the additional module attributes are strings.
return dict((n, _ModuleAttributeName(self, n)) for n in names)
dct = dict((n, _ModuleAttributeName(self, n)) for n in names)
file = self.py__file__()
if file is not None:
dct['__file__'] = _ModuleAttributeName(self, '__file__', file)
return dct
def iter_star_filters(self, search_global=False):
for star_module in self.star_imports():
@@ -145,13 +160,18 @@ class ModuleMixin(SubModuleDictMixin):
# to push the star imports into Evaluator.module_cache, if we reenable this.
@evaluator_method_cache([])
def star_imports(self):
from jedi.evaluate.imports import infer_import
from jedi.evaluate.imports import Importer
modules = []
for i in self.tree_node.iter_imports():
if i.is_star_import():
name = i.get_paths()[-1][-1]
new = infer_import(self, name)
new = Importer(
self.evaluator,
import_path=i.get_paths()[-1],
module_context=self,
level=i.level
).follow()
for module in new:
if isinstance(module, ModuleContext):
modules += module.star_imports()

View File

@@ -51,11 +51,8 @@ def _get_numpy_doc_string_cls():
global _numpy_doc_string_cache
if isinstance(_numpy_doc_string_cache, (ImportError, SyntaxError)):
raise _numpy_doc_string_cache
try:
from numpydoc.docscrape import NumpyDocString
_numpy_doc_string_cache = NumpyDocString
except (ImportError, SyntaxError) as e:
raise
from numpydoc.docscrape import NumpyDocString
_numpy_doc_string_cache = NumpyDocString
return _numpy_doc_string_cache

View File

@@ -17,7 +17,6 @@ It works as follows:
- execute these calls and check the input.
"""
from parso.python import tree
from jedi import settings
from jedi import debug
from jedi.evaluate.cache import evaluator_function_cache

View File

@@ -74,20 +74,22 @@ class AbstractUsedNamesFilter(AbstractFilter):
self._used_names = self._module_node.get_used_names()
self.context = context
def get(self, name):
def get(self, name, **filter_kwargs):
return self._convert_names(self._filter(
_get_definition_names(self._used_names, name)
_get_definition_names(self._used_names, name),
**filter_kwargs
))
def _convert_names(self, names):
return [self.name_class(self.context, name) for name in names]
def values(self):
def values(self, **filter_kwargs):
return self._convert_names(
name
for name_key in self._used_names
for name in self._filter(
_get_definition_names(self._used_names, name_key)
_get_definition_names(self._used_names, name_key),
**filter_kwargs
)
)
@@ -384,7 +386,7 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
>>> list(filters[1].values()) # package modules -> Also empty.
[]
>>> sorted(name.string_name for name in filters[2].values()) # Module attributes
['__doc__', '__file__', '__name__', '__package__']
['__doc__', '__name__', '__package__']
Finally, it yields the builtin filter, if `include_builtin` is
true (default).
@@ -407,5 +409,4 @@ def get_global_filters(evaluator, context, until_position, origin_scope):
context = context.parent_context
# Add builtins to the global scope.
for filter in evaluator.builtins_module.get_filters():
yield filter
yield next(evaluator.builtins_module.get_filters())

View File

@@ -14,7 +14,9 @@ from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
from jedi.evaluate.gradual.typing import TypeVar, LazyGenericClass, \
AbstractAnnotatedClass
from jedi.evaluate.gradual.typing import GenericClass
from jedi.evaluate.helpers import is_string
from jedi.evaluate.compiled import builtin_from_name
from jedi import debug
from jedi import parser_utils
@@ -106,6 +108,25 @@ def _split_comment_param_declaration(decl_text):
@evaluator_method_cache()
def infer_param(execution_context, param):
contexts = _infer_param(execution_context, param)
evaluator = execution_context.evaluator
if param.star_count == 1:
tuple_ = builtin_from_name(evaluator, 'tuple')
return ContextSet([GenericClass(
tuple_,
generics=(contexts,),
) for c in contexts])
elif param.star_count == 2:
dct = builtin_from_name(evaluator, 'dict')
return ContextSet([GenericClass(
dct,
generics=(ContextSet([builtin_from_name(evaluator, 'str')]), contexts),
) for c in contexts])
pass
return contexts
def _infer_param(execution_context, param):
"""
Infers the type of a function parameter, using type annotations.
"""

View File

@@ -40,7 +40,8 @@ def _stub_to_python_context_set(stub_context, ignore_compiled=False):
def _infer_from_stub(stub_module, qualified_names, ignore_compiled):
assert isinstance(stub_module, StubModuleContext), stub_module
from jedi.evaluate.compiled.mixed import MixedObject
assert isinstance(stub_module, (StubModuleContext, MixedObject)), stub_module
non_stubs = stub_module.non_stub_context_set
if ignore_compiled:
non_stubs = non_stubs.filter(lambda c: not c.is_compiled())

View File

@@ -1,5 +1,6 @@
import os
import re
from functools import wraps
from jedi.file_io import FileIO
from jedi._compatibility import FileNotFoundError, cast_path
@@ -87,6 +88,7 @@ def _cache_stub_file_map(version_info):
def import_module_decorator(func):
@wraps(func)
def wrapper(evaluator, import_names, parent_module_context, sys_path, prefer_stubs):
try:
python_context_set = evaluator.module_cache.get(import_names)

View File

@@ -10,7 +10,7 @@ from jedi import debug
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.compiled import builtin_from_name
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context, \
iterator_to_context_set, HelperContextMixin, ContextWrapper
iterator_to_context_set, ContextWrapper, LazyContextWrapper
from jedi.evaluate.lazy_context import LazyKnownContexts
from jedi.evaluate.context.iterable import SequenceLiteralContext
from jedi.evaluate.arguments import repack_with_argument_clinic
@@ -19,7 +19,7 @@ from jedi.evaluate.filters import FilterWrapper
from jedi.evaluate.names import NameWrapper, AbstractTreeName, \
AbstractNameDefinition, ContextName
from jedi.evaluate.helpers import is_string
from jedi.evaluate.context.klass import ClassMixin
from jedi.evaluate.context.klass import ClassMixin, ClassFilter
_PROXY_CLASS_TYPES = 'Tuple Generic Protocol Callable Type'.split()
_TYPE_ALIAS_TYPES = {
@@ -54,12 +54,15 @@ class _BaseTypingContext(Context):
return self._tree_name
def get_filters(self, *args, **kwargs):
# TODO this is obviously wrong.
class EmptyFilter():
def get(self, name):
# TODO this is obviously wrong. Is it though?
class EmptyFilter(ClassFilter):
def __init__(self):
pass
def get(self, name, **kwargs):
return []
def values(self):
def values(self, **kwargs):
return []
yield EmptyFilter()
@@ -112,6 +115,8 @@ class TypingModuleName(NameWrapper):
yield builtin_from_name(evaluator, u'True')
elif name == 'overload':
yield OverloadFunction.create_cached(evaluator, self.parent_context, self.tree_name)
elif name == 'NewType':
yield NewTypeFunction.create_cached(evaluator, self.parent_context, self.tree_name)
elif name == 'cast':
# TODO implement cast
yield CastFunction.create_cached(evaluator, self.parent_context, self.tree_name)
@@ -204,6 +209,9 @@ class _TypingClassMixin(object):
self.evaluator.builtins_module.py__getattribute__('object')
)]
def get_metaclasses(self):
return []
class TypingClassContextWithIndex(_TypingClassMixin, TypingContextWithIndex, ClassMixin):
pass
@@ -236,9 +244,9 @@ def _iter_over_arguments(maybe_tuple_context, defining_context):
yield ContextSet(resolve_forward_references(context_set))
class TypeAlias(HelperContextMixin):
def __init__(self, evaluator, parent_context, origin_tree_name, actual):
self.evaluator = evaluator
class TypeAlias(LazyContextWrapper):
def __init__(self, parent_context, origin_tree_name, actual):
self.evaluator = parent_context.evaluator
self.parent_context = parent_context
self._origin_tree_name = origin_tree_name
self._actual = actual # e.g. builtins.list
@@ -250,14 +258,10 @@ class TypeAlias(HelperContextMixin):
def py__name__(self):
return self.name.string_name
def __getattr__(self, name):
return getattr(self._get_type_alias_class(), name)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._actual)
@evaluator_method_cache()
def _get_type_alias_class(self):
def _get_wrapped_context(self):
module_name, class_name = self._actual.split('.')
if self.evaluator.environment.version_info.major == 2 and module_name == 'builtins':
module_name = '__builtin__'
@@ -456,6 +460,32 @@ class OverloadFunction(_BaseTypingContext):
return func_context_set
class NewTypeFunction(_BaseTypingContext):
def py__call__(self, arguments):
ordered_args = arguments.unpack()
next(ordered_args, (None, None))
_, second_arg = next(ordered_args, (None, None))
if second_arg is None:
return NO_CONTEXTS
return ContextSet(
NewType(
self.evaluator,
contextualized_node.context,
contextualized_node.node,
second_arg.infer(),
) for contextualized_node in arguments.get_calling_nodes())
class NewType(Context):
def __init__(self, evaluator, parent_context, tree_node, type_context_set):
super(NewType, self).__init__(evaluator, parent_context)
self._type_context_set = type_context_set
self.tree_node = tree_node
def py__call__(self, arguments):
return self._type_context_set.execute_annotation()
class CastFunction(_BaseTypingContext):
@repack_with_argument_clinic('type, object, /')
def py__call__(self, type_context_set, object_context_set):

View File

@@ -21,7 +21,7 @@ from jedi._compatibility import (FileNotFoundError, ImplicitNSInfo,
force_unicode, unicode)
from jedi import debug
from jedi import settings
from jedi.file_io import KnownContentFileIO, FolderIO, FileIO
from jedi.file_io import KnownContentFileIO, FileIO
from jedi.parser_utils import get_cached_code_lines
from jedi.evaluate import sys_path
from jedi.evaluate import helpers
@@ -33,6 +33,7 @@ from jedi.evaluate.names import ImportName, SubModuleName
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
from jedi.evaluate.gradual.typeshed import import_module_decorator
from jedi.evaluate.context.module import iter_module_names
from jedi.plugins import plugin_manager
class ModuleCache(object):
@@ -371,6 +372,7 @@ class Importer(object):
return names
@plugin_manager.decorate()
@import_module_decorator
def import_module(evaluator, import_names, parent_module_context, sys_path):
"""

View File

@@ -3,7 +3,7 @@ from abc import abstractmethod
from parso.tree import search_ancestor
from jedi._compatibility import Parameter
from jedi.evaluate.base_context import ContextSet
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
from jedi.cache import memoize_method
@@ -58,6 +58,23 @@ class AbstractNameDefinition(object):
return self.parent_context.api_type
class AbstractArbitraryName(AbstractNameDefinition):
"""
When you e.g. want to complete dicts keys, you probably want to complete
string literals, which is not really a name, but for Jedi we use this
concept of Name for completions as well.
"""
is_context_name = False
def __init__(self, evaluator, string):
self.evaluator = evaluator
self.string_name = string
self.parent_context = evaluator.builtins_module
def infer(self):
return NO_CONTEXTS
class AbstractTreeName(AbstractNameDefinition):
def __init__(self, parent_context, tree_name):
self.parent_context = parent_context
@@ -150,24 +167,104 @@ class TreeNameDefinition(AbstractTreeName):
return self._API_TYPES.get(definition.type, 'statement')
class ParamNameInterface(object):
class _ParamMixin(object):
def maybe_positional_argument(self, include_star=True):
options = [Parameter.POSITIONAL_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_star:
options.append(Parameter.VAR_POSITIONAL)
return self.get_kind() in options
def maybe_keyword_argument(self, include_stars=True):
options = [Parameter.KEYWORD_ONLY, Parameter.POSITIONAL_OR_KEYWORD]
if include_stars:
options.append(Parameter.VAR_KEYWORD)
return self.get_kind() in options
def _kind_string(self):
kind = self.get_kind()
if kind == Parameter.VAR_POSITIONAL: # *args
return '*'
if kind == Parameter.VAR_KEYWORD: # **kwargs
return '**'
return ''
class ParamNameInterface(_ParamMixin):
api_type = u'param'
def get_kind(self):
raise NotImplementedError
def to_string(self):
raise NotImplementedError
def get_param(self):
# TODO document better where this is used and when. Currently it has
# very limited use, but is still in use. It's currently not even
# clear what values would be allowed.
return None
class ParamName(AbstractTreeName, ParamNameInterface):
api_type = u'param'
@property
def star_count(self):
kind = self.get_kind()
if kind == Parameter.VAR_POSITIONAL:
return 1
if kind == Parameter.VAR_KEYWORD:
return 2
return 0
def __init__(self, parent_context, tree_name):
self.parent_context = parent_context
self.tree_name = tree_name
class BaseTreeParamName(ParamNameInterface, AbstractTreeName):
annotation_node = None
default_node = None
def to_string(self):
output = self._kind_string() + self.string_name
annotation = self.annotation_node
default = self.default_node
if annotation is not None:
output += ': ' + annotation.get_code(include_prefix=False)
if default is not None:
output += '=' + default.get_code(include_prefix=False)
return output
class ParamName(BaseTreeParamName):
def _get_param_node(self):
return search_ancestor(self.tree_name, 'param')
@property
def annotation_node(self):
return self._get_param_node().annotation
def infer_annotation(self, execute_annotation=True):
node = self.annotation_node
if node is None:
return NO_CONTEXTS
contexts = self.parent_context.parent_context.eval_node(node)
if execute_annotation:
contexts = contexts.execute_annotation()
return contexts
def infer_default(self):
node = self.default_node
if node is None:
return NO_CONTEXTS
return self.parent_context.parent_context.eval_node(node)
@property
def default_node(self):
return self._get_param_node().default
@property
def string_name(self):
name = self.tree_name.value
if name.startswith('__'):
# Params starting with __ are an equivalent to positional only
# variables in typeshed.
name = name[2:]
return name
def get_kind(self):
tree_param = self._get_param_node()
if tree_param.star_count == 1: # *args
@@ -175,23 +272,26 @@ class ParamName(AbstractTreeName, ParamNameInterface):
if tree_param.star_count == 2: # **kwargs
return Parameter.VAR_KEYWORD
parent = tree_param.parent
for p in parent.children:
if p.type == 'param':
if p.star_count:
return Parameter.KEYWORD_ONLY
if p == tree_param:
break
return Parameter.POSITIONAL_OR_KEYWORD
# Params starting with __ are an equivalent to positional only
# variables in typeshed.
if tree_param.name.value.startswith('__'):
return Parameter.POSITIONAL_ONLY
def to_string(self):
output = self.string_name
param_node = self._get_param_node()
if param_node.annotation is not None:
output += ': ' + param_node.annotation.get_code(include_prefix=False)
if param_node.default is not None:
output += '=' + param_node.default.get_code(include_prefix=False)
return output
parent = tree_param.parent
param_appeared = False
for p in parent.children:
if param_appeared:
if p == '/':
return Parameter.POSITIONAL_ONLY
else:
if p == '*':
return Parameter.KEYWORD_ONLY
if p.type == 'param':
if p.star_count:
return Parameter.KEYWORD_ONLY
if p == tree_param:
param_appeared = True
return Parameter.POSITIONAL_OR_KEYWORD
def infer(self):
return self.get_param().infer()
@@ -202,6 +302,17 @@ class ParamName(AbstractTreeName, ParamNameInterface):
return params[param_node.position_index]
class ParamNameWrapper(_ParamMixin):
def __init__(self, param_name):
self._wrapped_param_name = param_name
def __getattr__(self, name):
return getattr(self._wrapped_param_name, name)
def __repr__(self):
return '<%s: %s>' % (self.__class__.__name__, self._wrapped_param_name)
class ImportName(AbstractNameDefinition):
start_pos = (1, 0)
_level = 0

View File

@@ -1,31 +1,22 @@
from jedi._compatibility import Parameter
from jedi.cache import memoize_method
class AbstractSignature(object):
def __init__(self, context, is_bound=False):
self.context = context
self.is_bound = is_bound
@property
def name(self):
return self.context.name
@property
def annotation_string(self):
return ''
class _SignatureMixin(object):
def to_string(self):
def param_strings():
is_positional = False
is_kw_only = False
for n in self.get_param_names():
for n in self.get_param_names(resolve_stars=True):
kind = n.get_kind()
is_positional |= kind == Parameter.POSITIONAL_ONLY
if is_positional and kind != Parameter.POSITIONAL_ONLY:
yield '/'
is_positional = False
if kind == Parameter.KEYWORD_ONLY and not is_kw_only:
if kind == Parameter.VAR_POSITIONAL:
is_kw_only = True
elif kind == Parameter.KEYWORD_ONLY and not is_kw_only:
yield '*'
is_kw_only = True
@@ -40,15 +31,32 @@ class AbstractSignature(object):
s += ' -> ' + annotation
return s
def bind(self, context):
raise NotImplementedError
def get_param_names(self):
class AbstractSignature(_SignatureMixin):
def __init__(self, context, is_bound=False):
self.context = context
self.is_bound = is_bound
@property
def name(self):
return self.context.name
@property
def annotation_string(self):
return ''
def get_param_names(self, resolve_stars=False):
param_names = self._function_context.get_param_names()
if self.is_bound:
return param_names[1:]
return param_names
def bind(self, context):
raise NotImplementedError
def __repr__(self):
return '<%s: %s, %s>' % (self.__class__.__name__, self.context, self._function_context)
class TreeSignature(AbstractSignature):
def __init__(self, context, function_context=None, is_bound=False):
@@ -73,6 +81,14 @@ class TreeSignature(AbstractSignature):
return ''
return a.get_code(include_prefix=False)
@memoize_method
def get_param_names(self, resolve_stars=False):
params = super(TreeSignature, self).get_param_names(resolve_stars=False)
if resolve_stars:
from jedi.evaluate.star_args import process_params
params = process_params(params)
return params
class BuiltinSignature(AbstractSignature):
def __init__(self, context, return_string, is_bound=False):
@@ -90,3 +106,11 @@ class BuiltinSignature(AbstractSignature):
def bind(self, context):
assert not self.is_bound
return BuiltinSignature(context, self._return_string, is_bound=True)
class SignatureWrapper(_SignatureMixin):
def __init__(self, wrapped_signature):
self._wrapped_signature = wrapped_signature
def __getattr__(self, name):
return getattr(self._wrapped_signature, name)

206
jedi/evaluate/star_args.py Normal file
View File

@@ -0,0 +1,206 @@
"""
This module is responsible for evaluating *args and **kwargs for signatures.
This means for example in this case::
def foo(a, b, c): ...
def bar(*args):
return foo(1, *args)
The signature here for bar should be `bar(b, c)` instead of bar(*args).
"""
from jedi._compatibility import Parameter
from jedi.evaluate.utils import to_list
from jedi.evaluate.names import ParamNameWrapper
def _iter_nodes_for_param(param_name):
from parso.python.tree import search_ancestor
from jedi.evaluate.arguments import TreeArguments
execution_context = param_name.parent_context
function_node = execution_context.tree_node
module_node = function_node.get_root_node()
start = function_node.children[-1].start_pos
end = function_node.children[-1].end_pos
for name in module_node.get_used_names().get(param_name.string_name):
if start <= name.start_pos < end:
# Is used in the function
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
# anyway
trailer = search_ancestor(argument, 'trailer')
if trailer is not None: # Make sure we're in a function
context = execution_context.create_context(trailer)
if _goes_to_param_name(param_name, context, name):
contexts = _to_callables(context, trailer)
args = TreeArguments.create_cached(
execution_context.evaluator,
context=context,
argument_node=trailer.children[1],
trailer=trailer,
)
for c in contexts:
yield c, args
else:
assert False
def _goes_to_param_name(param_name, context, potential_name):
if potential_name.type != 'name':
return False
from jedi.evaluate.names import TreeNameDefinition
found = TreeNameDefinition(context, potential_name).goto()
return any(param_name.parent_context == p.parent_context
and param_name.start_pos == p.start_pos
for p in found)
def _to_callables(context, trailer):
from jedi.evaluate.syntax_tree import eval_trailer
atom_expr = trailer.parent
index = atom_expr.children[0] == 'await'
# Eval atom first
contexts = context.eval_node(atom_expr.children[index])
for trailer2 in atom_expr.children[index + 1:]:
if trailer == trailer2:
break
contexts = eval_trailer(context, contexts, trailer2)
return contexts
def _remove_given_params(arguments, param_names):
count = 0
used_keys = set()
for key, _ in arguments.unpack():
if key is None:
count += 1
else:
used_keys.add(key)
for p in param_names:
if count and p.maybe_positional_argument():
count -= 1
continue
if p.string_name in used_keys and p.maybe_keyword_argument():
continue
yield p
@to_list
def process_params(param_names, star_count=3): # default means both * and **
used_names = set()
arg_callables = []
kwarg_callables = []
kw_only_names = []
kwarg_names = []
arg_names = []
original_arg_name = None
original_kwarg_name = None
for p in param_names:
kind = p.get_kind()
if kind == Parameter.VAR_POSITIONAL:
if star_count & 1:
arg_callables = _iter_nodes_for_param(p)
original_arg_name = p
elif p.get_kind() == Parameter.VAR_KEYWORD:
if star_count & 2:
kwarg_callables = list(_iter_nodes_for_param(p))
original_kwarg_name = p
elif kind == Parameter.KEYWORD_ONLY:
if star_count & 2:
kw_only_names.append(p)
elif kind == Parameter.POSITIONAL_ONLY:
if star_count & 1:
yield p
else:
if star_count == 1:
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
elif star_count == 2:
kw_only_names.append(ParamNameFixedKind(p, Parameter.KEYWORD_ONLY))
else:
used_names.add(p.string_name)
yield p
longest_param_names = ()
found_arg_signature = False
found_kwarg_signature = False
for func_and_argument in arg_callables:
func, arguments = func_and_argument
new_star_count = star_count
if func_and_argument in kwarg_callables:
kwarg_callables.remove(func_and_argument)
else:
new_star_count = 1
for signature in func.get_signatures():
found_arg_signature = True
if new_star_count == 3:
found_kwarg_signature = True
args_for_this_func = []
for p in process_params(
list(_remove_given_params(
arguments,
signature.get_param_names(resolve_stars=False)
)), new_star_count):
if p.get_kind() == Parameter.VAR_KEYWORD:
kwarg_names.append(p)
elif p.get_kind() == Parameter.VAR_POSITIONAL:
arg_names.append(p)
elif p.get_kind() == Parameter.KEYWORD_ONLY:
kw_only_names.append(p)
else:
args_for_this_func.append(p)
if len(args_for_this_func) > len(longest_param_names):
longest_param_names = args_for_this_func
for p in longest_param_names:
if star_count == 1 and p.get_kind() != Parameter.VAR_POSITIONAL:
yield ParamNameFixedKind(p, Parameter.POSITIONAL_ONLY)
else:
if p.get_kind() == Parameter.POSITIONAL_OR_KEYWORD:
used_names.add(p.string_name)
yield p
if not found_arg_signature and original_arg_name is not None:
yield original_arg_name
elif arg_names:
yield arg_names[0]
for p in kw_only_names:
if p.string_name in used_names:
continue
yield p
used_names.add(p.string_name)
for func, arguments in kwarg_callables:
for signature in func.get_signatures():
found_kwarg_signature = True
for p in process_params(
list(_remove_given_params(
arguments,
signature.get_param_names(resolve_stars=False)
)), star_count=2):
if p.get_kind() != Parameter.KEYWORD_ONLY or not kwarg_names:
yield p
if not found_kwarg_signature and original_kwarg_name is not None:
yield original_kwarg_name
elif kwarg_names:
yield kwarg_names[0]
class ParamNameFixedKind(ParamNameWrapper):
def __init__(self, param_name, new_kind):
super(ParamNameFixedKind, self).__init__(param_name)
self._new_kind = new_kind
def get_kind(self):
return self._new_kind

View File

@@ -26,6 +26,8 @@ from jedi.evaluate.compiled.access import COMPARISON_OPERATORS
from jedi.evaluate.cache import evaluator_method_cache
from jedi.evaluate.gradual.stub_context import VersionInfo
from jedi.evaluate.gradual import annotation
from jedi.evaluate.context.decorator import Decoratee
from jedi.plugins import plugin_manager
def _limit_context_infers(func):
@@ -148,28 +150,28 @@ def eval_node(context, element):
return eval_or_test(context, element)
def eval_trailer(context, base_contexts, trailer):
def eval_trailer(context, atom_contexts, trailer):
trailer_op, node = trailer.children[:2]
if node == ')': # `arglist` is optional.
node = None
if trailer_op == '[':
trailer_op, node, _ = trailer.children
return base_contexts.get_item(
return atom_contexts.get_item(
eval_subscript_list(context.evaluator, context, node),
ContextualizedNode(context, trailer)
)
else:
debug.dbg('eval_trailer: %s in %s', trailer, base_contexts)
debug.dbg('eval_trailer: %s in %s', trailer, atom_contexts)
if trailer_op == '.':
return base_contexts.py__getattribute__(
return atom_contexts.py__getattribute__(
name_context=context,
name_or_str=node
)
else:
assert trailer_op == '(', 'trailer_op is actually %s' % trailer_op
args = arguments.TreeArguments(context.evaluator, context, node, trailer)
return base_contexts.execute(args)
return atom_contexts.execute(args)
def eval_atom(context, atom):
@@ -544,6 +546,7 @@ def _remove_statements(evaluator, context, stmt, name):
return eval_expr_stmt(context, stmt, seek_name=name)
@plugin_manager.decorate()
def tree_name_to_contexts(evaluator, context, tree_name):
context_set = NO_CONTEXTS
module_node = context.get_root_context().tree_node
@@ -666,6 +669,8 @@ def _apply_decorators(context, node):
return initial
debug.dbg('decorator end %s', values, color="MAGENTA")
if values != initial:
return ContextSet([Decoratee(c, decoratee_context) for c in values])
return values

View File

@@ -5,6 +5,7 @@ from weakref import WeakKeyDictionary
from parso.python import tree
from parso.cache import parser_cache
from parso import split_lines
from jedi._compatibility import literal_eval, force_unicode
@@ -278,3 +279,19 @@ def get_cached_code_lines(grammar, path):
to do this, but we avoid splitting all the lines again.
"""
return parser_cache[grammar._hashed][path].lines
def cut_value_at_position(leaf, position):
"""
Cuts of the value of the leaf at position
"""
lines = split_lines(leaf.value, keepends=True)[:position[0] - leaf.line + 1]
column = position[1]
if leaf.line == position[0]:
column -= leaf.column
lines[-1] = lines[-1][:column]
return ''.join(lines)
def get_string_quote(leaf):
return re.match('\w*("""|\'{3}|"|\')', leaf.value).group(1)

View File

@@ -1,37 +1,47 @@
from jedi.plugins.stdlib import StdlibPlugin
from jedi.plugins.flask import FlaskPlugin
from functools import wraps
class _PluginManager(object):
def __init__(self, registered_plugin_classes=()):
self._registered_plugin_classes = list(registered_plugin_classes)
def __init__(self):
self._registered_plugins = []
self._cached_base_callbacks = {}
self._built_functions = {}
def register(self, plugin_class):
def register(self, *plugins):
"""
Makes it possible to register your plugin.
"""
self._registered_plugins.append(plugin_class)
self._registered_plugins.extend(plugins)
self._build_functions()
def _build_chain(self, evaluator):
for plugin_class in self._registered_plugin_classes:
yield plugin_class(evaluator)
def decorate(self):
def decorator(callback):
@wraps(callback)
def wrapper(*args, **kwargs):
return built_functions[name](*args, **kwargs)
def get_callbacks(self, evaluator):
return _PluginCallbacks(self._build_chain(evaluator))
name = callback.__name__
assert name not in self._built_functions
built_functions = self._built_functions
built_functions[name] = callback
self._cached_base_callbacks[name] = callback
return wrapper
return decorator
def _build_functions(self):
for name, callback in self._cached_base_callbacks.items():
for plugin in reversed(self._registered_plugins):
# Need to reverse so the first plugin is run first.
try:
func = getattr(plugin, name)
except AttributeError:
pass
else:
callback = func(callback)
self._built_functions[name] = callback
class _PluginCallbacks(object):
def __init__(self, plugins):
self._plugins = list(plugins)
def decorate(self, name, callback):
for plugin in reversed(self._plugins):
# Need to reverse so the first plugin is run first.
callback = getattr(plugin, name)(callback)
return callback
plugin_manager = _PluginManager([
StdlibPlugin,
FlaskPlugin,
])
plugin_manager = _PluginManager()

View File

@@ -1,21 +0,0 @@
class BasePlugin(object):
"""
Plugins are created each time an evaluator is created.
"""
def __init__(self, evaluator):
# In __init__ you can do some caching.
self._evaluator = evaluator
def execute(self, callback):
"""
Decorates the execute(context, arguments) function.
"""
return callback
def import_module(self, callback):
"""
Decorates the
import_module(evaluator, import_path, sys_path, add_error_callback)
function.
"""
return callback

View File

@@ -1,25 +1,21 @@
from jedi.plugins.base import BasePlugin
class FlaskPlugin(BasePlugin):
def import_module(self, callback):
"""
Handle "magic" Flask extension imports:
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
"""
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
# New style.
ipath = (u'flask_' + import_names[2]),
context_set = callback(evaluator, ipath, None, *args, **kwargs)
if context_set:
return context_set
context_set = callback(evaluator, (u'flaskext',), None, *args, **kwargs)
return callback(
evaluator,
(u'flaskext', import_names[2]),
next(iter(context_set)),
*args, **kwargs
)
return callback(evaluator, import_names, module_context, *args, **kwargs)
return wrapper
def import_module(callback):
"""
Handle "magic" Flask extension imports:
``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
"""
def wrapper(evaluator, import_names, module_context, *args, **kwargs):
if len(import_names) == 3 and import_names[:2] == ('flask', 'ext'):
# New style.
ipath = (u'flask_' + import_names[2]),
context_set = callback(evaluator, ipath, None, *args, **kwargs)
if context_set:
return context_set
context_set = callback(evaluator, (u'flaskext',), None, *args, **kwargs)
return callback(
evaluator,
(u'flaskext', import_names[2]),
next(iter(context_set)),
*args, **kwargs
)
return callback(evaluator, import_names, module_context, *args, **kwargs)
return wrapper

10
jedi/plugins/registry.py Normal file
View File

@@ -0,0 +1,10 @@
"""
This is not a plugin, this is just the place were plugins are registered.
"""
from jedi.plugins import stdlib
from jedi.plugins import flask
from jedi.plugins import plugin_manager
plugin_manager.register(stdlib, flask)

View File

@@ -10,26 +10,31 @@ the standard library. The usual way to understand the standard library is the
compiled module that returns the types for C-builtins.
"""
import parso
import os
from jedi._compatibility import force_unicode
from jedi.plugins.base import BasePlugin
from jedi._compatibility import force_unicode, Parameter
from jedi import debug
from jedi.evaluate.utils import safe_property
from jedi.evaluate.helpers import get_str_or_none
from jedi.evaluate.arguments import ValuesArguments, \
repack_with_argument_clinic, AbstractArguments, TreeArgumentsWrapper
from jedi.evaluate import analysis
from jedi.evaluate import compiled
from jedi.evaluate.context.instance import \
AbstractInstanceContext, BoundMethod, InstanceArguments
from jedi.evaluate.context.instance import BoundMethod, InstanceArguments
from jedi.evaluate.base_context import ContextualizedNode, \
NO_CONTEXTS, ContextSet, ContextWrapper
NO_CONTEXTS, ContextSet, ContextWrapper, LazyContextWrapper
from jedi.evaluate.context import ClassContext, ModuleContext, \
FunctionExecutionContext
from jedi.evaluate.context.klass import ClassMixin
from jedi.evaluate.context.function import FunctionMixin
from jedi.evaluate.context import iterable
from jedi.evaluate.lazy_context import LazyTreeContext, LazyKnownContext, \
LazyKnownContexts
from jedi.evaluate.names import ContextName, BaseTreeParamName
from jedi.evaluate.syntax_tree import is_string
from jedi.evaluate.filters import AttributeOverwrite, publish_method
from jedi.evaluate.filters import AttributeOverwrite, publish_method, \
ParserTreeFilter, DictFilter
from jedi.evaluate.signature import AbstractSignature, SignatureWrapper
# Copied from Python 3.6's stdlib.
@@ -99,45 +104,48 @@ _NAMEDTUPLE_FIELD_TEMPLATE = '''\
'''
class StdlibPlugin(BasePlugin):
def execute(self, callback):
def wrapper(context, arguments):
try:
obj_name = context.name.string_name
except AttributeError:
pass
else:
if context.parent_context == self._evaluator.builtins_module:
module_name = 'builtins'
elif context.parent_context is not None and context.parent_context.is_module():
module_name = context.parent_context.py__name__()
else:
return callback(context, arguments=arguments)
if isinstance(context, BoundMethod):
if module_name == 'builtins':
if context.py__name__() == '__get__':
if context.class_context.py__name__() == 'property':
return builtins_property(
context,
arguments=arguments
)
elif context.py__name__() in ('deleter', 'getter', 'setter'):
if context.class_context.py__name__() == 'property':
return ContextSet([context.instance])
return callback(context, arguments=arguments)
# for now we just support builtin functions.
try:
func = _implemented[module_name][obj_name]
except KeyError:
pass
else:
return func(context, arguments=arguments)
def execute(callback):
def wrapper(context, arguments):
def call():
return callback(context, arguments=arguments)
return wrapper
try:
obj_name = context.name.string_name
except AttributeError:
pass
else:
if context.parent_context == context.evaluator.builtins_module:
module_name = 'builtins'
elif context.parent_context is not None and context.parent_context.is_module():
module_name = context.parent_context.py__name__()
else:
return call()
if isinstance(context, BoundMethod):
if module_name == 'builtins':
if context.py__name__() == '__get__':
if context.class_context.py__name__() == 'property':
return builtins_property(
context,
arguments=arguments,
callback=call,
)
elif context.py__name__() in ('deleter', 'getter', 'setter'):
if context.class_context.py__name__() == 'property':
return ContextSet([context.instance])
return call()
# for now we just support builtin functions.
try:
func = _implemented[module_name][obj_name]
except KeyError:
pass
else:
return func(context, arguments=arguments, callback=call)
return call()
return wrapper
def _follow_param(evaluator, arguments, index):
@@ -150,15 +158,18 @@ def _follow_param(evaluator, arguments, index):
def argument_clinic(string, want_obj=False, want_context=False,
want_arguments=False, want_evaluator=False):
want_arguments=False, want_evaluator=False,
want_callback=False):
"""
Works like Argument Clinic (PEP 436), to validate function params.
"""
def f(func):
@repack_with_argument_clinic(string, keep_arguments_param=True)
@repack_with_argument_clinic(string, keep_arguments_param=True,
keep_callback_param=True)
def wrapper(obj, *args, **kwargs):
arguments = kwargs.pop('arguments')
callback = kwargs.pop('callback')
assert not kwargs # Python 2...
debug.dbg('builtin start %s' % obj, color='MAGENTA')
result = NO_CONTEXTS
@@ -170,6 +181,8 @@ def argument_clinic(string, want_obj=False, want_context=False,
kwargs['evaluator'] = obj.evaluator
if want_arguments:
kwargs['arguments'] = arguments
if want_callback:
kwargs['callback'] = callback
result = func(*args, **kwargs)
debug.dbg('builtin end: %s', result, color='MAGENTA')
return result
@@ -230,20 +243,38 @@ def builtins_type(objects, bases, dicts):
return objects.py__class__()
class SuperInstance(AbstractInstanceContext):
class SuperInstance(LazyContextWrapper):
"""To be used like the object ``super`` returns."""
def __init__(self, evaluator, cls):
su = cls.py_mro()[1]
super().__init__(evaluator, su and su[0] or self)
def __init__(self, evaluator, instance):
self.evaluator = evaluator
self._instance = instance # Corresponds to super().__self__
def _get_bases(self):
return self._instance.py__class__().py__bases__()
def _get_wrapped_context(self):
objs = self._get_bases()[0].infer().execute_evaluated()
if not objs:
# This is just a fallback and will only be used, if it's not
# possible to find a class
return self._instance
return next(iter(objs))
def get_filters(self, search_global=False, until_position=None, origin_scope=None):
for b in self._get_bases():
for obj in b.infer().execute_evaluated():
for f in obj.get_filters():
yield f
@argument_clinic('[type[, obj]], /', want_context=True)
def builtins_super(types, objects, context):
# TODO make this able to detect multiple inheritance super
if isinstance(context, FunctionExecutionContext):
if isinstance(context.var_args, InstanceArguments):
su = context.var_args.instance.py__class__().py__bases__()
return su[0].infer().execute_evaluated()
instance = context.var_args.instance
# TODO if a class is given it doesn't have to be the direct super
# class, it can be an anecestor from long ago.
return ContextSet({SuperInstance(instance.evaluator, instance)})
return NO_CONTEXTS
@@ -361,6 +392,9 @@ class ClassMethodGet(AttributeOverwrite, ContextWrapper):
self._class = klass
self._function = function
def get_signatures(self):
return self._function.get_signatures()
def get_object(self):
return self._wrapped_context
@@ -388,7 +422,7 @@ def builtins_classmethod(functions, obj, arguments):
)
def collections_namedtuple(obj, arguments):
def collections_namedtuple(obj, arguments, callback):
"""
Implementation of the namedtuple function.
@@ -411,14 +445,16 @@ def collections_namedtuple(obj, arguments):
if not param_contexts:
return NO_CONTEXTS
_fields = list(param_contexts)[0]
if isinstance(_fields, compiled.CompiledValue):
fields = force_unicode(_fields.get_safe_value()).replace(',', ' ').split()
string = get_str_or_none(_fields)
if string is not None:
fields = force_unicode(string).replace(',', ' ').split()
elif isinstance(_fields, iterable.Sequence):
fields = [
force_unicode(v.get_safe_value())
force_unicode(get_str_or_none(v))
for lazy_context in _fields.py__iter__()
for v in lazy_context.infer() if is_string(v)
for v in lazy_context.infer()
]
fields = [f for f in fields if f is not None]
else:
return NO_CONTEXTS
@@ -454,17 +490,49 @@ class PartialObject(object):
def __getattr__(self, name):
return getattr(self._actual_context, name)
def py__call__(self, arguments):
key, lazy_context = next(self._arguments.unpack(), (None, None))
def _get_function(self, unpacked_arguments):
key, lazy_context = next(unpacked_arguments, (None, None))
if key is not None or lazy_context is None:
debug.warning("Partial should have a proper function %s", self._arguments)
return None
return lazy_context.infer()
def get_signatures(self):
unpacked_arguments = self._arguments.unpack()
func = self._get_function(unpacked_arguments)
if func is None:
return []
arg_count = 0
keys = set()
for key, _ in unpacked_arguments:
if key is None:
arg_count += 1
else:
keys.add(key)
return [PartialSignature(s, arg_count, keys) for s in func.get_signatures()]
def py__call__(self, arguments):
func = self._get_function(self._arguments.unpack())
if func is None:
return NO_CONTEXTS
return lazy_context.infer().execute(
return func.execute(
MergedPartialArguments(self._arguments, arguments)
)
class PartialSignature(SignatureWrapper):
def __init__(self, wrapped_signature, skipped_arg_count, skipped_arg_set):
super(PartialSignature, self).__init__(wrapped_signature)
self._skipped_arg_count = skipped_arg_count
self._skipped_arg_set = skipped_arg_set
def get_param_names(self, resolve_stars=False):
names = self._wrapped_signature.get_param_names()[self._skipped_arg_count:]
return [n for n in names if n.string_name not in self._skipped_arg_set]
class MergedPartialArguments(AbstractArguments):
def __init__(self, partial_arguments, call_arguments):
self._partial_arguments = partial_arguments
@@ -481,7 +549,7 @@ class MergedPartialArguments(AbstractArguments):
yield key_lazy_context
def functools_partial(obj, arguments):
def functools_partial(obj, arguments, callback):
return ContextSet(
PartialObject(instance, arguments)
for instance in obj.py__call__(arguments)
@@ -502,6 +570,66 @@ def _random_choice(sequences):
)
def _dataclass(obj, arguments, callback):
for c in _follow_param(obj.evaluator, arguments, 0):
if c.is_class():
return ContextSet([DataclassWrapper(c)])
else:
return ContextSet([obj])
return NO_CONTEXTS
class DataclassWrapper(ContextWrapper, ClassMixin):
def get_signatures(self):
param_names = []
for cls in reversed(list(self.py__mro__())):
if isinstance(cls, DataclassWrapper):
filter_ = cls.get_global_filter()
# .values ordering is not guaranteed, at least not in
# Python < 3.6, when dicts where not ordered, which is an
# implementation detail anyway.
for name in sorted(filter_.values(), key=lambda name: name.start_pos):
d = name.tree_name.get_definition()
annassign = d.children[1]
if d.type == 'expr_stmt' and annassign.type == 'annassign':
if len(annassign.children) < 4:
default = None
else:
default = annassign.children[3]
param_names.append(DataclassParamName(
parent_context=cls.parent_context,
tree_name=name.tree_name,
annotation_node=annassign.children[1],
default_node=default,
))
return [DataclassSignature(cls, param_names)]
class DataclassSignature(AbstractSignature):
def __init__(self, context, param_names):
super(DataclassSignature, self).__init__(context)
self._param_names = param_names
def get_param_names(self, resolve_stars=False):
return self._param_names
class DataclassParamName(BaseTreeParamName):
def __init__(self, parent_context, tree_name, annotation_node, default_node):
super(DataclassParamName, self).__init__(parent_context, tree_name)
self.annotation_node = annotation_node
self.default_node = default_node
def get_kind(self):
return Parameter.POSITIONAL_OR_KEYWORD
def infer(self):
if self.annotation_node is None:
return NO_CONTEXTS
else:
return self.parent_context.eval_node(self.annotation_node)
class ItemGetterCallable(ContextWrapper):
def __init__(self, instance, args_context_set):
super(ItemGetterCallable, self).__init__(instance)
@@ -527,6 +655,33 @@ class ItemGetterCallable(ContextWrapper):
return context_set
@argument_clinic('func, /')
def _functools_wraps(funcs):
return ContextSet(WrapsCallable(func) for func in funcs)
class WrapsCallable(ContextWrapper):
# XXX this is not the correct wrapped context, it should be a weird
# partials object, but it doesn't matter, because it's always used as a
# decorator anyway.
@repack_with_argument_clinic('func, /')
def py__call__(self, funcs):
return ContextSet({Wrapped(func, self._wrapped_context) for func in funcs})
class Wrapped(ContextWrapper, FunctionMixin):
def __init__(self, func, original_function):
super(Wrapped, self).__init__(func)
self._original_function = original_function
@property
def name(self):
return self._original_function.name
def get_signature_functions(self):
return [self]
@argument_clinic('*args, /', want_obj=True, want_arguments=True)
def _operator_itemgetter(args_context_set, obj, arguments):
return ContextSet([
@@ -535,6 +690,44 @@ def _operator_itemgetter(args_context_set, obj, arguments):
])
def _create_string_input_function(func):
@argument_clinic('string, /', want_obj=True, want_arguments=True)
def wrapper(strings, obj, arguments):
def iterate():
for context in strings:
s = get_str_or_none(context)
if s is not None:
s = func(s)
yield compiled.create_simple_object(context.evaluator, s)
contexts = ContextSet(iterate())
if contexts:
return contexts
return obj.py__call__(arguments)
return wrapper
@argument_clinic('*args, /', want_callback=True)
def _os_path_join(args_set, callback):
if len(args_set) == 1:
string = u''
sequence, = args_set
is_first = True
for lazy_context in sequence.py__iter__():
string_contexts = lazy_context.infer()
if len(string_contexts) != 1:
break
s = get_str_or_none(next(iter(string_contexts)))
if s is None:
break
if not is_first:
string += os.path.sep
string += force_unicode(s)
is_first = False
else:
return ContextSet([compiled.create_simple_object(sequence.evaluator, string)])
return callback()
_implemented = {
'builtins': {
'getattr': builtins_getattr,
@@ -552,15 +745,15 @@ _implemented = {
'deepcopy': _return_first_param,
},
'json': {
'load': lambda obj, arguments: NO_CONTEXTS,
'loads': lambda obj, arguments: NO_CONTEXTS,
'load': lambda obj, arguments, callback: NO_CONTEXTS,
'loads': lambda obj, arguments, callback: NO_CONTEXTS,
},
'collections': {
'namedtuple': collections_namedtuple,
},
'functools': {
'partial': functools_partial,
'wraps': _return_first_param,
'wraps': _functools_wraps,
},
'_weakref': {
'proxy': _return_first_param,
@@ -580,6 +773,63 @@ _implemented = {
# The _alias function just leads to some annoying type inference.
# Therefore, just make it return nothing, which leads to the stubs
# being used instead. This only matters for 3.7+.
'_alias': lambda obj, arguments: NO_CONTEXTS,
'_alias': lambda obj, arguments, callback: NO_CONTEXTS,
},
'dataclasses': {
# For now this works at least better than Jedi trying to understand it.
'dataclass': _dataclass
},
'os.path': {
'dirname': _create_string_input_function(os.path.dirname),
'abspath': _create_string_input_function(os.path.abspath),
'relpath': _create_string_input_function(os.path.relpath),
'join': _os_path_join,
}
}
def get_metaclass_filters(func):
def wrapper(cls, metaclasses):
for metaclass in metaclasses:
if metaclass.py__name__() == 'EnumMeta' \
and metaclass.get_root_context().py__name__() == 'enum':
filter_ = ParserTreeFilter(cls.evaluator, context=cls)
return [DictFilter({
name.string_name: EnumInstance(cls, name).name for name in filter_.values()
})]
return func(cls, metaclasses)
return wrapper
class EnumInstance(LazyContextWrapper):
def __init__(self, cls, name):
self.evaluator = cls.evaluator
self._cls = cls # Corresponds to super().__self__
self._name = name
self.tree_node = self._name.tree_name
@safe_property
def name(self):
return ContextName(self, self._name.tree_name)
def _get_wrapped_context(self):
obj, = self._cls.execute_evaluated()
return obj
def get_filters(self, search_global=False, position=None, origin_scope=None):
yield DictFilter(dict(
name=compiled.create_simple_object(self.evaluator, self._name.string_name).name,
value=self._name,
))
for f in self._get_wrapped_context().get_filters():
yield f
def tree_name_to_contexts(func):
def wrapper(evaluator, context, tree_name):
if tree_name.value == 'sep' and context.is_module() and context.py__name__() == 'os.path':
return ContextSet({
compiled.create_simple_object(evaluator, os.path.sep),
})
return func(evaluator, context, tree_name)
return wrapper

View File

@@ -11,10 +11,7 @@ import re
import os
import sys
from parso import split_lines
from jedi import Interpreter
from jedi.api.helpers import get_on_completion_name
READLINE_DEBUG = False
@@ -86,23 +83,18 @@ def setup_readline(namespace_module=__main__):
logging.debug("Start REPL completion: " + repr(text))
interpreter = Interpreter(text, [namespace_module.__dict__])
lines = split_lines(text)
position = (len(lines), len(lines[-1]))
name = get_on_completion_name(
interpreter._module_node,
lines,
position
)
before = text[:len(text) - len(name)]
completions = interpreter.completions()
logging.debug("REPL completions: %s", completions)
self.matches = [
text[:len(text) - c._like_name_length] + c.name_with_symbols
for c in completions
]
except:
logging.error("REPL Completion error:\n" + traceback.format_exc())
raise
finally:
sys.path.pop(0)
self.matches = [before + c.name_with_symbols for c in completions]
try:
return self.matches[state]
except IndexError:

View File

@@ -1 +1 @@
parso>=0.3.0
parso>=0.5.0

View File

@@ -37,7 +37,8 @@ setup(name='jedi',
install_requires=install_requires,
extras_require={
'testing': [
'pytest>=3.1.0',
# Pytest 5 doesn't support Python 2 and Python 3.4 anymore.
'pytest>=3.1.0,<5.0.0',
# docopt for sith doctests
'docopt',
# coloroma for colored debug output
@@ -60,6 +61,7 @@ setup(name='jedi',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Software Development :: Libraries :: Python Modules',
'Topic :: Text Editors :: Integrated Development Environments (IDE)',
'Topic :: Utilities',

View File

@@ -18,10 +18,12 @@ int(str)
str..
#? []
a(0):.
#? 2 ['and', 'or', 'if', 'is', 'in', 'not']
#? 2 []
0x0
#? ['and', 'or', 'if', 'is', 'in', 'not']
#? []
1j
#? ['and', 'or', 'if', 'is', 'in', 'not']
1j
x = None()
#?
x

View File

@@ -279,7 +279,7 @@ V(1).c()
V(1).d()
# Only keywords should be possible to complete.
#? ['is', 'in', 'not', 'and', 'or', 'if']
V(1).d()
V(1).d()
# -----------------

View File

@@ -310,11 +310,14 @@ follow_statement(1)
# class decorators should just be ignored
@should_ignore
class A():
x = 3
def ret(self):
return 1
#? int()
A().ret()
#? int()
A().x
# -----------------

View File

@@ -109,6 +109,23 @@ class X():
#! 3 []
X(foo=x)
# Multiple inheritance
class Foo:
def foo(self):
print("foo")
class Bar:
def bar(self):
print("bar")
class Baz(Foo, Bar):
def baz(self):
#! ['def foo']
super().foo
#! ['def bar']
super().bar
#! ['instance Foo']
super()
# -----------------
# imports
# -----------------
@@ -203,9 +220,10 @@ for key, value in [(1,2)]:
#! ['for key, value in [(1,2)]: key']
key
for i in []:
#! ['for i in []: i']
i
#! 4 ['for y in [1]: y']
for y in [1]:
#! ['for y in [1]: y']
y
# -----------------
# decorator

View File

@@ -1,4 +1,4 @@
import recurse_class2
from . import recurse_class2
class C(recurse_class2.C):
def a(self):

View File

@@ -1,4 +1,4 @@
import recurse_class1
from . import recurse_class1
class C(recurse_class1.C):
pass

View File

@@ -165,3 +165,19 @@ def keyword_only(a: str, *, b: str):
a.startswi
#? ['startswith']
b.startswi
def argskwargs(*args: int, **kwargs: float):
"""
This might be a bit confusing, but is part of the standard.
args is changed to Tuple[int] in this case and kwargs to Dict[str, float],
which makes sense if you think about it a bit.
"""
#? tuple()
args
#? int()
args[0]
#? str()
next(iter(kwargs.keys()))
#? float()
kwargs['']

View File

@@ -256,6 +256,31 @@ for key in x.keys():
for value in x.values():
#? int()
value
WrappingType = typing.NewType('WrappingType', str) # Chosen arbitrarily
y = WrappingType(0) # Per https://github.com/davidhalter/jedi/issues/1015#issuecomment-355795929
#? str()
y
def testnewtype(y):
"""
:type y: WrappingType
"""
#? str()
y
#? ["upper"]
y.u
WrappingType2 = typing.NewType()
def testnewtype2(y):
"""
:type y: WrappingType2
"""
#?
y
#? []
y.
# python >= 3.4
class TestDefaultDict(typing.DefaultDict[str, int]):
@@ -295,7 +320,6 @@ import typing as t
def union2(x: t.Union[int, str]):
#? int() str()
x
from typing import Union
def union3(x: Union[int, str]):
#? int() str()

View File

@@ -38,12 +38,16 @@ for char in NOT_DEFINED:
char
# -------------------------
# instance/class vars
# -------------------------
class Foo():
bar: int
baz: typing.ClassVar[str]
#? int()
#?
Foo.bar
#? int()
Foo().bar
@@ -51,3 +55,61 @@ Foo().bar
Foo.baz
#? str()
Foo().baz
class VarClass:
var_instance1: int = 1
var_instance2: float
var_class1: typing.ClassVar[str] = 1
var_class2: typing.ClassVar[bytes]
def __init__(self):
#? int()
d.var_instance1
#? float()
d.var_instance2
#? str()
d.var_class1
#? bytes()
d.var_class2
#? []
d.int
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2']
self.var_
#? ['var_class1', 'var_class2']
VarClass.var_
#?
VarClass.var_instance1
#?
VarClass.var_instance2
#? str()
VarClass.var_class1
#? bytes()
VarClass.var_class2
#? []
VarClass.int
d = VarClass()
#? ['var_class1', 'var_class2', 'var_instance1', 'var_instance2']
d.var_
#? int()
d.var_instance1
#? float()
d.var_instance2
#? str()
d.var_class1
#? bytes()
d.var_class2
#? []
d.int
import dataclasses
@dataclasses.dataclass
class DC:
name: int = 1
#? int()
DC().name

View File

@@ -67,6 +67,18 @@ class X(): pass
#? type
type(X)
# -----------------
# type() calls with multiple parameters
# -----------------
X = type('X', (object,), dict(a=1))
# Doesn't work yet.
#?
X.a
#?
X
if os.path.isfile():
#? ['abspath']
fails = os.path.abspath
@@ -290,3 +302,31 @@ class Test(metaclass=Meta):
result = super(Test, self).test_function()
#? []
result.
# -----------------
# Enum
# -----------------
# python >= 3.4
import enum
class X(enum.Enum):
attr_x = 3
attr_y = 2.0
#? ['mro']
X.mro
#? ['attr_x', 'attr_y']
X.attr_
#? str()
X.attr_x.name
#? int()
X.attr_x.value
#? str()
X.attr_y.name
#? float()
X.attr_y.value
#? str()
X().name
#? float()
X().attr_x.attr_y.value

View File

@@ -73,7 +73,7 @@ def _check_number(Script, source, result='float'):
def test_completion_on_number_literals(Script):
# No completions on an int literal (is a float).
assert [c.name for c in Script('1.').completions()] \
assert [c.name for c in Script('1. ').completions()] \
== ['and', 'if', 'in', 'is', 'not', 'or']
# Multiple points after an int literal basically mean that there's a float
@@ -109,9 +109,12 @@ def test_completion_on_complex_literals(Script):
_check_number(Script, '1j.', 'complex')
_check_number(Script, '44.j.', 'complex')
_check_number(Script, '4.0j.', 'complex')
# No dot no completion - I thought, but 4j is actually a literall after
# No dot no completion - I thought, but 4j is actually a literal after
# which a keyword like or is allowed. Good times, haha!
assert ({c.name for c in Script('4j').completions()} ==
# However this has been disabled again, because it apparently annoyed
# users. So no completion after j without a space :)
assert not Script('4j').completions()
assert ({c.name for c in Script('4j ').completions()} ==
{'if', 'and', 'in', 'is', 'not', 'or'})

View File

@@ -1,12 +1,13 @@
import sys
from textwrap import dedent
import inspect
import warnings
import pytest
from ..helpers import TestCase
from jedi import cache
from jedi.parser_utils import get_call_signature
from jedi import Interpreter
def assert_signature(Script, source, expected_name, expected_index=0, line=None, column=None):
@@ -43,7 +44,7 @@ class TestCallSignatures(TestCase):
run(s1, 'sorted', 0, 7)
run(s1, 'sorted', 1, 9)
run(s1, 'sorted', 1, 10)
run(s1, 'sorted', 1, 11)
run(s1, 'sorted', None, 11)
run(s1, 'bool', 0, 15)
s2 = "abs(), "
@@ -272,6 +273,13 @@ def test_int_params(Script):
assert sig2.params[0].name == 'x'
def test_pow_params(Script):
# See Github #1357.
for sig in Script('pow(').call_signatures():
param_names = [p.name for p in sig.params]
assert param_names in (['x', 'y'], ['x', 'y', 'z'])
def test_param_name(Script):
sigs = Script('open(something,').call_signatures()
for sig in sigs:
@@ -307,7 +315,8 @@ def test_signature_is_definition(Script):
# Now compare all the attributes that a CallSignature must also have.
for attr_name in dir(definition):
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer', 'params']
dont_scan = ['defined_names', 'parent', 'goto_assignments', 'infer',
'params', 'get_signatures', 'execute']
if attr_name.startswith('_') or attr_name in dont_scan:
continue
@@ -394,6 +403,148 @@ def test_keyword_argument_index(Script, environment):
assert get(both + 'foo(a, b, c').index == 0
code1 = 'def f(u, /, v=3, *, abc, abd, xyz): pass'
code2 = 'def g(u, /, v=3, *, abc, abd, xyz, **kwargs): pass'
code3 = 'def h(u, /, v, *args, x=1, y): pass'
code4 = 'def i(u, /, v, *args, x=1, y, **kwargs): pass'
_calls = [
# No *args, **kwargs
(code1, 'f(', 0),
(code1, 'f(a', 0),
(code1, 'f(a,', 1),
(code1, 'f(a,b', 1),
(code1, 'f(a,b,', 2),
(code1, 'f(a,b,c', None),
(code1, 'f(a,b,a', 2),
(code1, 'f(a,b,a=', None),
(code1, 'f(a,b,abc', 2),
(code1, 'f(a,b,abc=(', 2),
(code1, 'f(a,b,abc=(f,1,2,3', 2),
(code1, 'f(a,b,abd', 3),
(code1, 'f(a,b,x', 4),
(code1, 'f(a,b,xy', 4),
(code1, 'f(a,b,xyz=', 4),
(code1, 'f(a,b,xy=', None),
(code1, 'f(u=', (0, None)),
(code1, 'f(v=', 1),
# **kwargs
(code2, 'g(a,b,a', 2),
(code2, 'g(a,b,abc', 2),
(code2, 'g(a,b,abd', 3),
(code2, 'g(a,b,arr', 5),
(code2, 'g(a,b,xy', 4),
(code2, 'g(a,b,xyz=', 4),
(code2, 'g(a,b,xy=', 5),
(code2, 'g(a,b,abc=1,abd=4,', 4),
(code2, 'g(a,b,abc=1,xyz=3,abd=4,', 5),
(code2, 'g(a,b,abc=1,abd=4,lala', 5),
(code2, 'g(a,b,abc=1,abd=4,lala=', 5),
(code2, 'g(a,b,abc=1,abd=4,abd=', 5),
(code2, 'g(a,b,kw', 5),
(code2, 'g(a,b,kwargs=', 5),
(code2, 'g(u=', (0, 5)),
(code2, 'g(v=', 1),
# *args
(code3, 'h(a,b,c', 2),
(code3, 'h(a,b,c,', 2),
(code3, 'h(a,b,c,d', 2),
(code3, 'h(a,b,c,d[', 2),
(code3, 'h(a,b,c,(3,', 2),
(code3, 'h(a,b,c,(3,)', 2),
(code3, 'h(a,b,args=', None),
(code3, 'h(u,v=', 1),
(code3, 'h(u=', (0, None)),
(code3, 'h(u,*xxx', 1),
(code3, 'h(u,*xxx,*yyy', 1),
(code3, 'h(u,*[]', 1),
(code3, 'h(u,*', 1),
(code3, 'h(u,*, *', 1),
(code3, 'h(u,1,**', 3),
(code3, 'h(u,**y', 1),
(code3, 'h(u,x=2,**', 1),
(code3, 'h(u,x=2,**y', 1),
(code3, 'h(u,v=2,**y', 3),
(code3, 'h(u,x=2,**vv', 1),
# *args, **kwargs
(code4, 'i(a,b,c,d', 2),
(code4, 'i(a,b,c,d,e', 2),
(code4, 'i(a,b,c,d,e=', 5),
(code4, 'i(a,b,c,d,e=3', 5),
(code4, 'i(a,b,c,d=,x=', 3),
(code4, 'i(a,b,c,d=5,x=4', 3),
(code4, 'i(a,b,c,d=5,x=4,y', 4),
(code4, 'i(a,b,c,d=5,x=4,y=3,', 5),
(code4, 'i(a,b,c,d=5,y=4,x=3,', 5),
(code4, 'i(a,b,c,d=4,', 3),
(code4, 'i(a,b,c,x=1,d=,', 4),
# Error nodes
(code4, 'i(1, [a,b', 1),
(code4, 'i(1, [a,b=,', 2),
(code4, 'i(1, [a?b,', 2),
(code4, 'i(1, [a?b,*', 2),
(code4, 'i(?b,*r,c', 1),
(code4, 'i(?*', 0),
(code4, 'i(?**', (0, 1)),
]
@pytest.mark.parametrize('ending', ['', ')'])
@pytest.mark.parametrize('code, call, expected_index', _calls)
def test_signature_index(skip_python2, Script, environment, code, call, expected_index, ending):
if isinstance(expected_index, tuple):
expected_index = expected_index[environment.version_info > (3, 8)]
if environment.version_info < (3, 8):
code = code.replace('/,', '')
sig, = Script(code + '\n' + call + ending, column=len(call)).call_signatures()
index = sig.index
assert expected_index == index
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't support __signature__")
@pytest.mark.parametrize('code', ['foo', 'instance.foo'])
def test_arg_defaults(Script, environment, code):
def foo(arg="bla", arg1=1):
pass
class Klass:
def foo(self, arg="bla", arg1=1):
pass
instance = Klass()
src = dedent("""
def foo2(arg="bla", arg1=1):
pass
class Klass2:
def foo2(self, arg="bla", arg1=1):
pass
instance = Klass2()
""")
executed_locals = dict()
exec(src, None, executed_locals)
locals_ = locals()
def iter_scripts():
yield Interpreter(code + '(', namespaces=[locals_])
yield Script(src + code + "2(")
yield Interpreter(code + '2(', namespaces=[executed_locals])
for script in iter_scripts():
signatures = script.call_signatures()
assert signatures[0].params[0].description in ('param arg="bla"', "param arg='bla'")
assert signatures[0].params[1].description == 'param arg1=1'
def test_bracket_start(Script):
def bracket_start(src):
signatures = Script(src).call_signatures()

View File

@@ -7,19 +7,16 @@ from inspect import cleandoc
import pytest
import jedi
from jedi import __doc__ as jedi_doc, names
from ..helpers import TestCase
from jedi import __doc__ as jedi_doc
from jedi.evaluate.compiled import CompiledContextName
def test_is_keyword(Script):
#results = Script('import ', 1, 1, None).goto_definitions()
#assert len(results) == 1 and results[0].is_keyword is True
results = Script('str', 1, 1, None).goto_definitions()
assert len(results) == 1 and results[0].is_keyword is False
def test_basedefinition_type(Script, environment):
def test_basedefinition_type(Script, names):
def make_definitions():
"""
Return a list of definitions for parametrized tests.
@@ -44,7 +41,7 @@ def test_basedefinition_type(Script, environment):
""")
definitions = []
definitions += names(source, environment=environment)
definitions += names(source)
source += dedent("""
variable = sys or C or x or f or g or g() or h""")
@@ -102,8 +99,8 @@ def test_function_call_signature_in_doc(Script):
assert "f(x, y=1, z='a')" in str(doc)
def test_param_docstring():
param = jedi.names("def test(parameter): pass", all_scopes=True)[1]
def test_param_docstring(names):
param = names("def test(parameter): pass", all_scopes=True)[1]
assert param.name == 'parameter'
assert param.docstring() == ''
@@ -140,28 +137,32 @@ def test_completion_docstring(Script, jedi_path):
docstr('abcd=3;abcd', '')
docstr('"hello"\nabcd=3\nabcd', '')
docstr(dedent('''
docstr(
dedent('''
def x():
"hello"
0
x'''),
'hello'
)
docstr(dedent('''
docstr(
dedent('''
def x():
"hello";0
x'''),
'hello'
)
# Shouldn't work with a tuple.
docstr(dedent('''
docstr(
dedent('''
def x():
"hello",0
x'''),
''
)
# Should also not work if we rename something.
docstr(dedent('''
docstr(
dedent('''
def x():
"hello"
y = x
@@ -216,75 +217,61 @@ def test_param_endings(Script):
assert [p.description for p in sig.params] == ['param a', 'param b=5', 'param c=""']
class TestIsDefinition(TestCase):
@pytest.fixture(autouse=True)
def init(self, environment):
self.environment = environment
def _def(self, source, index=-1):
return names(
dedent(source),
references=True,
all_scopes=True,
environment=self.environment
)[index]
def _bool_is_definitions(self, source):
ns = names(dedent(source), references=True, all_scopes=True)
# Assure that names are definitely sorted.
ns = sorted(ns, key=lambda name: (name.line, name.column))
return [name.is_definition() for name in ns]
def test_name(self):
d = self._def('name')
assert d.name == 'name'
assert not d.is_definition()
def test_stmt(self):
src = 'a = f(x)'
d = self._def(src, 0)
assert d.name == 'a'
assert d.is_definition()
d = self._def(src, 1)
assert d.name == 'f'
assert not d.is_definition()
d = self._def(src)
assert d.name == 'x'
assert not d.is_definition()
def test_import(self):
assert self._bool_is_definitions('import x as a') == [False, True]
assert self._bool_is_definitions('from x import y') == [False, True]
assert self._bool_is_definitions('from x.z import y') == [False, False, True]
@pytest.mark.parametrize(
'code, index, name, is_definition', [
('name', 0, 'name', False),
('a = f(x)', 0, 'a', True),
('a = f(x)', 1, 'f', False),
('a = f(x)', 2, 'x', False),
]
)
def test_is_definition(names, code, index, name, is_definition):
d = names(
dedent(code),
references=True,
all_scopes=True,
)[index]
assert d.name == name
assert d.is_definition() == is_definition
class TestParent(TestCase):
@pytest.fixture(autouse=True)
def init(self, Script):
self.Script = Script
@pytest.mark.parametrize(
'code, expected', (
('import x as a', [False, True]),
('from x import y', [False, True]),
('from x.z import y', [False, False, True]),
)
)
def test_is_definition_import(names, code, expected):
ns = names(dedent(code), references=True, all_scopes=True)
# Assure that names are definitely sorted.
ns = sorted(ns, key=lambda name: (name.line, name.column))
assert [name.is_definition() for name in ns] == expected
def _parent(self, source, line=None, column=None):
def_, = self.Script(dedent(source), line, column).goto_assignments()
def test_parent(Script):
def _parent(source, line=None, column=None):
def_, = Script(dedent(source), line, column).goto_assignments()
return def_.parent()
def test_parent(self):
parent = self._parent('foo=1\nfoo')
assert parent.type == 'module'
parent = _parent('foo=1\nfoo')
assert parent.type == 'module'
parent = self._parent('''
def spam():
if 1:
y=1
y''')
assert parent.name == 'spam'
assert parent.parent().type == 'module'
parent = _parent('''
def spam():
if 1:
y=1
y''')
assert parent.name == 'spam'
assert parent.parent().type == 'module'
def test_on_function(self):
parent = self._parent('''\
def spam():
pass''', 1, len('def spam'))
assert parent.name == ''
assert parent.type == 'module'
def test_parent_on_function(Script):
code = 'def spam():\n pass'
def_, = Script(code, line=1, column=len('def spam')).goto_assignments()
parent = def_.parent()
assert parent.name == ''
assert parent.type == 'module'
def test_parent_on_completion(Script):
@@ -331,8 +318,8 @@ different as an implementation.
"""
def test_goto_assignment_repetition(environment):
defs = names('a = 1; a', references=True, definitions=False, environment=environment)
def test_goto_assignment_repetition(names):
defs = names('a = 1; a', references=True, definitions=False)
# Repeat on the same variable. Shouldn't change once we're on a
# definition.
for _ in range(3):
@@ -341,34 +328,34 @@ def test_goto_assignment_repetition(environment):
assert ass[0].description == 'a = 1'
def test_goto_assignments_named_params(environment):
def test_goto_assignments_named_params(names):
src = """\
def foo(a=1, bar=2):
pass
foo(bar=1)
"""
bar = names(dedent(src), references=True, environment=environment)[-1]
bar = names(dedent(src), references=True)[-1]
param = bar.goto_assignments()[0]
assert (param.line, param.column) == (1, 13)
assert param.type == 'param'
def test_class_call(environment):
def test_class_call(names):
src = 'from threading import Thread; Thread(group=1)'
n = names(src, references=True, environment=environment)[-1]
n = names(src, references=True)[-1]
assert n.name == 'group'
param_def = n.goto_assignments()[0]
assert param_def.name == 'group'
assert param_def.type == 'param'
def test_parentheses(environment):
n = names('("").upper', references=True, environment=environment)[-1]
def test_parentheses(names):
n = names('("").upper', references=True)[-1]
assert n.goto_assignments()[0].name == 'upper'
def test_import(environment):
nms = names('from json import load', references=True, environment=environment)
def test_import(names):
nms = names('from json import load', references=True)
assert nms[0].name == 'json'
assert nms[0].type == 'module'
n = nms[0].goto_assignments()[0]
@@ -381,7 +368,7 @@ def test_import(environment):
assert n.name == 'load'
assert n.type == 'function'
nms = names('import os; os.path', references=True, environment=environment)
nms = names('import os; os.path', references=True)
assert nms[0].name == 'os'
assert nms[0].type == 'module'
n = nms[0].goto_assignments()[0]
@@ -392,7 +379,7 @@ def test_import(environment):
assert n.name == 'path'
assert n.type == 'module'
nms = names('import os.path', references=True, environment=environment)
nms = names('import os.path', references=True)
n = nms[0].goto_assignments()[0]
assert n.name == 'os'
assert n.type == 'module'
@@ -403,8 +390,8 @@ def test_import(environment):
assert n.type == 'module'
def test_import_alias(environment):
nms = names('import json as foo', references=True, environment=environment)
def test_import_alias(names):
nms = names('import json as foo', references=True)
assert nms[0].name == 'json'
assert nms[0].type == 'module'
assert nms[0]._name.tree_name.parent.type == 'dotted_as_name'
@@ -455,3 +442,20 @@ def test_builtin_module_with_path(Script):
assert semlock.name == 'SemLock'
assert semlock.line is None
assert semlock.column is None
@pytest.mark.parametrize(
'code, description', [
('int', 'instance int'),
('str.index', 'instance int'),
('1', None),
]
)
def test_execute(Script, code, description):
definition, = Script(code).goto_assignments()
definitions = definition.execute()
if description is None:
assert not definitions
else:
d, = definitions
assert d.description == description

View File

@@ -1,8 +1,9 @@
import os
from os.path import join, sep as s
import sys
from textwrap import dedent
import pytest
from ..helpers import root_dir
def test_in_whitespace(Script):
@@ -69,8 +70,8 @@ def test_points_in_completion(Script):
def test_loading_unicode_files_with_bad_global_charset(Script, monkeypatch, tmpdir):
dirname = str(tmpdir.mkdir('jedi-test'))
filename1 = os.path.join(dirname, 'test1.py')
filename2 = os.path.join(dirname, 'test2.py')
filename1 = join(dirname, 'test1.py')
filename2 = join(dirname, 'test2.py')
if sys.version_info < (3, 0):
data = "# coding: latin-1\nfoo = 'm\xf6p'\n"
else:
@@ -119,7 +120,8 @@ def test_generator(Script):
def test_in_comment(Script):
assert Script(" # Comment").completions()
assert Script("max_attr_value = int(2) # Cast to int for spe").completions()
# TODO this is a bit ugly, that the behaviors in comments are different.
assert not Script("max_attr_value = int(2) # Cast to int for spe").completions()
def test_async(Script, environment):
@@ -140,3 +142,126 @@ def test_async(Script, environment):
def test_with_stmt_error_recovery(Script):
assert Script('with open('') as foo: foo.\na', line=1).completions()
@pytest.mark.parametrize(
'code, has_keywords', (
('', True),
('x;', True),
('1', False),
('1 ', True),
('1\t', True),
('1\n', True),
('1\\\n', True),
)
)
def test_keyword_completion(Script, code, has_keywords):
assert has_keywords == any(x.is_keyword for x in Script(code).completions())
f1 = join(root_dir, 'example.py')
f2 = join(root_dir, 'test', 'example.py')
os_path = 'from os.path import *\n'
# os.path.sep escaped
se = s * 2 if s == '\\' else s
@pytest.mark.parametrize(
'file, code, column, expected', [
# General tests / relative paths
(None, '"comp', None, ['ile', 'lex']), # No files like comp
(None, '"test', None, [s]),
(None, '"test', 4, ['t' + s]),
('example.py', '"test%scomp' % s, None, ['letion' + s]),
('example.py', 'r"comp"', None, "A LOT"),
('example.py', 'r"tes"', None, "A LOT"),
('example.py', 'r"tes"', 5, ['t' + s]),
('example.py', 'r" tes"', 6, []),
('test%sexample.py' % se, 'r"tes"', 5, ['t' + s]),
('test%sexample.py' % se, 'r"test%scomp"' % s, 5, ['t' + s]),
('test%sexample.py' % se, 'r"test%scomp"' % s, 11, ['letion' + s]),
('test%sexample.py' % se, '"%s"' % join('test', 'completion', 'basi'), 21, ['c.py']),
('example.py', 'rb"' + join('..', 'jedi', 'tes'), None, ['t' + s]),
# Absolute paths
(None, '"' + join(root_dir, 'test', 'test_ca'), None, ['che.py"']),
(None, '"%s"' % join(root_dir, 'test', 'test_ca'), len(root_dir) + 14, ['che.py']),
# Longer quotes
('example.py', 'r"""test', None, [s]),
('example.py', 'r"""\ntest', None, []),
('example.py', 'u"""tes\n', (1, 7), ['t' + s]),
('example.py', '"""test%stest_cache.p"""' % s, 20, ['y']),
('example.py', '"""test%stest_cache.p"""' % s, 19, ['py"""']),
# Adding
('example.py', '"test" + "%stest_cac' % se, None, ['he.py"']),
('example.py', '"test" + "%s" + "test_cac' % se, None, ['he.py"']),
('example.py', 'x = 1 + "test', None, []),
('example.py', 'x = f("te" + "st)', 16, [s]),
('example.py', 'x = f("te" + "st', 16, [s]),
('example.py', 'x = f("te" + "st"', 16, [s]),
('example.py', 'x = f("te" + "st")', 16, [s]),
('example.py', 'x = f("t" + "est")', 16, [s]),
# This is actually not correct, but for now leave it here, because of
# Python 2.
('example.py', 'x = f(b"t" + "est")', 17, [s]),
('example.py', '"test" + "', None, [s]),
# __file__
(f1, os_path + 'dirname(__file__) + "%stest' % s, None, [s]),
(f2, os_path + 'dirname(__file__) + "%stest_ca' % se, None, ['che.py"']),
(f2, os_path + 'dirname(abspath(__file__)) + sep + "test_ca', None, ['che.py"']),
(f2, os_path + 'join(dirname(__file__), "completion") + sep + "basi', None, ['c.py"']),
(f2, os_path + 'join("test", "completion") + sep + "basi', None, ['c.py"']),
# inside join
(f2, os_path + 'join(dirname(__file__), "completion", "basi', None, ['c.py"']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 43, ['c.py"']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi")', 43, ['c.py']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 35, ['']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi)', 33, ['on"']),
(f2, os_path + 'join(dirname(__file__), "completion", "basi")', 33, ['on"']),
# join with one argument. join will not get evaluated and the result is
# that directories and in a slash. This is unfortunate, but doesn't
# really matter.
(f2, os_path + 'join("tes', 9, ['t"']),
(f2, os_path + 'join(\'tes)', 9, ["t'"]),
(f2, os_path + 'join(r"tes"', 10, ['t']),
(f2, os_path + 'join("""tes""")', 11, ['t']),
# Almost like join but not really
(f2, os_path + 'join["tes', 9, ['t' + s]),
(f2, os_path + 'join["tes"', 9, ['t' + s]),
(f2, os_path + 'join["tes"]', 9, ['t' + s]),
(f2, os_path + 'join[dirname(__file__), "completi', 33, []),
(f2, os_path + 'join[dirname(__file__), "completi"', 33, []),
(f2, os_path + 'join[dirname(__file__), "completi"]', 33, []),
# With full paths
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi', 49, ['on"']),
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi"', 49, ['on']),
(f2, 'import os\nos.path.join(os.path.dirname(__file__), "completi")', 49, ['on']),
# With alias
(f2, 'import os.path as p as p\np.join(p.dirname(__file__), "completi', None, ['on"']),
(f2, 'from os.path import dirname, join as j\nj(dirname(__file__), "completi',
None, ['on"']),
# Trying to break it
(f2, os_path + 'join(["tes', 10, ['t' + s]),
(f2, os_path + 'join(["tes"]', 10, ['t' + s]),
(f2, os_path + 'join(["tes"])', 10, ['t' + s]),
(f2, os_path + 'join("test", "test_cac" + x,', 22, ['he.py']),
]
)
def test_file_path_completions(Script, file, code, column, expected):
line = None
if isinstance(column, tuple):
line, column = column
comps = Script(code, path=file, line=line, column=column).completions()
if expected == "A LOT":
assert len(comps) > 100 # This is basically global completions.
else:
assert [c.complete for c in comps] == expected

View File

@@ -1,30 +1,22 @@
"""
Tests for `api.defined_names`.
Tests for `api.names`.
"""
from textwrap import dedent
import pytest
from jedi import names
from ..helpers import TestCase
def _assert_definition_names(definitions, names_):
assert [d.name for d in definitions] == names_
class TestDefinedNames(TestCase):
@pytest.fixture(autouse=True)
def init(self, environment):
self.environment = environment
def _check_names(names, source, names_):
definitions = names(dedent(source))
_assert_definition_names(definitions, names_)
return definitions
def assert_definition_names(self, definitions, names_):
assert [d.name for d in definitions] == names_
def check_defined_names(self, source, names_):
definitions = names(dedent(source), environment=self.environment)
self.assert_definition_names(definitions, names_)
return definitions
def test_get_definitions_flat(self):
self.check_defined_names("""
def test_get_definitions_flat(names):
_check_names(names, """
import module
class Class:
pass
@@ -33,130 +25,135 @@ class TestDefinedNames(TestCase):
data = None
""", ['module', 'Class', 'func', 'data'])
def test_dotted_assignment(self):
self.check_defined_names("""
x = Class()
x.y.z = None
""", ['x', 'z']) # TODO is this behavior what we want?
def test_multiple_assignment(self):
self.check_defined_names("""
x = y = None
""", ['x', 'y'])
def test_dotted_assignment(names):
_check_names(names, """
x = Class()
x.y.z = None
""", ['x', 'z']) # TODO is this behavior what we want?
def test_multiple_imports(self):
self.check_defined_names("""
from module import a, b
from another_module import *
""", ['a', 'b'])
def test_nested_definitions(self):
definitions = self.check_defined_names("""
class Class:
def f():
pass
def g():
pass
""", ['Class'])
subdefinitions = definitions[0].defined_names()
self.assert_definition_names(subdefinitions, ['f', 'g'])
self.assertEqual([d.full_name for d in subdefinitions],
['__main__.Class.f', '__main__.Class.g'])
def test_multiple_assignment(names):
_check_names(names, "x = y = None", ['x', 'y'])
def test_nested_class(self):
definitions = self.check_defined_names("""
class L1:
class L2:
class L3:
def f(): pass
def test_multiple_imports(names):
_check_names(names, """
from module import a, b
from another_module import *
""", ['a', 'b'])
def test_nested_definitions(names):
definitions = _check_names(names, """
class Class:
def f():
pass
def g():
pass
""", ['Class'])
subdefinitions = definitions[0].defined_names()
_assert_definition_names(subdefinitions, ['f', 'g'])
assert [d.full_name for d in subdefinitions] == ['__main__.Class.f', '__main__.Class.g']
def test_nested_class(names):
definitions = _check_names(names, """
class L1:
class L2:
class L3:
def f(): pass
def f(): pass
def f(): pass
""", ['L1', 'f'])
subdefs = definitions[0].defined_names()
subsubdefs = subdefs[0].defined_names()
self.assert_definition_names(subdefs, ['L2', 'f'])
self.assert_definition_names(subsubdefs, ['L3', 'f'])
self.assert_definition_names(subsubdefs[0].defined_names(), ['f'])
def f(): pass
""", ['L1', 'f'])
subdefs = definitions[0].defined_names()
subsubdefs = subdefs[0].defined_names()
_assert_definition_names(subdefs, ['L2', 'f'])
_assert_definition_names(subsubdefs, ['L3', 'f'])
_assert_definition_names(subsubdefs[0].defined_names(), ['f'])
def test_class_fields_with_all_scopes_false(self):
definitions = self.check_defined_names("""
from module import f
g = f(f)
class C:
h = g
def foo(x=a):
bar = x
return bar
""", ['f', 'g', 'C', 'foo'])
C_subdefs = definitions[-2].defined_names()
foo_subdefs = definitions[-1].defined_names()
self.assert_definition_names(C_subdefs, ['h'])
self.assert_definition_names(foo_subdefs, ['x', 'bar'])
def test_class_fields_with_all_scopes_false(names):
definitions = _check_names(names, """
from module import f
g = f(f)
class C:
h = g
def test_async_stmt_with_all_scopes_false(self):
definitions = self.check_defined_names("""
from module import f
import asyncio
def foo(x=a):
bar = x
return bar
""", ['f', 'g', 'C', 'foo'])
C_subdefs = definitions[-2].defined_names()
foo_subdefs = definitions[-1].defined_names()
_assert_definition_names(C_subdefs, ['h'])
_assert_definition_names(foo_subdefs, ['x', 'bar'])
g = f(f)
class C:
h = g
def __init__(self):
pass
async def __aenter__(self):
pass
def test_async_stmt_with_all_scopes_false(names):
definitions = _check_names(names, """
from module import f
import asyncio
def foo(x=a):
bar = x
return bar
g = f(f)
class C:
h = g
def __init__(self):
pass
async def async_foo(duration):
async def wait():
await asyncio.sleep(100)
for i in range(duration//100):
await wait()
return duration//100*100
async def __aenter__(self):
pass
async with C() as cinst:
d = cinst
""", ['f', 'asyncio', 'g', 'C', 'foo', 'async_foo', 'cinst', 'd'])
C_subdefs = definitions[3].defined_names()
foo_subdefs = definitions[4].defined_names()
async_foo_subdefs = definitions[5].defined_names()
cinst_subdefs = definitions[6].defined_names()
self.assert_definition_names(C_subdefs, ['h', '__init__', '__aenter__'])
self.assert_definition_names(foo_subdefs, ['x', 'bar'])
self.assert_definition_names(async_foo_subdefs, ['duration', 'wait', 'i'])
# We treat d as a name outside `async with` block
self.assert_definition_names(cinst_subdefs, [])
def foo(x=a):
bar = x
return bar
def test_follow_imports(environment):
async def async_foo(duration):
async def wait():
await asyncio.sleep(100)
for i in range(duration//100):
await wait()
return duration//100*100
async with C() as cinst:
d = cinst
""", ['f', 'asyncio', 'g', 'C', 'foo', 'async_foo', 'cinst', 'd'])
C_subdefs = definitions[3].defined_names()
foo_subdefs = definitions[4].defined_names()
async_foo_subdefs = definitions[5].defined_names()
cinst_subdefs = definitions[6].defined_names()
_assert_definition_names(C_subdefs, ['h', '__init__', '__aenter__'])
_assert_definition_names(foo_subdefs, ['x', 'bar'])
_assert_definition_names(async_foo_subdefs, ['duration', 'wait', 'i'])
# We treat d as a name outside `async with` block
_assert_definition_names(cinst_subdefs, [])
def test_follow_imports(names):
# github issue #344
imp = names('import datetime', environment=environment)[0]
imp = names('import datetime')[0]
assert imp.name == 'datetime'
datetime_names = [str(d.name) for d in imp.defined_names()]
assert 'timedelta' in datetime_names
def test_names_twice(environment):
def test_names_twice(names):
source = dedent('''
def lol():
pass
''')
defs = names(source=source, environment=environment)
defs = names(source=source)
assert defs[0].defined_names() == []
def test_simple_name(environment):
defs = names('foo', references=True, environment=environment)
def test_simple_name(names):
defs = names('foo', references=True)
assert not defs[0]._name.infer()
def test_no_error(environment):
def test_no_error(names):
code = dedent("""
def foo(a, b):
if a == 10:

View File

@@ -7,8 +7,8 @@ import pytest
import jedi
from jedi._compatibility import is_py3, py_version
from jedi.evaluate.compiled import mixed
from jedi.evaluate.compiled import mixed, context
from importlib import import_module
if py_version > 30:
def exec_(source, global_map):
@@ -197,7 +197,13 @@ def test_getitem_side_effects():
_assert_interpreter_complete('foo["asdf"].upper', locals(), ['upper'])
def test_property_error_oldstyle():
@pytest.fixture(params=[False, True])
def allow_descriptor_access_or_not(request, monkeypatch):
monkeypatch.setattr(jedi.Interpreter, '_allow_descriptor_getattr_default', request.param)
return request.param
def test_property_error_oldstyle(allow_descriptor_access_or_not):
lst = []
class Foo3:
@property
@@ -209,11 +215,14 @@ def test_property_error_oldstyle():
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
_assert_interpreter_complete('foo.bar.baz', locals(), [])
# There should not be side effects
assert lst == []
if allow_descriptor_access_or_not:
assert lst == [1, 1]
else:
# There should not be side effects
assert lst == []
def test_property_error_newstyle():
def test_property_error_newstyle(allow_descriptor_access_or_not):
lst = []
class Foo3(object):
@property
@@ -225,10 +234,25 @@ def test_property_error_newstyle():
_assert_interpreter_complete('foo.bar', locals(), ['bar'])
_assert_interpreter_complete('foo.bar.baz', locals(), [])
# There should not be side effects
assert lst == []
if allow_descriptor_access_or_not:
assert lst == [1, 1]
else:
# There should not be side effects
assert lst == []
def test_property_content():
class Foo3(object):
@property
def bar(self):
return 1
foo = Foo3()
def_, = jedi.Interpreter('foo.bar', [locals()]).goto_definitions()
assert def_.name == 'int'
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
def test_param_completion():
def foo(bar):
pass
@@ -262,7 +286,7 @@ def test_completion_params():
def test_completion_param_annotations():
# Need to define this function not directly in Python. Otherwise Jedi is to
# clever and uses the Python code instead of the signature object.
code = 'def foo(a: 1, b: str, c: int = 1.0): pass'
code = 'def foo(a: 1, b: str, c: int = 1.0) -> bytes: pass'
exec_(code, locals())
script = jedi.Interpreter('foo', [locals()])
c, = script.completions()
@@ -271,7 +295,11 @@ def test_completion_param_annotations():
assert [d.name for d in b.infer()] == ['str']
assert {d.name for d in c.infer()} == {'int', 'float'}
d, = jedi.Interpreter('foo()', [locals()]).goto_definitions()
assert d.name == 'bytes'
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
def test_keyword_argument():
def f(some_keyword_argument):
pass
@@ -423,3 +451,57 @@ def test_simple_completions(code, completions):
defs = jedi.Interpreter(code, [locals()]).completions()
assert [d.name for d in defs] == completions
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Python 2 doesn't have lru_cache")
def test__wrapped__():
from functools import lru_cache
@lru_cache(maxsize=128)
def syslogs_to_df():
pass
c, = jedi.Interpreter('syslogs_to_df', [locals()]).completions()
# Apparently the function starts on the line where the decorator starts.
assert c.line == syslogs_to_df.__wrapped__.__code__.co_firstlineno + 1
@pytest.mark.parametrize('module_name', ['sys', 'time'])
def test_core_module_completes(module_name):
module = import_module(module_name)
assert jedi.Interpreter(module_name + '.\n', [locals()]).completions()
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
@pytest.mark.parametrize(
'code, expected, index', [
('a(', ['a', 'b', 'c'], 0),
('b(', ['b', 'c'], 0),
# Might or might not be correct, because c is given as a keyword
# argument as well, but that is just what inspect.signature returns.
('c(', ['b', 'c'], 0),
]
)
def test_partial_signatures(code, expected, index):
import functools
def func(a, b, c):
pass
a = functools.partial(func)
b = functools.partial(func, 1)
c = functools.partial(func, 1, c=2)
sig, = jedi.Interpreter(code, [locals()]).call_signatures()
assert sig.name == 'partial'
assert [p.name for p in sig.params] == expected
assert index == sig.index
@pytest.mark.skipif(sys.version_info[0] == 2, reason="Ignore Python 2, because EOL")
def test_type_var():
"""This was an issue before, see Github #1369"""
import typing
x = typing.TypeVar('myvar')
def_, = jedi.Interpreter('x', [locals()]).goto_definitions()
assert def_.name == 'TypeVar'

View File

@@ -1,6 +1,7 @@
import os
from ..helpers import get_example_dir
from ..helpers import get_example_dir, set_cwd, root_dir
from jedi import Interpreter
def test_django_default_project(Script):
@@ -13,3 +14,11 @@ def test_django_default_project(Script):
c, = script.completions()
assert c.name == "SomeModel"
assert script._evaluator.project._django is True
def test_interpreter_project_path():
# Run from anywhere it should be the cwd.
dir = os.path.join(root_dir, 'test')
with set_cwd(dir):
project = Interpreter('', [locals()])._evaluator.project
assert project._path == dir

View File

@@ -0,0 +1,74 @@
import sys
import pytest
_tuple_code = 'from typing import Tuple\ndef f(x: Tuple[int]): ...\nf'
@pytest.mark.parametrize(
'code, expected_params, execute_annotation', [
('def f(x: 1, y): ...\nf', [None, None], True),
('def f(x: 1, y): ...\nf', ['instance int', None], False),
('def f(x: int): ...\nf', ['instance int'], True),
('from typing import List\ndef f(x: List[int]): ...\nf', ['instance list'], True),
('from typing import List\ndef f(x: List[int]): ...\nf', ['class list'], False),
(_tuple_code, ['Tuple: _SpecialForm = ...'], True),
(_tuple_code, ['Tuple: _SpecialForm = ...'], False),
('x=str\ndef f(p: x): ...\nx=int\nf', ['instance int'], True),
('def f(*args, **kwargs): ...\nf', [None, None], False),
('def f(*args: int, **kwargs: str): ...\nf', ['class int', 'class str'], False),
]
)
def test_param_annotation(Script, code, expected_params, execute_annotation, skip_python2):
func, = Script(code).goto_assignments()
sig, = func.get_signatures()
for p, expected in zip(sig.params, expected_params):
annotations = p.infer_annotation(execute_annotation=execute_annotation)
if expected is None:
assert not annotations
else:
annotation, = annotations
assert annotation.description == expected
@pytest.mark.parametrize(
'code, expected_params', [
('def f(x=1, y=int, z): pass\nf', ['instance int', 'class int', None]),
('def f(*args, **kwargs): pass\nf', [None, None]),
('x=1\ndef f(p=x): pass\nx=""\nf', ['instance int']),
]
)
def test_param_default(Script, code, expected_params):
func, = Script(code).goto_assignments()
sig, = func.get_signatures()
for p, expected in zip(sig.params, expected_params):
annotations = p.infer_default()
if expected is None:
assert not annotations
else:
annotation, = annotations
assert annotation.description == expected
@pytest.mark.skipif(sys.version_info < (3, 5), reason="Python <3.5 doesn't support __signature__")
@pytest.mark.parametrize(
'code, index, param_code, kind', [
('def f(x=1): pass\nf', 0, 'x=1', 'POSITIONAL_OR_KEYWORD'),
('def f(*args:int): pass\nf', 0, '*args: int', 'VAR_POSITIONAL'),
('def f(**kwargs: List[x]): pass\nf', 0, '**kwargs: List[x]', 'VAR_KEYWORD'),
('def f(*, x:int=5): pass\nf', 0, 'x: int=5', 'KEYWORD_ONLY'),
('def f(*args, x): pass\nf', 1, 'x', 'KEYWORD_ONLY'),
]
)
def test_param_kind_and_name(code, index, param_code, kind, Script, skip_python2):
func, = Script(code).goto_assignments()
sig, = func.get_signatures()
param = sig.params[index]
assert param.to_string() == param_code
assert param.kind.name == kind
def test_staticmethod(Script):
s, = Script('staticmethod(').call_signatures()
assert s.to_string() == 'staticmethod(f: Callable)'

View File

@@ -1,3 +1,6 @@
from jedi._compatibility import force_unicode
def test_module_attributes(Script):
def_, = Script('__name__').completions()
assert def_.name == '__name__'
@@ -5,3 +8,14 @@ def test_module_attributes(Script):
assert def_.column is None
str_, = def_.infer()
assert str_.name == 'str'
def test_module__file__(Script, environment):
assert not Script('__file__').goto_definitions()
def_, = Script('__file__', path='example.py').goto_definitions()
value = force_unicode(def_._name._context.get_safe_value())
assert value.endswith('example.py')
def_, = Script('import antigravity; antigravity.__file__').goto_definitions()
value = force_unicode(def_._name._context.get_safe_value())
assert value.endswith('.py')

View File

@@ -6,6 +6,7 @@ from textwrap import dedent
import jedi
import pytest
from ..helpers import unittest
import sys
try:
import numpydoc # NOQA
@@ -21,6 +22,11 @@ except ImportError:
else:
numpy_unavailable = False
if sys.version_info.major == 2:
# In Python 2 there's an issue with tox/docutils that makes the tests fail,
# Python 2 is soon end-of-life, so just don't support numpydoc for it anymore.
numpydoc_unavailable = True
def test_function_doc(Script):
defs = Script("""
@@ -385,3 +391,26 @@ def test_numpy_comp_returns():
)
names = [c.name for c in jedi.Script(s).completions()]
assert 'diagonal' in names
def test_decorator(Script):
code = dedent('''
def decorator(name=None):
def _decorate(func):
@wraps(func)
def wrapper(*args, **kwargs):
"""wrapper docstring"""
return func(*args, **kwargs)
return wrapper
return _decorate
@decorator('testing')
def check_user(f):
"""Nice docstring"""
pass
check_user''')
d, = Script(code).goto_definitions()
assert d.docstring(raw=True) == 'Nice docstring'

View File

@@ -462,3 +462,16 @@ def test_import_with_semicolon(Script):
names = [c.name for c in Script('xzy; from abc import ').completions()]
assert 'ABCMeta' in names
assert 'abc' not in names
def test_relative_import_star(Script):
# Coming from github #1235
import jedi
source = """
from . import *
furl.c
"""
script = jedi.Script(source, 3, len("furl.c"), 'export.py')
assert script.completions()

View File

@@ -1,5 +1,8 @@
import pytest
from textwrap import dedent
from operator import ge, lt
import re
import pytest
from jedi.evaluate.gradual.conversion import _stub_to_python_context_set
@@ -32,3 +35,234 @@ def test_compiled_signature(Script, environment, code, sig, names, op, version):
signature, = compiled.get_signatures()
assert signature.to_string() == sig
assert [n.string_name for n in signature.get_param_names()] == names
classmethod_code = '''
class X:
@classmethod
def x(cls, a, b):
pass
@staticmethod
def static(a, b):
pass
'''
partial_code = '''
import functools
def func(a, b, c):
pass
a = functools.partial(func)
b = functools.partial(func, 1)
c = functools.partial(func, 1, c=2)
d = functools.partial()
'''
@pytest.mark.parametrize(
'code, expected', [
('def f(a, * args, x): pass\n f(', 'f(a, *args, x)'),
('def f(a, *, x): pass\n f(', 'f(a, *, x)'),
('def f(*, x= 3,**kwargs): pass\n f(', 'f(*, x=3, **kwargs)'),
('def f(x,/,y,* ,z): pass\n f(', 'f(x, /, y, *, z)'),
('def f(a, /, *, x=3, **kwargs): pass\n f(', 'f(a, /, *, x=3, **kwargs)'),
(classmethod_code + 'X.x(', 'x(cls, a, b)'),
(classmethod_code + 'X().x(', 'x(cls, a, b)'),
(classmethod_code + 'X.static(', 'static(a, b)'),
(classmethod_code + 'X().static(', 'static(a, b)'),
(partial_code + 'a(', 'func(a, b, c)'),
(partial_code + 'b(', 'func(b, c)'),
(partial_code + 'c(', 'func(b)'),
(partial_code + 'd(', None),
]
)
def test_tree_signature(Script, environment, code, expected):
# Only test this in the latest version, because of /
if environment.version_info < (3, 8):
pytest.skip()
if expected is None:
assert not Script(code).call_signatures()
else:
sig, = Script(code).call_signatures()
assert expected == sig.to_string()
@pytest.mark.parametrize(
'combination, expected', [
# Functions
('full_redirect(simple)', 'b, *, c'),
('full_redirect(simple4)', 'b, x: int'),
('full_redirect(a)', 'b, *args'),
('full_redirect(kw)', 'b, *, c, **kwargs'),
('full_redirect(akw)', 'c, *args, **kwargs'),
# Non functions
('full_redirect(lambda x, y: ...)', 'y'),
('full_redirect()', '*args, **kwargs'),
('full_redirect(1)', '*args, **kwargs'),
# Classes / inheritance
('full_redirect(C)', 'z, *, c'),
('full_redirect(C())', 'y'),
('D', 'D(a, z, /)'),
('D()', 'D(x, y)'),
('D().foo', 'foo(a, *, bar, z, **kwargs)'),
# Merging
('two_redirects(simple, simple)', 'a, b, *, c'),
('two_redirects(simple2, simple2)', 'x'),
('two_redirects(akw, kw)', 'a, c, *args, **kwargs'),
('two_redirects(kw, akw)', 'a, b, *args, c, **kwargs'),
('combined_redirect(simple, simple2)', 'a, b, /, *, x'),
('combined_redirect(simple, simple3)', 'a, b, /, *, a, x: int'),
('combined_redirect(simple2, simple)', 'x, /, *, a, b, c'),
('combined_redirect(simple3, simple)', 'a, x: int, /, *, a, b, c'),
('combined_redirect(simple, kw)', 'a, b, /, *, a, b, c, **kwargs'),
('combined_redirect(kw, simple)', 'a, b, /, *, a, b, c'),
('combined_lot_of_args(kw, simple4)', '*, b'),
('combined_lot_of_args(simple4, kw)', '*, b, c, **kwargs'),
('combined_redirect(combined_redirect(simple2, simple4), combined_redirect(kw, simple5))',
'x, /, *, y'),
('combined_redirect(combined_redirect(simple4, simple2), combined_redirect(simple5, kw))',
'a, b, x: int, /, *, a, b, c, **kwargs'),
('combined_redirect(combined_redirect(a, kw), combined_redirect(kw, simple5))',
'a, b, /, *args, y'),
('no_redirect(kw)', '*args, **kwargs'),
('no_redirect(akw)', '*args, **kwargs'),
('no_redirect(simple)', '*args, **kwargs'),
]
)
def test_nested_signatures(Script, environment, combination, expected, skip_pre_python35):
code = dedent('''
def simple(a, b, *, c): ...
def simple2(x): ...
def simple3(a, x: int): ...
def simple4(a, b, x: int): ...
def simple5(y): ...
def a(a, b, *args): ...
def kw(a, b, *, c, **kwargs): ...
def akw(a, c, *args, **kwargs): ...
def no_redirect(func):
return lambda *args, **kwargs: func(1)
def full_redirect(func):
return lambda *args, **kwargs: func(1, *args, **kwargs)
def two_redirects(func1, func2):
return lambda *args, **kwargs: func1(*args, **kwargs) + func2(1, *args, **kwargs)
def combined_redirect(func1, func2):
return lambda *args, **kwargs: func1(*args) + func2(**kwargs)
def combined_lot_of_args(func1, func2):
return lambda *args, **kwargs: func1(1, 2, 3, 4, *args) + func2(a=3, x=1, y=1, **kwargs)
class C:
def __init__(self, a, z, *, c): ...
def __call__(self, x, y): ...
def foo(self, bar, z, **kwargs): ...
class D(C):
def __init__(self, *args):
super().__init__(*args)
def foo(self, a, **kwargs):
super().foo(**kwargs)
''')
code += 'z = ' + combination + '\nz('
sig, = Script(code).call_signatures()
computed = sig.to_string()
if not re.match(r'\w+\(', expected):
expected = '<lambda>(' + expected + ')'
assert expected == computed
def test_pow_signature(Script):
# See github #1357
sigs = Script('pow(').call_signatures()
strings = {sig.to_string() for sig in sigs}
assert strings == {'pow(x: float, y: float, z: float, /) -> float',
'pow(x: float, y: float, /) -> float',
'pow(x: int, y: int, z: int, /) -> Any',
'pow(x: int, y: int, /) -> Any'}
@pytest.mark.parametrize(
'code, signature', [
[dedent('''
import functools
def f(x):
pass
def x(f):
@functools.wraps(f)
def wrapper(*args):
# Have no arguments here, but because of wraps, the signature
# should still be f's.
return f(*args)
return wrapper
x(f)('''), 'f(x, /)'],
[dedent('''
import functools
def f(x):
pass
def x(f):
@functools.wraps(f)
def wrapper():
# Have no arguments here, but because of wraps, the signature
# should still be f's.
return 1
return wrapper
x(f)('''), 'f()'],
]
)
def test_wraps_signature(Script, code, signature, skip_pre_python35):
sigs = Script(code).call_signatures()
assert {sig.to_string() for sig in sigs} == {signature}
@pytest.mark.parametrize(
'start, start_params', [
['@dataclass\nclass X:', []],
['@dataclass(eq=True)\nclass X:', []],
[dedent('''
class Y():
y: int
@dataclass
class X(Y):'''), []],
[dedent('''
@dataclass
class Y():
y: int
z = 5
@dataclass
class X(Y):'''), ['y']],
]
)
def test_dataclass_signature(Script, skip_pre_python37, start, start_params):
code = dedent('''
name: str
foo = 3
price: float
quantity: int = 0.0
X(''')
code = 'from dataclasses import dataclass\n' + start + code
sig, = Script(code).call_signatures()
assert [p.name for p in sig.params] == start_params + ['name', 'price', 'quantity']
quantity, = sig.params[-1].infer()
assert quantity.name == 'int'
price, = sig.params[-2].infer()
assert price.name == 'float'