mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-06 22:14:27 +08:00
Compare commits
2279 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b5b2e791b | ||
|
|
b89f9445c2 | ||
|
|
ce6ddb91de | ||
|
|
fe60b5ca13 | ||
|
|
fa2d03a4fb | ||
|
|
1b16ca0e2e | ||
|
|
f9cec89038 | ||
|
|
bc4f6ed9dd | ||
|
|
fd435a7bbb | ||
|
|
ce0ed4b8ae | ||
|
|
42a759a7ae | ||
|
|
6dcae857a7 | ||
|
|
34792c0077 | ||
|
|
6df463b1e3 | ||
|
|
4740178bdf | ||
|
|
06d6776422 | ||
|
|
1095820006 | ||
|
|
47e60107b2 | ||
|
|
12a2d10595 | ||
|
|
ccdf7eddf4 | ||
|
|
83d4ec9e84 | ||
|
|
69750b9bf0 | ||
|
|
a03a093e2c | ||
|
|
6094e7b39a | ||
|
|
98d0a55a02 | ||
|
|
6eabde1519 | ||
|
|
a4f45993f8 | ||
|
|
49e35497ae | ||
|
|
bf310c780c | ||
|
|
e671a0cb6d | ||
|
|
a5a36a049c | ||
|
|
43ff2833f3 | ||
|
|
5f2f4af851 | ||
|
|
bf56103428 | ||
|
|
78e87d0ab8 | ||
|
|
04572422d4 | ||
|
|
cb55b45d47 | ||
|
|
e3fedb52f1 | ||
|
|
c1f4e7d874 | ||
|
|
4082728c32 | ||
|
|
66e2a0fce4 | ||
|
|
39fe9a1979 | ||
|
|
f18493b627 | ||
|
|
fa2abb5ff6 | ||
|
|
5b81abd537 | ||
|
|
01b2e8e6b8 | ||
|
|
ff439039da | ||
|
|
216f976fd5 | ||
|
|
e617c9d344 | ||
|
|
58ef6cd36b | ||
|
|
abf63d73d3 | ||
|
|
76c0c373da | ||
|
|
209e2713fd | ||
|
|
f12ed2088a | ||
|
|
94bf83c826 | ||
|
|
cce3ecb1e4 | ||
|
|
10aa21f970 | ||
|
|
425287055b | ||
|
|
a9e2cd5a74 | ||
|
|
2f7d0ec42c | ||
|
|
20be4f02c8 | ||
|
|
6364dd1511 | ||
|
|
19b8eaea59 | ||
|
|
b892c07841 | ||
|
|
cefc363f64 | ||
|
|
45c90efb5c | ||
|
|
0571e12617 | ||
|
|
86e0e16625 | ||
|
|
b3edda30c4 | ||
|
|
9d1587a41d | ||
|
|
e593396417 | ||
|
|
a9cb9fbb1f | ||
|
|
3f74981d5e | ||
|
|
38f853cf86 | ||
|
|
4b7e837f0f | ||
|
|
a2d9fbcd42 | ||
|
|
6315709fea | ||
|
|
48e5aa777b | ||
|
|
69be26b16e | ||
|
|
5e509814f7 | ||
|
|
07fbcd2262 | ||
|
|
1c87ae378d | ||
|
|
b1f95b4bf9 | ||
|
|
7d9205d4ae | ||
|
|
9b3cd15c5f | ||
|
|
1418aada91 | ||
|
|
f98a9f7999 | ||
|
|
35c2d660cb | ||
|
|
c09e21ae4b | ||
|
|
480c352d33 | ||
|
|
8f167be980 | ||
|
|
e86afc1705 | ||
|
|
7423c65eb5 | ||
|
|
b651c6541a | ||
|
|
403564315c | ||
|
|
5e6138d16f | ||
|
|
6ef18bea50 | ||
|
|
9505dabfef | ||
|
|
4783c065da | ||
|
|
bb303a75c0 | ||
|
|
1e633ab8ed | ||
|
|
89f525407a | ||
|
|
d7d42c8e39 | ||
|
|
abb2250bf5 | ||
|
|
ae2becb531 | ||
|
|
14069e81fd | ||
|
|
401e8d3100 | ||
|
|
e7c2c85b9f | ||
|
|
784e965d3a | ||
|
|
10c4dbf785 | ||
|
|
7281302281 | ||
|
|
27603f9780 | ||
|
|
d9a90d5d5e | ||
|
|
9957565b37 | ||
|
|
5bc174bf8d | ||
|
|
89f070ea98 | ||
|
|
04d24acb5a | ||
|
|
3b7106ae71 | ||
|
|
74116fe2ea | ||
|
|
1233caebdc | ||
|
|
d78567f853 | ||
|
|
1ece7698c2 | ||
|
|
7851dff915 | ||
|
|
e4987b3e7a | ||
|
|
d1851c369c | ||
|
|
d63fbd8624 | ||
|
|
b0f664ec94 | ||
|
|
9957374508 | ||
|
|
7f3a7db7e6 | ||
|
|
3ffe8475b8 | ||
|
|
396d7df314 | ||
|
|
0c618a4456 | ||
|
|
c4c36d8e2e | ||
|
|
829dda3ee9 | ||
|
|
a16f52b9fb | ||
|
|
a49c062b35 | ||
|
|
da15e916de | ||
|
|
480a464179 | ||
|
|
db0e90763b | ||
|
|
92af043906 | ||
|
|
806ad06d6a | ||
|
|
dac1fb0a06 | ||
|
|
ec08506704 | ||
|
|
7bcb420a0a | ||
|
|
546b970240 | ||
|
|
24a1bbb3ca | ||
|
|
a0de93a638 | ||
|
|
216ce8726c | ||
|
|
0c1ba1b305 | ||
|
|
5ab351dc8f | ||
|
|
f1366b8a74 | ||
|
|
7f67324210 | ||
|
|
a51f667be8 | ||
|
|
f7b445353f | ||
|
|
46154a3ee7 | ||
|
|
0790f376ca | ||
|
|
332631434c | ||
|
|
8ee0c8593e | ||
|
|
5a912de937 | ||
|
|
ef96c4c66b | ||
|
|
155a1dd3fc | ||
|
|
65601b6532 | ||
|
|
6e4dfda727 | ||
|
|
1fbe0d8d2e | ||
|
|
6e184bca97 | ||
|
|
188fdcd34f | ||
|
|
f4e537fd72 | ||
|
|
cfd8eb23b8 | ||
|
|
57c7d61989 | ||
|
|
db28eee760 | ||
|
|
0cd6a8f5cc | ||
|
|
17343bb57c | ||
|
|
182e1e864c | ||
|
|
782c561e86 | ||
|
|
9838040ca3 | ||
|
|
eea35ffc31 | ||
|
|
b639e7fd11 | ||
|
|
2c1e591718 | ||
|
|
49e4b1a0f8 | ||
|
|
ebfc330e86 | ||
|
|
e597dcc8fd | ||
|
|
07fc1ef837 | ||
|
|
a25e192ff9 | ||
|
|
e6a748b1a7 | ||
|
|
227cf00638 | ||
|
|
a9d32fbc99 | ||
|
|
b5e0c1e9c6 | ||
|
|
2aec4678da | ||
|
|
f9a35ae42a | ||
|
|
0538a3e224 | ||
|
|
64516f1b45 | ||
|
|
1dc83115be | ||
|
|
c651109b9a | ||
|
|
1df98c5bd6 | ||
|
|
aab9fd2fbe | ||
|
|
4e2ca9e5fd | ||
|
|
395f7fc59e | ||
|
|
4c557d4050 | ||
|
|
86eb48a89b | ||
|
|
3262ad4350 | ||
|
|
fb34df3987 | ||
|
|
23db298e2f | ||
|
|
9d5acf3c53 | ||
|
|
7e295d05a1 | ||
|
|
50b85153ce | ||
|
|
0e5869b52f | ||
|
|
d67dfba7f5 | ||
|
|
a3a9ae1a26 | ||
|
|
e41b966283 | ||
|
|
4188526e2d | ||
|
|
804b0f0d06 | ||
|
|
7b15f1736c | ||
|
|
4846848a1e | ||
|
|
344fef1e2f | ||
|
|
bc23458164 | ||
|
|
9a54e583e7 | ||
|
|
59ccd2da93 | ||
|
|
737c1e5792 | ||
|
|
f72adf0cbc | ||
|
|
5184d0cb9c | ||
|
|
2d0258db1a | ||
|
|
f5e6a25542 | ||
|
|
bc5a8ddf87 | ||
|
|
eabddb9698 | ||
|
|
6fcdc44f3e | ||
|
|
0d1a45ddc1 | ||
|
|
f9183bbf64 | ||
|
|
7ec8454fc1 | ||
|
|
a3410f124a | ||
|
|
3488f6b61d | ||
|
|
3dad9cac6b | ||
|
|
7aa13e35e9 | ||
|
|
cf1b54cfe5 | ||
|
|
8669405a1c | ||
|
|
54775acc7a | ||
|
|
be184241fd | ||
|
|
61ad05d511 | ||
|
|
1872ad311b | ||
|
|
364d33119c | ||
|
|
1702a6340e | ||
|
|
4ab35cac7b | ||
|
|
21f1df18b6 | ||
|
|
8ea4c0589c | ||
|
|
1d1c0ec3af | ||
|
|
7e637c5e5e | ||
|
|
4f11f20e1d | ||
|
|
674e0114a5 | ||
|
|
1f082b69d2 | ||
|
|
9de5ab2037 | ||
|
|
3415ccbb73 | ||
|
|
b165596a6e | ||
|
|
089a4713e3 | ||
|
|
365d725bc1 | ||
|
|
7586900fd9 | ||
|
|
c4de9ae2d3 | ||
|
|
3a0a484fcb | ||
|
|
df7dd026d2 | ||
|
|
a2108de2c0 | ||
|
|
6d0d75c7d9 | ||
|
|
d4f0424ddc | ||
|
|
cd6113c2c3 | ||
|
|
c9a21adc5f | ||
|
|
9adcf3d233 | ||
|
|
34cc8e9ad7 | ||
|
|
cf923ec6de | ||
|
|
105c097fea | ||
|
|
574b790296 | ||
|
|
3870253b56 | ||
|
|
21a380f7cb | ||
|
|
404661f361 | ||
|
|
1e58f9a15c | ||
|
|
24236be3ce | ||
|
|
8705149619 | ||
|
|
782dedd439 | ||
|
|
f9bbccbc13 | ||
|
|
cecdaa98ae | ||
|
|
9980f760b1 | ||
|
|
5946a5cd8c | ||
|
|
32687474db | ||
|
|
98a8b6c76c | ||
|
|
ca08365a81 | ||
|
|
8239328e42 | ||
|
|
b9131c6070 | ||
|
|
1c342d36e5 | ||
|
|
2d672d2f28 | ||
|
|
c62cbd6654 | ||
|
|
c36904d983 | ||
|
|
669b70b2cd | ||
|
|
7459d67fee | ||
|
|
741097827d | ||
|
|
4ceca54138 | ||
|
|
860d5e8889 | ||
|
|
64d131060c | ||
|
|
b7cdec427e | ||
|
|
df66b35444 | ||
|
|
cd9f2f31ea | ||
|
|
b54d7433c7 | ||
|
|
855fb5a936 | ||
|
|
8fdf16b316 | ||
|
|
fa6194c0a9 | ||
|
|
2d17b81313 | ||
|
|
cb1730f628 | ||
|
|
d848047012 | ||
|
|
716beae455 | ||
|
|
d16355fcf2 | ||
|
|
cd3d40a3b8 | ||
|
|
b3fc10a6e4 | ||
|
|
09dbbc6361 | ||
|
|
f5ad561c51 | ||
|
|
0db50b521d | ||
|
|
9942a3d44c | ||
|
|
47637c147c | ||
|
|
2fb072532a | ||
|
|
70aa7fc917 | ||
|
|
384b2ad014 | ||
|
|
f2975f9a05 | ||
|
|
41c146a6f3 | ||
|
|
be594f1498 | ||
|
|
99eba4e0eb | ||
|
|
43806f8668 | ||
|
|
d4aa583e16 | ||
|
|
381fbeda6a | ||
|
|
3104443212 | ||
|
|
16e2b86bcf | ||
|
|
0caee73975 | ||
|
|
7f25e28d89 | ||
|
|
ce8473ee63 | ||
|
|
7ccee7d8fc | ||
|
|
7cd89cff6e | ||
|
|
e1c0d2c501 | ||
|
|
be7a1346ec | ||
|
|
6dbc5e783e | ||
|
|
1115cbd94d | ||
|
|
bf4ec2282f | ||
|
|
e6e43413ff | ||
|
|
e9a0c01af8 | ||
|
|
d0270b5e59 | ||
|
|
b57654aed3 | ||
|
|
78ad06612e | ||
|
|
434866558a | ||
|
|
42963a0e03 | ||
|
|
c2d1da09cb | ||
|
|
f362932ec5 | ||
|
|
3b48c76e4a | ||
|
|
d56f607f35 | ||
|
|
39a2cd8aa2 | ||
|
|
14ca8e6499 | ||
|
|
2a227dcc7a | ||
|
|
12090ce74b | ||
|
|
25973554e2 | ||
|
|
138c22afe9 | ||
|
|
d19535340c | ||
|
|
5fcbed721d | ||
|
|
812776b9ce | ||
|
|
d606ea6759 | ||
|
|
c314e1c36e | ||
|
|
8c7a883abd | ||
|
|
55facaaf3d | ||
|
|
17ca3a620f | ||
|
|
9836a1b347 | ||
|
|
8c3fd99009 | ||
|
|
4d9cb083ac | ||
|
|
612fd23777 | ||
|
|
dca505c884 | ||
|
|
7fd5c8af8f | ||
|
|
97fb95ec0c | ||
|
|
e6d8a955d2 | ||
|
|
a3a147f028 | ||
|
|
c761dded35 | ||
|
|
92623232c3 | ||
|
|
9b58bf6199 | ||
|
|
9d5eb28523 | ||
|
|
857e0fc00e | ||
|
|
bf8b58aeeb | ||
|
|
f6803bce2c | ||
|
|
6bff30fbbb | ||
|
|
6d927d502e | ||
|
|
2e1284f044 | ||
|
|
11eb4f8fde | ||
|
|
c19c13e2c6 | ||
|
|
891383f8dc | ||
|
|
ce1ac38cde | ||
|
|
df951733cd | ||
|
|
912fe68069 | ||
|
|
be82d5ff36 | ||
|
|
784f9ff081 | ||
|
|
0f39135ae5 | ||
|
|
94d374c9ce | ||
|
|
f3152a8c2b | ||
|
|
f3eaa418bb | ||
|
|
f9176578ea | ||
|
|
17eeb73767 | ||
|
|
7756792bba | ||
|
|
ba4e3393d3 | ||
|
|
1a89fafce4 | ||
|
|
df307b8eda | ||
|
|
d96887b102 | ||
|
|
89ad9a500b | ||
|
|
086728365c | ||
|
|
f9e36943d4 | ||
|
|
b5c1c6d414 | ||
|
|
df76b2462e | ||
|
|
343a10d491 | ||
|
|
72c52f5f15 | ||
|
|
cfa01d3ac5 | ||
|
|
f8e7447d35 | ||
|
|
2ac806e39f | ||
|
|
7ebbf9da44 | ||
|
|
1c4a2edbdb | ||
|
|
1d3082249f | ||
|
|
09950233e7 | ||
|
|
d48575c8c5 | ||
|
|
f8a0cf76c8 | ||
|
|
851e0d59f0 | ||
|
|
10b2de2c3f | ||
|
|
3718d62e24 | ||
|
|
a793dd7c91 | ||
|
|
0850b86456 | ||
|
|
f07dee3564 | ||
|
|
f871f5e726 | ||
|
|
803c3cb271 | ||
|
|
7ff76bb7d0 | ||
|
|
e7feeef64e | ||
|
|
8aaa8e0044 | ||
|
|
cbfbe7c08d | ||
|
|
81926a785c | ||
|
|
9ccb596f93 | ||
|
|
25db8de0da | ||
|
|
24dffe4226 | ||
|
|
c3fc129695 | ||
|
|
02c3d651bd | ||
|
|
bdd4deedc1 | ||
|
|
9d55194b92 | ||
|
|
102f83ea85 | ||
|
|
22902f6dba | ||
|
|
5a3565785c | ||
|
|
0f2a7215bb | ||
|
|
61e9371849 | ||
|
|
dde40b3a71 | ||
|
|
ebb2786748 | ||
|
|
28f256d2a6 | ||
|
|
883f5a3824 | ||
|
|
ac33d5dea3 | ||
|
|
604029568c | ||
|
|
eac5ac8426 | ||
|
|
7e9ad9e733 | ||
|
|
e2090772f3 | ||
|
|
525b88e9f1 | ||
|
|
3c90a84f68 | ||
|
|
ea33db388b | ||
|
|
f68d65ed59 | ||
|
|
3c7621049c | ||
|
|
dd60a8a4c9 | ||
|
|
5bd6a9c164 | ||
|
|
c743e5d9f3 | ||
|
|
5ca69458d4 | ||
|
|
bb9731b561 | ||
|
|
a2f4d1bbe7 | ||
|
|
88c13639bc | ||
|
|
28c1ba6c1c | ||
|
|
a2764283ba | ||
|
|
0ffd566957 | ||
|
|
5b54ac835d | ||
|
|
5f6a25fb58 | ||
|
|
d6d9286242 | ||
|
|
4c964ae655 | ||
|
|
8000d425ec | ||
|
|
c7cd84b1a4 | ||
|
|
6a89599fa5 | ||
|
|
5f40fa9bc6 | ||
|
|
24cde8e974 | ||
|
|
dea80b20e9 | ||
|
|
197d64d9a8 | ||
|
|
a2bbbfe2d5 | ||
|
|
2e9fac0b71 | ||
|
|
83e0e3bd8d | ||
|
|
2f651966e7 | ||
|
|
ffbaa4afea | ||
|
|
e11db6e8e4 | ||
|
|
eea6c7f41b | ||
|
|
01f53236a4 | ||
|
|
c39326616c | ||
|
|
b1aef26464 | ||
|
|
97117bfaf2 | ||
|
|
f12262881d | ||
|
|
95b0cdcb5e | ||
|
|
0f8e7b453e | ||
|
|
516b58b287 | ||
|
|
e53acb4150 | ||
|
|
7de475318a | ||
|
|
6dda514ec6 | ||
|
|
72a3a33e33 | ||
|
|
d26926a582 | ||
|
|
0731206b9d | ||
|
|
c2451ddd03 | ||
|
|
88adf84fc2 | ||
|
|
94c97765c8 | ||
|
|
1c56d15836 | ||
|
|
7985ef37d4 | ||
|
|
8f4f6d6ac3 | ||
|
|
4a065642f2 | ||
|
|
3276db0bdc | ||
|
|
88757f00e7 | ||
|
|
6d79ac9fde | ||
|
|
25af28946e | ||
|
|
950f5c186c | ||
|
|
8f96cbdabf | ||
|
|
17b3611c53 | ||
|
|
9240a20d13 | ||
|
|
6220b20659 | ||
|
|
2feb0acd7d | ||
|
|
8efd111426 | ||
|
|
616e9bf275 | ||
|
|
78f0f5855f | ||
|
|
0f11f65682 | ||
|
|
43363936cd | ||
|
|
0f25eb9c9a | ||
|
|
8ceb76b3f6 | ||
|
|
25e6db5e82 | ||
|
|
7c7864d500 | ||
|
|
a9761079e6 | ||
|
|
20fad922bc | ||
|
|
3cef022a15 | ||
|
|
52b0450953 | ||
|
|
7b725553ff | ||
|
|
e811651b00 | ||
|
|
fbba7714e4 | ||
|
|
bdb36ab626 | ||
|
|
1a466d9641 | ||
|
|
94f99aaeb3 | ||
|
|
851980e2a9 | ||
|
|
88c766afb0 | ||
|
|
13254a30df | ||
|
|
50af2650bb | ||
|
|
788562715e | ||
|
|
0888dd468f | ||
|
|
fd9a493868 | ||
|
|
661fdb2b26 | ||
|
|
23f267bb86 | ||
|
|
4af138f4fb | ||
|
|
10bc578bfe | ||
|
|
2406e58386 | ||
|
|
5cd212c51c | ||
|
|
fd6540a9e5 | ||
|
|
521e240c5f | ||
|
|
b4fa42a282 | ||
|
|
fb72e1b448 | ||
|
|
da9d312185 | ||
|
|
96c969687a | ||
|
|
f83844408f | ||
|
|
b247423184 | ||
|
|
9c77113e21 | ||
|
|
91857c2c0a | ||
|
|
886dadaaff | ||
|
|
d574162da3 | ||
|
|
0aa1ef6639 | ||
|
|
33c61b8708 | ||
|
|
bedf3bff0e | ||
|
|
d838eaecd2 | ||
|
|
cf3d83ee4f | ||
|
|
7247c32990 | ||
|
|
75ae73ee97 | ||
|
|
753440682e | ||
|
|
53f39c88e4 | ||
|
|
d3e3021a3d | ||
|
|
e46e1269a2 | ||
|
|
a5f7412296 | ||
|
|
b198434694 | ||
|
|
58998748e3 | ||
|
|
6bddca011c | ||
|
|
f147cb1133 | ||
|
|
d06efd0dd1 | ||
|
|
96132587b7 | ||
|
|
5d273f4630 | ||
|
|
95cec459a8 | ||
|
|
3b4fa2aa9c | ||
|
|
54e29eede1 | ||
|
|
c159b9debd | ||
|
|
eecdf31601 | ||
|
|
7f2f025866 | ||
|
|
ed3564831c | ||
|
|
8c1e518ab7 | ||
|
|
c7a862ec19 | ||
|
|
6e3bd38600 | ||
|
|
e6bdaea73e | ||
|
|
ebb9df07f3 | ||
|
|
8df917f1df | ||
|
|
30f72c48c4 | ||
|
|
e03924895b | ||
|
|
af055ec69c | ||
|
|
9d8ad4cc04 | ||
|
|
a6ef8efb72 | ||
|
|
ccc1262a3e | ||
|
|
656324f686 | ||
|
|
bd1ef659e8 | ||
|
|
afc61c2576 | ||
|
|
4d5373d626 | ||
|
|
609737322d | ||
|
|
fa63c92cf7 | ||
|
|
e5fabb4c5f | ||
|
|
bb91b96286 | ||
|
|
fd23946de3 | ||
|
|
a2b8c44e8f | ||
|
|
0a1de619b4 | ||
|
|
31d5c92dae | ||
|
|
d1873f8e1e | ||
|
|
58ba47841c | ||
|
|
0f2d6ac27a | ||
|
|
76ce422590 | ||
|
|
1f773d8e65 | ||
|
|
4451d2fec7 | ||
|
|
0ef8053919 | ||
|
|
140a45081f | ||
|
|
ebdaf0177d | ||
|
|
f2f11bc574 | ||
|
|
5f2a402b19 | ||
|
|
5f226bc82e | ||
|
|
a892887b04 | ||
|
|
d1ac00f64f | ||
|
|
03e1770a24 | ||
|
|
42adadd0cb | ||
|
|
3708ab3514 | ||
|
|
c9334d140b | ||
|
|
35e992c37c | ||
|
|
a92c28840b | ||
|
|
c96994dd8d | ||
|
|
bb6f0d5e91 | ||
|
|
bf9a3a4ca8 | ||
|
|
eef47e951e | ||
|
|
17892556f8 | ||
|
|
b65c1c26aa | ||
|
|
bc3e1ada03 | ||
|
|
1f82efa86d | ||
|
|
94c00229f2 | ||
|
|
5614ef2fed | ||
|
|
8ff5ca81d2 | ||
|
|
ff60c0af87 | ||
|
|
89398e5c87 | ||
|
|
f8d9f498d0 | ||
|
|
30738a092b | ||
|
|
f527138e6c | ||
|
|
24a4c3ceba | ||
|
|
48e25c1b9b | ||
|
|
f1a9e681ad | ||
|
|
f4cbf61604 | ||
|
|
5e990d9206 | ||
|
|
80db4dcf56 | ||
|
|
e557129121 | ||
|
|
c15e0ef9b8 | ||
|
|
e455709a31 | ||
|
|
c03ae0315e | ||
|
|
bc53dabce3 | ||
|
|
969a8f1fd9 | ||
|
|
0a7820f6de | ||
|
|
da935baa99 | ||
|
|
cc8483a07a | ||
|
|
48c4262f66 | ||
|
|
d069a4e482 | ||
|
|
2061919b64 | ||
|
|
a7110a4e08 | ||
|
|
b7be5a4fe2 | ||
|
|
876109267a | ||
|
|
1c0f9e1f30 | ||
|
|
6efafb348e | ||
|
|
36b4b797c1 | ||
|
|
ce1093406a | ||
|
|
dcffe8e60b | ||
|
|
0516637e8d | ||
|
|
3bc66c2f00 | ||
|
|
742c4370b5 | ||
|
|
292ad9d9ac | ||
|
|
3457bd77eb | ||
|
|
1874e9be81 | ||
|
|
3f86d803d2 | ||
|
|
26bf2ceb15 | ||
|
|
bfa15c61f1 | ||
|
|
61619c4db1 | ||
|
|
50be49544d | ||
|
|
b1d3c7ef52 | ||
|
|
7dff25f7c9 | ||
|
|
ab4fe548f2 | ||
|
|
c4cf0d78e1 | ||
|
|
d1f7400829 | ||
|
|
ee8cdb667d | ||
|
|
24114ba631 | ||
|
|
9d171609da | ||
|
|
518d2449a7 | ||
|
|
a906a76ccd | ||
|
|
af20905f7d | ||
|
|
d536a20019 | ||
|
|
bcefb04d54 | ||
|
|
dac2655915 | ||
|
|
14180ad185 | ||
|
|
dbf88f2750 | ||
|
|
0a3ff6bd70 | ||
|
|
d6f6c29a63 | ||
|
|
c7d1b8de9e | ||
|
|
b4628abc60 | ||
|
|
aef675c79b | ||
|
|
41602124c7 | ||
|
|
5c246649e2 | ||
|
|
6c9f187884 | ||
|
|
871575b06c | ||
|
|
fd4ba3f47e | ||
|
|
204b072388 | ||
|
|
e7ab318107 | ||
|
|
52d72157c0 | ||
|
|
ac47866c4c | ||
|
|
c47021150e | ||
|
|
a39b2e95c1 | ||
|
|
d42d3f45f0 | ||
|
|
b4494e588f | ||
|
|
0697a39145 | ||
|
|
e43b0cec4a | ||
|
|
ab4f282b03 | ||
|
|
4bc9075d0b | ||
|
|
faddf412f9 | ||
|
|
e22a44d79e | ||
|
|
4cc03d2239 | ||
|
|
1e929b0aa0 | ||
|
|
13b393a5e3 | ||
|
|
6166e7961e | ||
|
|
370e539a7e | ||
|
|
fd1f9f22e9 | ||
|
|
bcb7cc864c | ||
|
|
de2f753546 | ||
|
|
2cf06bcf48 | ||
|
|
cf954bf006 | ||
|
|
9d2083fa08 | ||
|
|
6a9745b42b | ||
|
|
87161df2f0 | ||
|
|
7ef07b576f | ||
|
|
6e63799a7d | ||
|
|
841fe75326 | ||
|
|
f6465c5202 | ||
|
|
14ac0512a9 | ||
|
|
f2722952e7 | ||
|
|
b7919bd3e6 | ||
|
|
7a55484b79 | ||
|
|
670d6e8639 | ||
|
|
6313934d94 | ||
|
|
40fced2450 | ||
|
|
692bf5cfb7 | ||
|
|
66e28eb52e | ||
|
|
3388a9659b | ||
|
|
eb88c483fb | ||
|
|
2c62166ff6 | ||
|
|
3101e43aa6 | ||
|
|
a49c757b8a | ||
|
|
3ad3dc08b8 | ||
|
|
eee919174d | ||
|
|
e802f5aabd | ||
|
|
e3c4b5b77e | ||
|
|
4c7179bc87 | ||
|
|
f4b1fc479d | ||
|
|
e1425de8a4 | ||
|
|
8ff2ea4b38 | ||
|
|
e7a77e438d | ||
|
|
a05628443e | ||
|
|
d09882f970 | ||
|
|
e5ec2a3adf | ||
|
|
d02af44331 | ||
|
|
251ff447bc | ||
|
|
4a1d9a9116 | ||
|
|
ceccbf3678 | ||
|
|
e930f47861 | ||
|
|
d630ed55f3 | ||
|
|
bec87f7ff8 | ||
|
|
045b8a35a2 | ||
|
|
8eb980db73 | ||
|
|
18f84d3af7 | ||
|
|
2ccd015b5a | ||
|
|
1a62674254 | ||
|
|
7645762a25 | ||
|
|
2e036bffb5 | ||
|
|
feefd47ddd | ||
|
|
f42ab8872d | ||
|
|
7c3dbef9c5 | ||
|
|
8cccdde28d | ||
|
|
5cd4a52bcd | ||
|
|
517fa27dc6 | ||
|
|
329329c195 | ||
|
|
8bde54a072 | ||
|
|
235b887b75 | ||
|
|
da2a55c73f | ||
|
|
0435e0e85c | ||
|
|
9c0efd5a67 | ||
|
|
066b8b7165 | ||
|
|
7683c05de3 | ||
|
|
eaa49aa26b | ||
|
|
3f6a718c34 | ||
|
|
6cfcba0d97 | ||
|
|
4d3f314baa | ||
|
|
e3e6727a2d | ||
|
|
b985a380bc | ||
|
|
11b61596e0 | ||
|
|
290e2151df | ||
|
|
cc8a3f192d | ||
|
|
0c56aa4d4b | ||
|
|
6a75a0c590 | ||
|
|
8440e1719f | ||
|
|
ddcd48edd8 | ||
|
|
7e98c9449b | ||
|
|
dbdd556a2b | ||
|
|
9bc01da9c4 | ||
|
|
5c68304bec | ||
|
|
59e7bacfae | ||
|
|
318fab8682 | ||
|
|
bff6e95e28 | ||
|
|
8cc836e816 | ||
|
|
58f54d8391 | ||
|
|
9d7858eb3a | ||
|
|
6df755e8b6 | ||
|
|
2a86f7d82f | ||
|
|
7287d67e7a | ||
|
|
44ba40958e | ||
|
|
d9960081f5 | ||
|
|
c12cbf2106 | ||
|
|
6e10313cca | ||
|
|
28027a3fee | ||
|
|
a246624f70 | ||
|
|
621bd7d1db | ||
|
|
445dc2411e | ||
|
|
ed36efabeb | ||
|
|
62a77dcd16 | ||
|
|
c61ca0d27b | ||
|
|
26f0fa9eb0 | ||
|
|
4cd2b9a355 | ||
|
|
eb103d293c | ||
|
|
4931180df1 | ||
|
|
2937c95e9e | ||
|
|
f53b08516d | ||
|
|
c6ca889927 | ||
|
|
a6dfc130c9 | ||
|
|
3645ea0557 | ||
|
|
df7080c1da | ||
|
|
a098bf28af | ||
|
|
8bcd1f5fd9 | ||
|
|
e1564da23d | ||
|
|
9c1063c35a | ||
|
|
c3503672d5 | ||
|
|
c56dae4835 | ||
|
|
591e3c4565 | ||
|
|
4fb595f422 | ||
|
|
11a12d6ca8 | ||
|
|
bd2ed8dbbd | ||
|
|
a17d4d9e16 | ||
|
|
700dd9380a | ||
|
|
4f6116ac6e | ||
|
|
cc34c7d4f3 | ||
|
|
796a2b4df5 | ||
|
|
f3919823fb | ||
|
|
46f8e53e71 | ||
|
|
8dc7f2d899 | ||
|
|
c79269b3ee | ||
|
|
1e27491545 | ||
|
|
f31c90926e | ||
|
|
8459b02a98 | ||
|
|
ba6154c314 | ||
|
|
095f1295af | ||
|
|
4f56ec5daf | ||
|
|
3ba68b5bc6 | ||
|
|
cac73f2d44 | ||
|
|
ba7776c0d9 | ||
|
|
072d506302 | ||
|
|
76a4820926 | ||
|
|
10c5990614 | ||
|
|
a0536bd854 | ||
|
|
800ab65701 | ||
|
|
fdb5071bec | ||
|
|
a17b56f260 | ||
|
|
9b9cacfbf9 | ||
|
|
d8deceb4b1 | ||
|
|
9c4cd40b7e | ||
|
|
4243d01560 | ||
|
|
5da9f9facd | ||
|
|
ea0972d7ac | ||
|
|
bf446f2729 | ||
|
|
1cdeee6519 | ||
|
|
cc1664c69a | ||
|
|
a7415be0ea | ||
|
|
74fc29be9a | ||
|
|
aca2a5a409 | ||
|
|
088fca2f8e | ||
|
|
1813105b69 | ||
|
|
e30385465c | ||
|
|
47d3aa73dc | ||
|
|
441ede2c7f | ||
|
|
dfc6ea8ce2 | ||
|
|
673ea0c5a5 | ||
|
|
0e707d3824 | ||
|
|
92a2e17a9e | ||
|
|
3b6bbab556 | ||
|
|
2d31e2e760 | ||
|
|
bac91652ea | ||
|
|
67b720d939 | ||
|
|
ff96b052d0 | ||
|
|
9824929ad1 | ||
|
|
a36d609756 | ||
|
|
04a738c014 | ||
|
|
0a53ce5136 | ||
|
|
bb3a81c578 | ||
|
|
54bd0b437f | ||
|
|
9dc18054ee | ||
|
|
cab7c6fdc7 | ||
|
|
1cc8f96f26 | ||
|
|
47e2cf95d2 | ||
|
|
cf1f66600c | ||
|
|
8770e12d16 | ||
|
|
8e2bfdc07e | ||
|
|
ce748e6dc7 | ||
|
|
4837822e32 | ||
|
|
3ae0bb9805 | ||
|
|
829ee0e6b0 | ||
|
|
ba6cd1e2d4 | ||
|
|
87a0566637 | ||
|
|
57e18da7ae | ||
|
|
8cdd9d3de5 | ||
|
|
66ad620692 | ||
|
|
818577f423 | ||
|
|
cea7a12908 | ||
|
|
50c5eb5786 | ||
|
|
8914bbbcc3 | ||
|
|
dfd7910dd3 | ||
|
|
1da0a7bd58 | ||
|
|
e4cf9293c2 | ||
|
|
c8b3443d5f | ||
|
|
469ddc281d | ||
|
|
cf26ede702 | ||
|
|
5853c67906 | ||
|
|
83ce8b1162 | ||
|
|
b7a8929905 | ||
|
|
ca13c44788 | ||
|
|
94a97ff8e8 | ||
|
|
46ac4371df | ||
|
|
9fa4811425 | ||
|
|
7e769b87f3 | ||
|
|
c7296ade68 | ||
|
|
eff670679c | ||
|
|
3ec73f1da3 | ||
|
|
cc136a2879 | ||
|
|
73161fe72e | ||
|
|
35fb8a942c | ||
|
|
e86487cb96 | ||
|
|
b4163a3912 | ||
|
|
dc3d6a3975 | ||
|
|
0931c5492d | ||
|
|
7715655c96 | ||
|
|
4c22f4dbb1 | ||
|
|
31936776a5 | ||
|
|
8611fcf8ea | ||
|
|
ff0e3ec8fb | ||
|
|
a8782d0070 | ||
|
|
70bf3d9586 | ||
|
|
8c737ba17e | ||
|
|
5a54d94aa5 | ||
|
|
02320f832d | ||
|
|
148fffae28 | ||
|
|
c45c8ec8ef | ||
|
|
dd89325441 | ||
|
|
82ed28955d | ||
|
|
f3c8bc10f5 | ||
|
|
9fb94bb621 | ||
|
|
3e478cc6bb | ||
|
|
a4a0d482a2 | ||
|
|
3b2dddd1d3 | ||
|
|
110d89724e | ||
|
|
7a988d9d8b | ||
|
|
6daa03e98d | ||
|
|
9578e4252b | ||
|
|
a21f443756 | ||
|
|
1d17033717 | ||
|
|
eca8278eef | ||
|
|
d9383f1927 | ||
|
|
1087b62e95 | ||
|
|
f2a64e24c8 | ||
|
|
fcf8506531 | ||
|
|
22c3beffd0 | ||
|
|
0202d4ed0a | ||
|
|
63a9418bd5 | ||
|
|
fc785ce6ea | ||
|
|
4161bfc7f2 | ||
|
|
290d1c151a | ||
|
|
fcede44c2a | ||
|
|
536fd8c7c0 | ||
|
|
341d79681a | ||
|
|
66a36c3b94 | ||
|
|
fcecac20ec | ||
|
|
9e818dc377 | ||
|
|
e5496381f3 | ||
|
|
5fc308f1f8 | ||
|
|
694b05bb8c | ||
|
|
bd861e40a8 | ||
|
|
e1d787821b | ||
|
|
adff6d34a4 | ||
|
|
d7d9c9642a | ||
|
|
4bbaec68e8 | ||
|
|
dbb61357c3 | ||
|
|
f90aeceb27 | ||
|
|
7f8ba17990 | ||
|
|
5bf6e7048b | ||
|
|
ebe9921208 | ||
|
|
f03c70e577 | ||
|
|
2cc898ba35 | ||
|
|
38460ce9d7 | ||
|
|
2b5af19989 | ||
|
|
bcf726054e | ||
|
|
1514695fc1 | ||
|
|
f32b0aebeb | ||
|
|
6c7b8f669f | ||
|
|
87d5334b9e | ||
|
|
cefc4a46a3 | ||
|
|
39605bfa08 | ||
|
|
1f4be4bc51 | ||
|
|
afbd8cad89 | ||
|
|
0194efcd6b | ||
|
|
96156dd5df | ||
|
|
095a9c530a | ||
|
|
45edfbdeeb | ||
|
|
540a57766d | ||
|
|
e56d4fde98 | ||
|
|
51e2e90dce | ||
|
|
902b355aea | ||
|
|
542a2a339e | ||
|
|
41a6591d88 | ||
|
|
f91f655d55 | ||
|
|
49eb2c0a12 | ||
|
|
ec2391c74f | ||
|
|
0ce414eb94 | ||
|
|
38eb2c9ba3 | ||
|
|
9d35adda02 | ||
|
|
6e2a76feb9 | ||
|
|
35442eff81 | ||
|
|
8fc84a2aaa | ||
|
|
7bdedb40e3 | ||
|
|
3219f14c63 | ||
|
|
7639bc2da9 | ||
|
|
5bc6ce231b | ||
|
|
a6bf49783f | ||
|
|
621e280451 | ||
|
|
6b9add4264 | ||
|
|
92c59180fd | ||
|
|
923fcf95d9 | ||
|
|
902f0754e0 | ||
|
|
12b07a435d | ||
|
|
b9f8a7f52e | ||
|
|
769b3556d2 | ||
|
|
5e3e268cc6 | ||
|
|
e656a5f18f | ||
|
|
536a77551b | ||
|
|
a2cebc4b92 | ||
|
|
3065609162 | ||
|
|
8e33fd1931 | ||
|
|
ed3fdf8876 | ||
|
|
46982ce42b | ||
|
|
28ecc2709a | ||
|
|
33224ae7e1 | ||
|
|
d9260bf78b | ||
|
|
a51dc54759 | ||
|
|
5acbb06315 | ||
|
|
7319f8bf2c | ||
|
|
d9ddaa31ae | ||
|
|
5874b0bd69 | ||
|
|
9eef771ec5 | ||
|
|
9e6c53151b | ||
|
|
84d10657a3 | ||
|
|
5c4b3da45d | ||
|
|
ad92882c48 | ||
|
|
8213d183fb | ||
|
|
4fca7bd22d | ||
|
|
c112858a1c | ||
|
|
deaa7265dd | ||
|
|
72fc85f4c3 | ||
|
|
df697cfb03 | ||
|
|
fd054d1add | ||
|
|
95763f0bb0 | ||
|
|
aab0002950 | ||
|
|
ddbb87fd1d | ||
|
|
bc99fbdfea | ||
|
|
48ac0c9421 | ||
|
|
37a9d1536c | ||
|
|
3dbe5c10ae | ||
|
|
ab8f0ba834 | ||
|
|
4bd7c2e627 | ||
|
|
1f73c65dcd | ||
|
|
bd5909e7b2 | ||
|
|
6f70e759a4 | ||
|
|
0474371f23 | ||
|
|
654475b7d6 | ||
|
|
fbeff00761 | ||
|
|
c582545628 | ||
|
|
759808e8bb | ||
|
|
36b800f8d3 | ||
|
|
7e64bfa075 | ||
|
|
54f4bd0bad | ||
|
|
cf65ecdb96 | ||
|
|
700bd12122 | ||
|
|
4ba3dc69b3 | ||
|
|
b8a1f6da55 | ||
|
|
19aa50bb7f | ||
|
|
8aee1e6213 | ||
|
|
f46f00bc71 | ||
|
|
fea80c7fc8 | ||
|
|
87852c1295 | ||
|
|
3d784c748e | ||
|
|
e5d1091e80 | ||
|
|
7254bec92c | ||
|
|
74de9e7d53 | ||
|
|
f54291a30b | ||
|
|
4d3f6fa790 | ||
|
|
b8dfbc5d18 | ||
|
|
f43d144e23 | ||
|
|
76e0e6a8c5 | ||
|
|
7b6405f76c | ||
|
|
8a26a23884 | ||
|
|
6ffeea7eea | ||
|
|
582df2f76d | ||
|
|
5c79472024 | ||
|
|
378712dbc1 | ||
|
|
b13c4c446f | ||
|
|
e81c241905 | ||
|
|
c77f33b73b | ||
|
|
b895924311 | ||
|
|
86071dda54 | ||
|
|
1ba83414a5 | ||
|
|
59c5b51c0d | ||
|
|
c2fd7b3104 | ||
|
|
4bc4f167e9 | ||
|
|
3c68d3d341 | ||
|
|
8478ad7ffb | ||
|
|
98b592cb68 | ||
|
|
c38e4fce70 | ||
|
|
6e5e706288 | ||
|
|
0e92be66db | ||
|
|
03b4177d3d | ||
|
|
761f0828c7 | ||
|
|
facd21afc6 | ||
|
|
e1d840c89b | ||
|
|
15c13c1386 | ||
|
|
6d632a01eb | ||
|
|
4b15c8459a | ||
|
|
00b220516d | ||
|
|
364a527fd9 | ||
|
|
2039ab9a3c | ||
|
|
d48816603e | ||
|
|
f61d041830 | ||
|
|
f7fae4dde7 | ||
|
|
893b695a61 | ||
|
|
2653752f9c | ||
|
|
d73f32745d | ||
|
|
1fa678e3fe | ||
|
|
a84087682d | ||
|
|
48ffc5473a | ||
|
|
0b56bf8f08 | ||
|
|
85278242c3 | ||
|
|
6baa3ae8e1 | ||
|
|
0bbc8d6e9a | ||
|
|
8f306953da | ||
|
|
88ebb3e140 | ||
|
|
954fd56fcc | ||
|
|
e8afb46cde | ||
|
|
a6fcf779d4 | ||
|
|
527ef6fcdd | ||
|
|
a0f95fc89f | ||
|
|
96d650cab3 | ||
|
|
659aaf6861 | ||
|
|
d68545d8de | ||
|
|
f5ae7148dd | ||
|
|
e86a2ec566 | ||
|
|
e179b3e526 | ||
|
|
66022edf14 | ||
|
|
ae79919eb4 | ||
|
|
fbe58306c3 | ||
|
|
9c19f72af3 | ||
|
|
a9f1d3d9bb | ||
|
|
1db3e9a65d | ||
|
|
599eded3d1 | ||
|
|
4e68287bba | ||
|
|
008e9860a8 | ||
|
|
8cd5932fed | ||
|
|
67c007338a | ||
|
|
aea2ddcbd8 | ||
|
|
4d332c32c0 | ||
|
|
02046d5333 | ||
|
|
2faa8ade8b | ||
|
|
f9292ca8fa | ||
|
|
40b01bfd2c | ||
|
|
46e9b9e7cf | ||
|
|
96848dd627 | ||
|
|
e7d9a59da2 | ||
|
|
dd400f115a | ||
|
|
34f131e9b3 | ||
|
|
47d6ae3da1 | ||
|
|
79f9d78c83 | ||
|
|
06d2119f51 | ||
|
|
b27f47683c | ||
|
|
0be9ab0caf | ||
|
|
c8564a68df | ||
|
|
75262d294f | ||
|
|
ac4dd06d11 | ||
|
|
d4f3963cd0 | ||
|
|
a3659e2750 | ||
|
|
3a74d65404 | ||
|
|
acda3527cb | ||
|
|
03f6d0edf8 | ||
|
|
c79faa6b10 | ||
|
|
4b10644100 | ||
|
|
274f8dbb02 | ||
|
|
efa51a1d70 | ||
|
|
0a420339e8 | ||
|
|
fe5523268e | ||
|
|
b16c987a72 | ||
|
|
35efdd84d2 | ||
|
|
1495a0ec4c | ||
|
|
33586deef1 | ||
|
|
7bdd71f9a7 | ||
|
|
a67861a320 | ||
|
|
fe8a605d4a | ||
|
|
7ad7d22fb0 | ||
|
|
bdb01c7546 | ||
|
|
73003a995b | ||
|
|
06890203dd | ||
|
|
e97bb1d2e5 | ||
|
|
4fd1149be2 | ||
|
|
51475a5b39 | ||
|
|
a0cadd9375 | ||
|
|
b4dc95553f | ||
|
|
edb17b8e7c | ||
|
|
59f26ad6ab | ||
|
|
286d2c9b1a | ||
|
|
04bc9eb62c | ||
|
|
9c950321df | ||
|
|
4572503c9f | ||
|
|
7d28f4ce5b | ||
|
|
2a27ec37ae | ||
|
|
066b189bfa | ||
|
|
18ecb5a746 | ||
|
|
305bfd3a3c | ||
|
|
8311328a8e | ||
|
|
24b392b915 | ||
|
|
356c25a399 | ||
|
|
5329f95096 | ||
|
|
eb5586d7e0 | ||
|
|
b7febc1960 | ||
|
|
d31ca7e9f0 | ||
|
|
0f13e02fc2 | ||
|
|
2a86d810cd | ||
|
|
473dbb0f69 | ||
|
|
51912db46a | ||
|
|
41dc514546 | ||
|
|
e3d2bce7ff | ||
|
|
9b21c02819 | ||
|
|
c94bce315a | ||
|
|
8beea77bc8 | ||
|
|
9290b7291b | ||
|
|
4969b52ddf | ||
|
|
06a6cea02d | ||
|
|
9469533b9f | ||
|
|
98d0fc632e | ||
|
|
622db8d2d7 | ||
|
|
0619d58cd3 | ||
|
|
b1d2f2462b | ||
|
|
bccc85f453 | ||
|
|
4db6793719 | ||
|
|
ec6fa0c97c | ||
|
|
8b1f35a8b1 | ||
|
|
e7020bea3d | ||
|
|
bb3eb23864 | ||
|
|
88cf198552 | ||
|
|
e0f26dd7a1 | ||
|
|
d913d7d701 | ||
|
|
dd6befdc52 | ||
|
|
c1d8454f0c | ||
|
|
c4b0b45a1d | ||
|
|
eba088b049 | ||
|
|
ba67d384c1 | ||
|
|
ba9c318d22 | ||
|
|
ce3ec4eecb | ||
|
|
e148d5120f | ||
|
|
3828532065 | ||
|
|
6d361e03ac | ||
|
|
250ac77f4a | ||
|
|
ddb2ccb657 | ||
|
|
bd24ee2ab3 | ||
|
|
b13a9f7d5b | ||
|
|
fcec30dff6 | ||
|
|
0992dc7ae9 | ||
|
|
60a73f6bac | ||
|
|
a9d8f389a9 | ||
|
|
9a3f41e63b | ||
|
|
3fcecb3d6d | ||
|
|
ead0964282 | ||
|
|
0cbd1e6cff | ||
|
|
b38da47981 | ||
|
|
c393a406ee | ||
|
|
7573e2033a | ||
|
|
ecc574025c | ||
|
|
51ac055a38 | ||
|
|
c9e4cdaba1 | ||
|
|
aceef78a21 | ||
|
|
86f4f7be45 | ||
|
|
041fd992b3 | ||
|
|
05ce1c8237 | ||
|
|
3e684519e6 | ||
|
|
9f3a2f93c4 | ||
|
|
193ba47f50 | ||
|
|
05fe29a156 | ||
|
|
bd754718e1 | ||
|
|
df014dc527 | ||
|
|
6d5e9f4b0f | ||
|
|
faf6752ff8 | ||
|
|
ee6331747f | ||
|
|
eee6810576 | ||
|
|
f87f8c028b | ||
|
|
b97237f264 | ||
|
|
4e260cdadb | ||
|
|
337c03e5be | ||
|
|
bf4d42798b | ||
|
|
2fb04db0ab | ||
|
|
592f3771fc | ||
|
|
925dd38c18 | ||
|
|
6142d18206 | ||
|
|
9d34df2fed | ||
|
|
02c96b37db | ||
|
|
55c08e06ab | ||
|
|
84f6d95fde | ||
|
|
4cbe2898c0 | ||
|
|
8a2b7f18cd | ||
|
|
85f8f2a764 | ||
|
|
14fc5ed289 | ||
|
|
39b294e085 | ||
|
|
217b632213 | ||
|
|
caee8e9952 | ||
|
|
b19ba12566 | ||
|
|
f54617867d | ||
|
|
6fb49eaadf | ||
|
|
8e60689bcf | ||
|
|
4415de010d | ||
|
|
f61246bf13 | ||
|
|
0c419a5094 | ||
|
|
895e774962 | ||
|
|
a9b1de7060 | ||
|
|
680388a7e8 | ||
|
|
2629ff55f3 | ||
|
|
c6d2aa6da2 | ||
|
|
165639c1dd | ||
|
|
d19233a338 | ||
|
|
03920502c4 | ||
|
|
fffb39227e | ||
|
|
9ee6285414 | ||
|
|
600272366f | ||
|
|
2e90e3b2b1 | ||
|
|
21a18c698e | ||
|
|
9986d8c9aa | ||
|
|
49f996867d | ||
|
|
ad4f546aca | ||
|
|
9e23f4d67b | ||
|
|
a5dff65142 | ||
|
|
8157d119a7 | ||
|
|
199799a966 | ||
|
|
3b4f292464 | ||
|
|
e4d1e5455f | ||
|
|
a23bbbfbb9 | ||
|
|
7ce77b724d | ||
|
|
f06e7f55c0 | ||
|
|
f47211c129 | ||
|
|
9cc3b18d52 | ||
|
|
4619552589 | ||
|
|
467839a9ea | ||
|
|
084995c378 | ||
|
|
005f69390c | ||
|
|
ecca190462 | ||
|
|
5d0d09bb7d | ||
|
|
972cae4859 | ||
|
|
77bc2d548a | ||
|
|
35e5cf2c2a | ||
|
|
c6f0ecd223 | ||
|
|
f727e4e661 | ||
|
|
1ad4003740 | ||
|
|
1108ad9994 | ||
|
|
f7f9b1e5ec | ||
|
|
c3d40949b1 | ||
|
|
a7accf4171 | ||
|
|
ab80646b86 | ||
|
|
3d0ac09fc9 | ||
|
|
0a84678a60 | ||
|
|
4a5c992b1a | ||
|
|
04b7c99753 | ||
|
|
499408657b | ||
|
|
4ec3fb6e12 | ||
|
|
463cbb1595 | ||
|
|
03608151e8 | ||
|
|
822394663c | ||
|
|
52517f78b1 | ||
|
|
a191b7b458 | ||
|
|
e68273c0ff | ||
|
|
aeff5faa3d | ||
|
|
0fd3757a51 | ||
|
|
1b064c1078 | ||
|
|
5726c29385 | ||
|
|
7c1c4981fb | ||
|
|
81488bcd20 | ||
|
|
99008eef43 | ||
|
|
3a9dc0ca2e | ||
|
|
98a550e352 | ||
|
|
4b8505b78d | ||
|
|
b7c2bacbd2 | ||
|
|
8108122347 | ||
|
|
45dada9552 | ||
|
|
38e0cbc1d2 | ||
|
|
e008a515e3 | ||
|
|
fd1e6afd07 | ||
|
|
9dd088f3db | ||
|
|
8e1417e3ce | ||
|
|
97526aa320 | ||
|
|
16e0351897 | ||
|
|
c0c7c949fd | ||
|
|
b8bc4060dd | ||
|
|
c737e3ee40 | ||
|
|
4c3d4508e9 | ||
|
|
70bcc9405f | ||
|
|
6a82f60901 | ||
|
|
814998253a | ||
|
|
a22c6da89f | ||
|
|
876a6a5c22 | ||
|
|
642e8f2aa6 | ||
|
|
a64ef2759c | ||
|
|
d58bbce24f | ||
|
|
ca6a7215e2 | ||
|
|
93b7548f1a | ||
|
|
24db05841b | ||
|
|
375d1d57fb | ||
|
|
c2e50e1d0d | ||
|
|
7988c1d11b | ||
|
|
8ab2a5320e | ||
|
|
b5a62825ce | ||
|
|
ec70815318 | ||
|
|
a739c17a6f | ||
|
|
ab5f4b6774 | ||
|
|
a5a544cb09 | ||
|
|
7d2374ed81 | ||
|
|
97b642a3e1 | ||
|
|
1151700114 | ||
|
|
75f654b944 | ||
|
|
bb852c3e85 | ||
|
|
1fbb69b35a | ||
|
|
0352c3250a | ||
|
|
268f828963 | ||
|
|
21508a8c79 | ||
|
|
f9de26f72c | ||
|
|
22580f771c | ||
|
|
9b338f69a6 | ||
|
|
fa0424cfd6 | ||
|
|
f6808a96e0 | ||
|
|
02bd7e5bc7 | ||
|
|
e8e3e8c111 | ||
|
|
c8588191f9 | ||
|
|
97e7f608df | ||
|
|
fae2c8c060 | ||
|
|
b4f2d82867 | ||
|
|
6a480780f8 | ||
|
|
41dc5382fa | ||
|
|
ba160e72ab | ||
|
|
0703a69369 | ||
|
|
c490d37c2d | ||
|
|
84219236a7 | ||
|
|
57fd995727 | ||
|
|
a803d687e2 | ||
|
|
c7927fb141 | ||
|
|
05d9602032 | ||
|
|
e76120da06 | ||
|
|
25bbecc269 | ||
|
|
08bb9cfae7 | ||
|
|
703b747a31 | ||
|
|
ff149b74e0 | ||
|
|
3d08eb92d5 | ||
|
|
02d16ac55c | ||
|
|
18eb7622ba | ||
|
|
13dd173664 | ||
|
|
73c078ec7a | ||
|
|
cdf50e2a69 | ||
|
|
2b0b29f921 | ||
|
|
0dc60fb535 | ||
|
|
5722a3458e | ||
|
|
93c52f615a | ||
|
|
050d686a27 | ||
|
|
7156ddf607 | ||
|
|
1cccc832b6 | ||
|
|
fd4eca5e03 | ||
|
|
1d9b9cff47 | ||
|
|
f4fe113c0f | ||
|
|
c7fc715535 | ||
|
|
eeea88046e | ||
|
|
dea887d27d | ||
|
|
8329e2e969 | ||
|
|
60415033b4 | ||
|
|
a06d760f45 | ||
|
|
b7687fcfb7 | ||
|
|
0ec86d5034 | ||
|
|
cef23f44cd | ||
|
|
e889a4923e | ||
|
|
114aba462c | ||
|
|
26c7cec7b5 | ||
|
|
3e3a33ab79 | ||
|
|
7f386e0e68 | ||
|
|
82d970d2b8 | ||
|
|
3ed9e836cc | ||
|
|
f984e8d6ef | ||
|
|
670cf4d394 | ||
|
|
e85fba844c | ||
|
|
ee5557ddf6 | ||
|
|
42f72b219b | ||
|
|
374721b789 | ||
|
|
01cec186ae | ||
|
|
3fb89f9f9b | ||
|
|
a0b4e76c1a | ||
|
|
97bf83aa03 | ||
|
|
ca7658cab7 | ||
|
|
dd78f4cfbf | ||
|
|
08019075c3 | ||
|
|
943617a94f | ||
|
|
d579c0ad57 | ||
|
|
76c6104415 | ||
|
|
ef9d803ce3 | ||
|
|
a26cb42d07 | ||
|
|
6b9b2836ba | ||
|
|
abdb8de89d | ||
|
|
ac492ef598 | ||
|
|
947bfe7b78 | ||
|
|
be6c90d135 | ||
|
|
8cb059deda | ||
|
|
0f4da5c1cf | ||
|
|
de138e9114 | ||
|
|
15bb9b29a2 | ||
|
|
4c132d94b9 | ||
|
|
925fc89447 | ||
|
|
cb95dbc707 | ||
|
|
1e3b6a201d | ||
|
|
3829ef4785 | ||
|
|
b382f06be0 | ||
|
|
94faceb57c | ||
|
|
a9ff58683e | ||
|
|
fafd6b2ac6 | ||
|
|
344a03e6b2 | ||
|
|
265abe1d08 | ||
|
|
ebdae87821 | ||
|
|
56ec79d62a | ||
|
|
c413b486fb | ||
|
|
cb0a0d228a | ||
|
|
3ae4a154f9 | ||
|
|
aa2dc6be09 | ||
|
|
a62ba86d7b | ||
|
|
454447d422 | ||
|
|
02d10a3aff | ||
|
|
4479b866ff | ||
|
|
907fdaa153 | ||
|
|
b85c0db72e | ||
|
|
8852745cf3 | ||
|
|
d1501527a2 | ||
|
|
ccd7939a92 | ||
|
|
db716d96e5 | ||
|
|
5f81353182 | ||
|
|
b71a851081 | ||
|
|
474dcb857a | ||
|
|
5ad0e3d72e | ||
|
|
2cf1797465 | ||
|
|
f2f54f2864 | ||
|
|
38232fe133 | ||
|
|
4405c4f190 | ||
|
|
c3a0fec2d9 | ||
|
|
8e3caaca7f | ||
|
|
860f627f48 | ||
|
|
3ddbee1666 | ||
|
|
fc20faf8f8 | ||
|
|
0749e5091a | ||
|
|
e61949da66 | ||
|
|
fdad24cc0a | ||
|
|
3ed30409ea | ||
|
|
d55d494e0a | ||
|
|
e7423696af | ||
|
|
c6c49d1476 | ||
|
|
4564275eba | ||
|
|
9b610c9760 | ||
|
|
ce97b0a5e7 | ||
|
|
5a26d4cf8f | ||
|
|
a0adff9d36 | ||
|
|
ad2fbf71ba | ||
|
|
097b073d20 | ||
|
|
a3afdc0ece | ||
|
|
ed092e6da7 | ||
|
|
78973a9f35 | ||
|
|
f672d3329a | ||
|
|
be269f3e1c | ||
|
|
c1047bef4f | ||
|
|
1b0677ec55 | ||
|
|
5ef0563abe | ||
|
|
56d8945d17 | ||
|
|
7f853a324a | ||
|
|
7f3e55df02 | ||
|
|
144aa97c00 | ||
|
|
9871fe2adf | ||
|
|
95f3aed82c | ||
|
|
8ba3e5d463 | ||
|
|
c8937ccdbf | ||
|
|
49f652a2ad | ||
|
|
12dbdbf258 | ||
|
|
abba305f64 | ||
|
|
d4cccd452d | ||
|
|
a555def6ca | ||
|
|
827a79861d | ||
|
|
42b6e20729 | ||
|
|
f3364a458c | ||
|
|
48b1b9a1aa | ||
|
|
787276366e | ||
|
|
6e758acd16 | ||
|
|
eef02e5c56 | ||
|
|
26951f5c18 | ||
|
|
bb42850d63 | ||
|
|
0ff1a88cc4 | ||
|
|
f80828cb07 | ||
|
|
65d5c6eb2b | ||
|
|
94dfe7bf69 | ||
|
|
97f342fc4c | ||
|
|
a43a6cbc06 | ||
|
|
8c495a1142 | ||
|
|
5d3028bd1f | ||
|
|
07f9f241c6 | ||
|
|
659c043584 | ||
|
|
b98bf07767 | ||
|
|
84eb91beaa | ||
|
|
de03b96232 | ||
|
|
0d11a94dad | ||
|
|
da4e6f275e | ||
|
|
b24e782b7d | ||
|
|
1139761525 | ||
|
|
0a56211df8 | ||
|
|
586354b571 | ||
|
|
bade4e661f | ||
|
|
c8d658e452 | ||
|
|
30526c564e | ||
|
|
8ec6f54f86 | ||
|
|
1213b51c66 | ||
|
|
c6173efe61 | ||
|
|
5ba8fd1267 | ||
|
|
4aa91efc2e | ||
|
|
448f08b74e | ||
|
|
b4e41ef953 | ||
|
|
fcf214b548 | ||
|
|
b9e8bff5e2 | ||
|
|
9c40c75136 | ||
|
|
77bd393a92 | ||
|
|
55d40e22b3 | ||
|
|
190793d82f | ||
|
|
d6c89ced99 | ||
|
|
d9332aec8c | ||
|
|
cdc9520c9d | ||
|
|
6cdde65052 | ||
|
|
6d62e55b5e | ||
|
|
ed93bbfb68 | ||
|
|
39eefdbc00 | ||
|
|
1e9e684575 | ||
|
|
3fb5b4992b | ||
|
|
4d647238b3 | ||
|
|
f83c38f5c1 | ||
|
|
f7076da700 | ||
|
|
9a713bc36f | ||
|
|
c6dcfcdf6d | ||
|
|
df038d8f05 | ||
|
|
0e5b17be85 | ||
|
|
4b3262622b | ||
|
|
3ef99863ee | ||
|
|
255d4fc04f | ||
|
|
742f385f23 | ||
|
|
0cc7ea9bc9 | ||
|
|
b39928188f | ||
|
|
946869ab23 | ||
|
|
5fa8338886 | ||
|
|
ec7b6b8d80 | ||
|
|
6f41530a03 | ||
|
|
1002acf907 | ||
|
|
d2355ea53b | ||
|
|
bee9bd7621 | ||
|
|
5a6d8ba010 | ||
|
|
8d24e35fa9 | ||
|
|
fc4d1151c7 | ||
|
|
c9e3e6902b | ||
|
|
2a3ecbac60 | ||
|
|
8e27c60120 | ||
|
|
11f3eece6d | ||
|
|
901182bcfc | ||
|
|
6a67d2dad2 | ||
|
|
1411fc11ee | ||
|
|
8e2e73fd81 | ||
|
|
4292129652 | ||
|
|
877705ca42 | ||
|
|
7bd3669220 | ||
|
|
9aa8f6bcf2 | ||
|
|
b2b08ab432 | ||
|
|
3bec1a6938 | ||
|
|
9bb88b43ca | ||
|
|
a2931d7a48 | ||
|
|
d241c31e3c | ||
|
|
b1e6901d61 | ||
|
|
f46d676130 | ||
|
|
9463c112df | ||
|
|
d44e7086d7 | ||
|
|
c05629b3de | ||
|
|
c64ee8a07c | ||
|
|
857f6a79ae | ||
|
|
744662d096 | ||
|
|
81e7dcf31e | ||
|
|
eca845fa81 | ||
|
|
3df63cff12 | ||
|
|
16b64f59b7 | ||
|
|
6f9f5102d0 | ||
|
|
b17e7d5746 | ||
|
|
95cd8427f4 | ||
|
|
03de39092a | ||
|
|
aa924cd09b | ||
|
|
beacb58eb1 | ||
|
|
70527d7329 | ||
|
|
f01b2fb4d9 | ||
|
|
655344c09c | ||
|
|
d2d1bb4def | ||
|
|
b5016d6f43 | ||
|
|
7583d297ad | ||
|
|
146ddd5669 | ||
|
|
ffd720c323 | ||
|
|
8cad21819c | ||
|
|
016e66846b | ||
|
|
6cf6903d32 | ||
|
|
ea490b9a2b | ||
|
|
7ec76bc0b5 | ||
|
|
4b2518ca9a | ||
|
|
1b668966ce | ||
|
|
c4f0c7940f | ||
|
|
f9eedfbf64 | ||
|
|
05a3d7a3bc | ||
|
|
cbd16e6d6b | ||
|
|
7d41fb970e | ||
|
|
3251d8ffe6 | ||
|
|
6eb92f55df | ||
|
|
c654301f22 | ||
|
|
55feb95d41 | ||
|
|
9e29e35e16 | ||
|
|
8db3bb3dc1 | ||
|
|
7f5225cb70 | ||
|
|
dc2f4e06c8 | ||
|
|
61ccbb0d3e | ||
|
|
4176af337f | ||
|
|
cc68942ec1 | ||
|
|
52ae6e7f0b | ||
|
|
ba59ab40ab | ||
|
|
0fb5fd271a | ||
|
|
8e3f85c475 | ||
|
|
b1bd630a37 | ||
|
|
4b829c358b | ||
|
|
02ab71ff26 | ||
|
|
ac962ea6db | ||
|
|
7de5fee3ad | ||
|
|
e70c49fea2 | ||
|
|
c640aa9213 | ||
|
|
9d5f57d798 | ||
|
|
063eef3eaf | ||
|
|
b5d1e00930 | ||
|
|
f53c977069 | ||
|
|
051db30dfb | ||
|
|
4f64dd30f9 | ||
|
|
904c4d04bb | ||
|
|
f49d48fbd2 | ||
|
|
e4170d65b7 | ||
|
|
b7eeb60e9c | ||
|
|
7fc7e631f8 | ||
|
|
0e95aaeaad | ||
|
|
dcbc60e1f0 | ||
|
|
03f29c51cf | ||
|
|
5ff3e4d1d1 | ||
|
|
8b1d4a7824 | ||
|
|
079783e3a1 | ||
|
|
409bf907d9 | ||
|
|
4a2ada56e5 | ||
|
|
de7b638e6c | ||
|
|
a6a71c59f4 | ||
|
|
e57ff54caa | ||
|
|
1430ac2675 | ||
|
|
eb07c0b4cf | ||
|
|
be6760e427 | ||
|
|
f8f858216f | ||
|
|
037a069ddd | ||
|
|
dc15470e0b | ||
|
|
895eae1d54 | ||
|
|
ad48ec4cfd | ||
|
|
a6693616a0 | ||
|
|
ea6462daf4 | ||
|
|
67d7f8d867 | ||
|
|
ee86b58ab9 | ||
|
|
5099ef15b4 | ||
|
|
c675e85d69 | ||
|
|
afced5014c | ||
|
|
cabdb7f032 | ||
|
|
8fcf885de3 | ||
|
|
2d6c037f39 | ||
|
|
d9919efb4c | ||
|
|
1302d8abef | ||
|
|
c6586ed811 | ||
|
|
eb0977b700 | ||
|
|
b7c866f5e4 | ||
|
|
7c385f72a1 | ||
|
|
9af8638589 | ||
|
|
16ec84efe4 | ||
|
|
c0c1aff577 | ||
|
|
45a5eee18a | ||
|
|
d0b0fb3cb3 | ||
|
|
f71d6883d9 | ||
|
|
43849d2b8e | ||
|
|
2d8d4d5c99 | ||
|
|
2cb1bd162f | ||
|
|
f996df087e | ||
|
|
c647bfa490 | ||
|
|
a925301caf | ||
|
|
202b1784a1 | ||
|
|
87fd56859d | ||
|
|
73aca23615 | ||
|
|
44b9b8787a | ||
|
|
171874d288 | ||
|
|
329270e444 | ||
|
|
4d3a698a12 | ||
|
|
df9c9d8dff | ||
|
|
0e42df2da7 | ||
|
|
3afcfccba8 | ||
|
|
2f562040ac | ||
|
|
6ced926db0 | ||
|
|
ad0000886d | ||
|
|
3c74b9bf10 | ||
|
|
05eb06d91b | ||
|
|
3602c95341 | ||
|
|
b2f6758a9c | ||
|
|
e843c6108d | ||
|
|
2724ac9e07 | ||
|
|
201cf880f9 | ||
|
|
0bf4bf36f0 | ||
|
|
3bef9a67b8 | ||
|
|
3ba3d72d6b | ||
|
|
44639ee50e | ||
|
|
0f037d0e6c | ||
|
|
1e12e1e318 | ||
|
|
bb050eebed | ||
|
|
9f26c27b6d | ||
|
|
c801e24afc | ||
|
|
31442ecb3b | ||
|
|
24a06d2bf9 | ||
|
|
e61e210b41 | ||
|
|
255d0d9fb5 | ||
|
|
8c9ac923c6 | ||
|
|
85fc799d62 | ||
|
|
3d5b13c25e | ||
|
|
cccbf50a0e | ||
|
|
e50f65527d | ||
|
|
a356859e7e | ||
|
|
96d607d411 | ||
|
|
d6232e238a | ||
|
|
8d0c4d3cec | ||
|
|
e95f4c7aa5 | ||
|
|
d222d78c7b | ||
|
|
aaae4b343e | ||
|
|
7ccc0d9d7b | ||
|
|
02b01a8bc3 | ||
|
|
c0f5c5f24c | ||
|
|
c997d568f3 | ||
|
|
87bcaadf40 | ||
|
|
f4a6856e54 | ||
|
|
fa17681cf6 | ||
|
|
7c56052d58 | ||
|
|
2fc53045c7 | ||
|
|
2f1ce2bbf9 | ||
|
|
aa37f6f738 | ||
|
|
2ad652a071 | ||
|
|
ab8d7e8659 | ||
|
|
7cd79c440c | ||
|
|
a4b5950495 | ||
|
|
04095f7682 | ||
|
|
1c105b5c68 | ||
|
|
f4c17e578c | ||
|
|
993567ca56 | ||
|
|
e01d901399 | ||
|
|
a437c2cb02 | ||
|
|
b6612a83c3 | ||
|
|
151935dc67 | ||
|
|
ad69daf1a3 | ||
|
|
234f3d93cd | ||
|
|
77a7792afc | ||
|
|
e2fea0a5de | ||
|
|
fce37fa0e3 | ||
|
|
7ab3586e52 | ||
|
|
92a8a84ff2 | ||
|
|
156e5f6beb | ||
|
|
8e9a91abf8 | ||
|
|
32d2397e64 | ||
|
|
087a58965b | ||
|
|
b7a164afa8 | ||
|
|
b659b20d27 | ||
|
|
d77e43b57d | ||
|
|
bfd8ce475a | ||
|
|
967d35e4be | ||
|
|
0cad79ad18 | ||
|
|
cd8c9436c5 | ||
|
|
f93134d4f8 | ||
|
|
5743f54d69 | ||
|
|
1914d10836 | ||
|
|
6b579d53ec | ||
|
|
6031971028 | ||
|
|
c1d65ff144 | ||
|
|
7374819ade | ||
|
|
9d19b060a9 | ||
|
|
23d61e5e97 | ||
|
|
46742328b6 | ||
|
|
467c2e5def | ||
|
|
ffd9a6b484 | ||
|
|
8aca357de6 | ||
|
|
1a32663f85 | ||
|
|
4fecca032d | ||
|
|
2a9e678877 | ||
|
|
17136e03d2 | ||
|
|
94f2677752 | ||
|
|
eac69aef2b | ||
|
|
2dd2d06bca | ||
|
|
5a2e3ee8e3 | ||
|
|
8ac7d1fdb6 | ||
|
|
0bf8a69024 | ||
|
|
9bb8f335c9 | ||
|
|
8d313e014f | ||
|
|
a79d386eba | ||
|
|
48b137a7f5 | ||
|
|
b4a4dacebd | ||
|
|
efd8861d62 | ||
|
|
2f86f549f5 | ||
|
|
cc0c4cc308 | ||
|
|
e3d5ee8332 | ||
|
|
3c201cc36c | ||
|
|
f6983d6126 | ||
|
|
1c80705276 | ||
|
|
d3f205f634 | ||
|
|
b542b17d93 | ||
|
|
59c7623769 | ||
|
|
e2ab4c060f | ||
|
|
025b8bba76 | ||
|
|
5e7ff808d4 | ||
|
|
86fbf3fef6 | ||
|
|
24174632d4 | ||
|
|
1065768c77 | ||
|
|
ca784916bb | ||
|
|
fcda3f7bc5 | ||
|
|
fcda62862c | ||
|
|
881ffadb5c | ||
|
|
7b20ad7749 | ||
|
|
ddef626e66 | ||
|
|
50399935c9 | ||
|
|
57587f71ab | ||
|
|
b561d1fc17 | ||
|
|
ed90a69e2c | ||
|
|
3703c43d62 | ||
|
|
30c2e64d9e | ||
|
|
af12789762 | ||
|
|
9bf2b9f6e4 | ||
|
|
50edd82268 | ||
|
|
babf074448 | ||
|
|
9d3043ee39 | ||
|
|
33b73d7fbc | ||
|
|
af51c9cc33 | ||
|
|
f55da1e1d6 | ||
|
|
ba0d71bef1 | ||
|
|
add33f5f80 | ||
|
|
79189f243a | ||
|
|
81b42c8633 | ||
|
|
541a8d3a3e | ||
|
|
3cbba71e7e | ||
|
|
9617d4527d | ||
|
|
dc77c12e83 | ||
|
|
3ec78ba6c9 | ||
|
|
a21eaf9dba | ||
|
|
249564d6ea | ||
|
|
90a28c7b1e | ||
|
|
46da1df5ae | ||
|
|
fda6409600 | ||
|
|
d1be92ac80 | ||
|
|
b6cb1fb72d | ||
|
|
26b49f8d01 | ||
|
|
c87398a8c2 | ||
|
|
3940fd8eff | ||
|
|
aa4846bff6 | ||
|
|
3ec194093d | ||
|
|
f7442032b2 | ||
|
|
2c5e2609f3 | ||
|
|
ae1f5fa511 | ||
|
|
0c37256050 | ||
|
|
decb5046ea | ||
|
|
b2824a3547 | ||
|
|
74c965b55c | ||
|
|
83ba02d0fb | ||
|
|
63bd762f91 | ||
|
|
cc9641f8c1 | ||
|
|
c446bcf885 | ||
|
|
d9e711ab11 | ||
|
|
3260867918 | ||
|
|
d90011c002 | ||
|
|
2406c8374f | ||
|
|
3d4f241129 | ||
|
|
9766abf1c5 | ||
|
|
feefde400e | ||
|
|
15ae767a79 | ||
|
|
b293e8e9e1 | ||
|
|
bb0bf41cab | ||
|
|
b2c0597a7d | ||
|
|
3c3ad7b240 | ||
|
|
a7c21eff4b | ||
|
|
6b86ad9083 | ||
|
|
2b268435c4 | ||
|
|
07d48df314 | ||
|
|
a07b062752 | ||
|
|
dd1e53b498 | ||
|
|
2eb5e9b42d | ||
|
|
5e6e4356fc | ||
|
|
5bb88ca703 | ||
|
|
eb27c64c71 | ||
|
|
644e292fa7 | ||
|
|
28ecbd6b6a | ||
|
|
021d1bc568 | ||
|
|
12a0357f6b | ||
|
|
55982d699b | ||
|
|
1948f23fb3 | ||
|
|
cb3cd3022d | ||
|
|
d2c0b13a02 | ||
|
|
cf6cae728a | ||
|
|
8b039287c8 | ||
|
|
75203c55f8 | ||
|
|
aeeb4880b1 | ||
|
|
d5d7679120 | ||
|
|
986c69abea | ||
|
|
a73c7092bb | ||
|
|
3ecae30b5c | ||
|
|
6dc53c3887 | ||
|
|
4fbede7445 | ||
|
|
c29cde6784 | ||
|
|
f610af36c6 | ||
|
|
d8090cfa0a | ||
|
|
b847bb1c72 | ||
|
|
4491175db4 | ||
|
|
d0fa228282 | ||
|
|
faacfb9578 | ||
|
|
26329de5a5 | ||
|
|
1eb8658922 | ||
|
|
8fa3f093a1 | ||
|
|
fbc327b960 | ||
|
|
52aa5b6764 | ||
|
|
4a5cb389b7 | ||
|
|
f2d67f4a5d | ||
|
|
3581ce7059 | ||
|
|
0a67b387c6 | ||
|
|
a352fc8595 | ||
|
|
a93dff2673 | ||
|
|
7856d27724 | ||
|
|
da3ffd8bd0 | ||
|
|
742179ee38 | ||
|
|
d5d9e51f66 | ||
|
|
19096f83db | ||
|
|
2f3fb54ebb | ||
|
|
e12f9d5a1c | ||
|
|
a45d86c2a4 | ||
|
|
be58b627b2 | ||
|
|
b008a525cb | ||
|
|
228440c03f | ||
|
|
3f5ac0cf56 | ||
|
|
1e8674b51c | ||
|
|
a8401f6923 | ||
|
|
dddd302980 | ||
|
|
5d44e1991f | ||
|
|
55f0966a9a | ||
|
|
7daa26ce81 | ||
|
|
8dca2b81e4 | ||
|
|
b14b3d1012 | ||
|
|
43c04a71a8 | ||
|
|
9313fb9021 | ||
|
|
380f0ac404 | ||
|
|
1b8c87215d | ||
|
|
65340e6e24 | ||
|
|
f96a14e7f4 | ||
|
|
ad83f5419a | ||
|
|
ba5abf4700 | ||
|
|
78f0cc9e8a | ||
|
|
d6bdb206c8 | ||
|
|
6539031d5a | ||
|
|
f35c233289 | ||
|
|
fbd72179a1 | ||
|
|
af5d9d804e | ||
|
|
8e8271cf54 | ||
|
|
b5b0214c3c | ||
|
|
4bb7a595e8 | ||
|
|
7d3eba1d8d | ||
|
|
f3b2d49880 | ||
|
|
bdff4e21a8 | ||
|
|
f1b45bed96 | ||
|
|
fe41c29b29 | ||
|
|
a06ca5d035 | ||
|
|
75a02a13d9 | ||
|
|
8fad33b125 | ||
|
|
bbc6e830e2 | ||
|
|
ef9d0421fa | ||
|
|
cc493866cd | ||
|
|
2ec4d1e426 | ||
|
|
de311b2f2d | ||
|
|
c2b78b175c | ||
|
|
ff6516d1d7 | ||
|
|
f435f23570 | ||
|
|
994e7d1910 | ||
|
|
389d4e3d9c | ||
|
|
43ffcb0802 | ||
|
|
5fda4a2f8b | ||
|
|
9807a7f038 | ||
|
|
57fa5f5bd9 | ||
|
|
1b11162132 | ||
|
|
75ab83da63 | ||
|
|
cc3b08fd1b | ||
|
|
eb9a852443 | ||
|
|
93d50e0f0c | ||
|
|
62df944c47 | ||
|
|
d07d1a78d3 | ||
|
|
1107967f76 | ||
|
|
5d9f29743c | ||
|
|
6807e3b6d5 | ||
|
|
1244eb9998 | ||
|
|
9ece2844f4 | ||
|
|
a646d930c8 | ||
|
|
6f8385143f | ||
|
|
1a29552bff | ||
|
|
190a531daa | ||
|
|
a68e35c895 | ||
|
|
9722860417 | ||
|
|
7fff203360 | ||
|
|
bd3bd2e53b | ||
|
|
6abd96a398 | ||
|
|
eac8cfe63d | ||
|
|
928e80c9e9 | ||
|
|
4a69ab3bf8 | ||
|
|
91a18ec63c | ||
|
|
9e7879d43f | ||
|
|
99c08fd205 | ||
|
|
82af902cc8 | ||
|
|
d0c1df5f2a | ||
|
|
a5e6f26267 | ||
|
|
4730c71b16 | ||
|
|
9cbf20aa48 | ||
|
|
68bd61708e | ||
|
|
fa16c9e59d | ||
|
|
39162de2a8 | ||
|
|
4a3fc91c1e | ||
|
|
ab872b9a34 | ||
|
|
e086c433ff | ||
|
|
5d24bc7625 | ||
|
|
74db580671 | ||
|
|
6036ea60d1 | ||
|
|
f432a0b7c4 | ||
|
|
38176ae7e6 | ||
|
|
35ce54630e | ||
|
|
39f1dfc85e | ||
|
|
0edc63ca8b | ||
|
|
3351b06603 | ||
|
|
5302032b63 | ||
|
|
6bf21c4157 | ||
|
|
a28b179a45 | ||
|
|
7d6141abb7 | ||
|
|
e3203ebaa5 | ||
|
|
ecda9cc746 | ||
|
|
ab4e415aec | ||
|
|
369dca79ef | ||
|
|
8dc2aee4b4 | ||
|
|
78ac2c1f1f | ||
|
|
2dfe2de0fe | ||
|
|
aef4aa6859 | ||
|
|
2ec503d6eb | ||
|
|
f5f9fc1955 | ||
|
|
10383de959 | ||
|
|
c0c6ce2987 | ||
|
|
7fc311bb3e | ||
|
|
5979b93a7a | ||
|
|
ac6b7ff14e | ||
|
|
80ab4d8ff5 | ||
|
|
bf6974dabb | ||
|
|
28a55386b6 | ||
|
|
1fce0b45f4 | ||
|
|
18e6a784e8 | ||
|
|
511ba5231a | ||
|
|
0edfe86d8b | ||
|
|
762d56204f | ||
|
|
a884b6c782 | ||
|
|
1a5710f140 | ||
|
|
af9f019d37 | ||
|
|
cbf6c617de | ||
|
|
921ab6e391 | ||
|
|
e74d4fe9b7 | ||
|
|
7c8051feab | ||
|
|
7b896ae5d0 | ||
|
|
b3ffc092cd | ||
|
|
bd5af5f148 | ||
|
|
4a7bded98d | ||
|
|
5261cdf4a1 | ||
|
|
05d07c23ab | ||
|
|
10bc446255 | ||
|
|
ac7ce7c481 | ||
|
|
4daa73d487 | ||
|
|
3cfbedcb69 | ||
|
|
18b6febe86 | ||
|
|
465264e07d | ||
|
|
3526def0a0 | ||
|
|
05cf6af546 | ||
|
|
9fe9bed1c9 | ||
|
|
6ddc242746 | ||
|
|
5081b06016 | ||
|
|
fe78fa9850 | ||
|
|
11b2ac9923 | ||
|
|
73682b95f5 | ||
|
|
705f561bdb | ||
|
|
84b89f4689 | ||
|
|
bc5ca4d8ae | ||
|
|
53ca7c19cd | ||
|
|
b3a07941bb | ||
|
|
62842c8ac1 | ||
|
|
d30af70351 | ||
|
|
52746faabf | ||
|
|
f7f32fe206 | ||
|
|
aa8e2c7173 | ||
|
|
facbf61133 | ||
|
|
1ade520ac0 | ||
|
|
5466f930be | ||
|
|
505c424cf4 | ||
|
|
62a941f233 | ||
|
|
97c9aca245 | ||
|
|
49eae5b6f8 | ||
|
|
7a48fdc5f6 | ||
|
|
faba29a42b | ||
|
|
403cf02c65 | ||
|
|
59d43683dc | ||
|
|
50b58a314e | ||
|
|
a3b5247de9 | ||
|
|
5143c71589 | ||
|
|
31bf8e48bb | ||
|
|
61de28f741 | ||
|
|
c8caa8f4ac | ||
|
|
c196075cb8 | ||
|
|
dfbd1f8772 | ||
|
|
b5670fdc5f | ||
|
|
cdb96bff47 | ||
|
|
35361f4edc | ||
|
|
9bba91628a | ||
|
|
b073b05aa0 | ||
|
|
e6f28b06b5 | ||
|
|
4e75a35468 | ||
|
|
e827559340 | ||
|
|
6bcac44050 | ||
|
|
ee43fd7579 | ||
|
|
b809768934 | ||
|
|
1739ae44f0 | ||
|
|
f72f3f3797 | ||
|
|
18f26a0c04 | ||
|
|
873558a392 | ||
|
|
c88afb71c9 | ||
|
|
27ab4ba339 | ||
|
|
8a9202135b | ||
|
|
7711167052 | ||
|
|
e7635b40d5 | ||
|
|
f5cbb5de49 | ||
|
|
2cd1ae73ed | ||
|
|
061489ec9a | ||
|
|
df55f62ad8 | ||
|
|
7d2b7bb3c1 | ||
|
|
c4e2892100 | ||
|
|
61bc15b1aa | ||
|
|
5bad06d4b6 | ||
|
|
b9f8daf848 | ||
|
|
a34ee5bb92 |
@@ -1,15 +1,13 @@
|
||||
[run]
|
||||
omit =
|
||||
jedi/_compatibility.py
|
||||
jedi/evaluate/compiled/subprocess/__main__.py
|
||||
jedi/inference/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
|
||||
|
||||
[report]
|
||||
# Regexes for lines to exclude from consideration
|
||||
exclude_lines =
|
||||
# Don't complain about missing debug-only code:
|
||||
def __repr__
|
||||
debug.warning
|
||||
|
||||
14
.editorconfig
Normal file
14
.editorconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.py]
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
indent_size = 2
|
||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1 @@
|
||||
github: [davidhalter]
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,7 +2,6 @@
|
||||
*.sw?
|
||||
*.pyc
|
||||
.ropeproject
|
||||
.tox
|
||||
.coveralls.yml
|
||||
.coverage
|
||||
.idea
|
||||
@@ -13,3 +12,5 @@ jedi.egg-info/
|
||||
record.json
|
||||
/.cache/
|
||||
/.pytest_cache
|
||||
/.mypy_cache
|
||||
/venv/
|
||||
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "jedi/third_party/typeshed"]
|
||||
path = jedi/third_party/typeshed
|
||||
url = https://github.com/davidhalter/typeshed.git
|
||||
[submodule "jedi/third_party/django-stubs"]
|
||||
path = jedi/third_party/django-stubs
|
||||
url = https://github.com/davidhalter/django-stubs
|
||||
2
.readthedocs.yml
Normal file
2
.readthedocs.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
python:
|
||||
pip_install: true
|
||||
105
.travis.yml
105
.travis.yml
@@ -1,57 +1,74 @@
|
||||
dist: xenial
|
||||
language: python
|
||||
sudo: true
|
||||
python:
|
||||
- 2.7
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.9-dev
|
||||
- 3.8
|
||||
- 3.7
|
||||
- 3.6
|
||||
|
||||
env:
|
||||
- JEDI_TEST_ENVIRONMENT=27
|
||||
- JEDI_TEST_ENVIRONMENT=34
|
||||
- JEDI_TEST_ENVIRONMENT=35
|
||||
- JEDI_TEST_ENVIRONMENT=38
|
||||
- JEDI_TEST_ENVIRONMENT=39
|
||||
- JEDI_TEST_ENVIRONMENT=37
|
||||
- JEDI_TEST_ENVIRONMENT=36
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
# Required to properly create a virtual environment with system Python 3.4.
|
||||
- python3.4-venv
|
||||
- JEDI_TEST_ENVIRONMENT=interpreter
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- python: pypy
|
||||
- env: TOXENV=sith
|
||||
- python: 3.7-dev
|
||||
include:
|
||||
- python: 3.6
|
||||
env:
|
||||
- TOXENV=cov
|
||||
- JEDI_TEST_ENVIRONMENT=36
|
||||
- python: 3.6
|
||||
env: TOXENV=sith
|
||||
# For now ignore pypy, there are so many issues that we don't really need
|
||||
# to run it.
|
||||
#- python: pypy
|
||||
- python: "3.7-dev"
|
||||
before_install:
|
||||
- ./travis_install.sh
|
||||
# Need to add the path to the Python versions in the end. This might add
|
||||
# something twice, but it doesn't really matter, because they are appended.
|
||||
- export PATH=$PATH:/opt/python/3.5/bin
|
||||
# 3.6 was not installed manually, but already is on the system. However
|
||||
# it's not on path (unless 3.6 is selected).
|
||||
- export PATH=$PATH:/opt/python/3.6/bin
|
||||
- python: 3.8
|
||||
script:
|
||||
- 'pip install coverage'
|
||||
- 'coverage run --source jedi -m pytest'
|
||||
- 'coverage report'
|
||||
after_script:
|
||||
- |
|
||||
pip install --quiet codecov coveralls
|
||||
coverage xml
|
||||
coverage report -m
|
||||
coveralls
|
||||
bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X search -X fix -X xcode -f coverage.xml
|
||||
- python: 3.8
|
||||
install:
|
||||
- 'pip install .[qa]'
|
||||
script:
|
||||
- 'flake8 jedi setup.py'
|
||||
- 'mypy jedi sith.py'
|
||||
install:
|
||||
- pip install --quiet tox-travis
|
||||
- sudo apt-get -y install python3-venv
|
||||
- pip install .[testing]
|
||||
script:
|
||||
- tox
|
||||
after_script:
|
||||
- |
|
||||
if [ $TOXENV == "cov" ]; then
|
||||
pip install --quiet codecov coveralls
|
||||
coverage xml
|
||||
coverage report -m
|
||||
coveralls
|
||||
bash <(curl -s https://codecov.io/bash) -X gcov -X coveragepy -X search -X fix -X xcode -f coverage.xml
|
||||
# Setup/install Python for $JEDI_TEST_ENVIRONMENT.
|
||||
set -ex
|
||||
test_env_version=${JEDI_TEST_ENVIRONMENT:0:1}.${JEDI_TEST_ENVIRONMENT:1:1}
|
||||
if [ "$TRAVIS_PYTHON_VERSION" != "$test_env_version" ] && [ "$JEDI_TEST_ENVIRONMENT" != "interpreter" ]; then
|
||||
python_bin=python$test_env_version
|
||||
python_path="$(which $python_bin || true)"
|
||||
if [ -z "$python_path" ]; then
|
||||
# Only required for JEDI_TEST_ENVIRONMENT=38, because it's not always
|
||||
# available.
|
||||
download_name=python-$test_env_version
|
||||
if [ "$JEDI_TEST_ENVIRONMENT" == "39" ]; then
|
||||
wget https://storage.googleapis.com/travis-ci-language-archives/python/binaries/ubuntu/16.04/x86_64/python-3.9-dev.tar.bz2
|
||||
sudo tar xjf python-3.9-dev.tar.bz2 --directory / opt/python
|
||||
ln -s "/opt/python/3.9-dev/bin/python" /home/travis/bin/python3.9
|
||||
else
|
||||
wget https://s3.amazonaws.com/travis-python-archives/binaries/ubuntu/16.04/x86_64/$download_name.tar.bz2
|
||||
sudo tar xjf $download_name.tar.bz2 --directory / opt/python
|
||||
ln -s "/opt/python/${test_env_version}/bin/python" /home/travis/bin/$python_bin
|
||||
fi
|
||||
elif [ "${python_path#/opt/pyenv/shims}" != "$python_path" ]; then
|
||||
# Activate pyenv version (required with JEDI_TEST_ENVIRONMENT=36).
|
||||
pyenv_bin="$(pyenv whence --path "$python_bin" | head -n1)"
|
||||
ln -s "$pyenv_bin" /home/travis/bin/$python_bin
|
||||
fi
|
||||
$python_bin --version
|
||||
python_ver=$($python_bin -c 'import sys; print("%d%d" % sys.version_info[0:2])')
|
||||
if [ "$JEDI_TEST_ENVIRONMENT" != "$python_ver" ]; then
|
||||
echo "Unexpected Python version for $JEDI_TEST_ENVIRONMENT: $python_ver"
|
||||
set +ex
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
set +ex
|
||||
- pytest
|
||||
|
||||
108
AUTHORS.txt
108
AUTHORS.txt
@@ -1,55 +1,67 @@
|
||||
Main Authors
|
||||
============
|
||||
------------
|
||||
|
||||
David Halter (@davidhalter) <davidhalter88@gmail.com>
|
||||
Takafumi Arakaki (@tkf) <aka.tkf@gmail.com>
|
||||
- David Halter (@davidhalter) <davidhalter88@gmail.com>
|
||||
- Takafumi Arakaki (@tkf) <aka.tkf@gmail.com>
|
||||
|
||||
Code Contributors
|
||||
=================
|
||||
-----------------
|
||||
|
||||
Danilo Bargen (@dbrgn) <mail@dbrgn.ch>
|
||||
Laurens Van Houtven (@lvh) <_@lvh.cc>
|
||||
Aldo Stracquadanio (@Astrac) <aldo.strac@gmail.com>
|
||||
Jean-Louis Fuchs (@ganwell) <ganwell@fangorn.ch>
|
||||
tek (@tek)
|
||||
Yasha Borevich (@jjay) <j.borevich@gmail.com>
|
||||
Aaron Griffin <aaronmgriffin@gmail.com>
|
||||
andviro (@andviro)
|
||||
Mike Gilbert (@floppym) <floppym@gentoo.org>
|
||||
Aaron Meurer (@asmeurer) <asmeurer@gmail.com>
|
||||
Lubos Trilety <ltrilety@redhat.com>
|
||||
Akinori Hattori (@hattya) <hattya@gmail.com>
|
||||
srusskih (@srusskih)
|
||||
Steven Silvester (@blink1073)
|
||||
Colin Duquesnoy (@ColinDuquesnoy) <colin.duquesnoy@gmail.com>
|
||||
Jorgen Schaefer (@jorgenschaefer) <contact@jorgenschaefer.de>
|
||||
Fredrik Bergroth (@fbergroth)
|
||||
Mathias Fußenegger (@mfussenegger)
|
||||
Syohei Yoshida (@syohex) <syohex@gmail.com>
|
||||
ppalucky (@ppalucky)
|
||||
immerrr (@immerrr) immerrr@gmail.com
|
||||
Albertas Agejevas (@alga)
|
||||
Savor d'Isavano (@KenetJervet) <newelevenken@163.com>
|
||||
Phillip Berndt (@phillipberndt) <phillip.berndt@gmail.com>
|
||||
Ian Lee (@IanLee1521) <IanLee1521@gmail.com>
|
||||
Farkhad Khatamov (@hatamov) <comsgn@gmail.com>
|
||||
Kevin Kelley (@kelleyk) <kelleyk@kelleyk.net>
|
||||
Sid Shanker (@squidarth) <sid.p.shanker@gmail.com>
|
||||
Reinoud Elhorst (@reinhrst)
|
||||
Guido van Rossum (@gvanrossum) <guido@python.org>
|
||||
Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro>
|
||||
Cristi Burcă (@scribu)
|
||||
bstaint (@bstaint)
|
||||
Mathias Rav (@Mortal) <rav@cs.au.dk>
|
||||
Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
|
||||
Simon Ruggier (@sruggier)
|
||||
Élie Gouzien (@ElieGouzien)
|
||||
Robin Roth (@robinro)
|
||||
Malte Plath (@langsamer)
|
||||
Anton Zub (@zabulazza)
|
||||
Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
|
||||
Tobias Rzepka (@TobiasRzepka)
|
||||
micbou (@micbou)
|
||||
Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
|
||||
- Danilo Bargen (@dbrgn) <mail@dbrgn.ch>
|
||||
- Laurens Van Houtven (@lvh) <_@lvh.cc>
|
||||
- Aldo Stracquadanio (@Astrac) <aldo.strac@gmail.com>
|
||||
- Jean-Louis Fuchs (@ganwell) <ganwell@fangorn.ch>
|
||||
- tek (@tek)
|
||||
- Yasha Borevich (@jjay) <j.borevich@gmail.com>
|
||||
- Aaron Griffin <aaronmgriffin@gmail.com>
|
||||
- andviro (@andviro)
|
||||
- Mike Gilbert (@floppym) <floppym@gentoo.org>
|
||||
- Aaron Meurer (@asmeurer) <asmeurer@gmail.com>
|
||||
- Lubos Trilety <ltrilety@redhat.com>
|
||||
- Akinori Hattori (@hattya) <hattya@gmail.com>
|
||||
- srusskih (@srusskih)
|
||||
- Steven Silvester (@blink1073)
|
||||
- Colin Duquesnoy (@ColinDuquesnoy) <colin.duquesnoy@gmail.com>
|
||||
- Jorgen Schaefer (@jorgenschaefer) <contact@jorgenschaefer.de>
|
||||
- Fredrik Bergroth (@fbergroth)
|
||||
- Mathias Fußenegger (@mfussenegger)
|
||||
- Syohei Yoshida (@syohex) <syohex@gmail.com>
|
||||
- ppalucky (@ppalucky)
|
||||
- immerrr (@immerrr) immerrr@gmail.com
|
||||
- Albertas Agejevas (@alga)
|
||||
- Savor d'Isavano (@KenetJervet) <newelevenken@163.com>
|
||||
- Phillip Berndt (@phillipberndt) <phillip.berndt@gmail.com>
|
||||
- Ian Lee (@IanLee1521) <IanLee1521@gmail.com>
|
||||
- Farkhad Khatamov (@hatamov) <comsgn@gmail.com>
|
||||
- Kevin Kelley (@kelleyk) <kelleyk@kelleyk.net>
|
||||
- Sid Shanker (@squidarth) <sid.p.shanker@gmail.com>
|
||||
- Reinoud Elhorst (@reinhrst)
|
||||
- Guido van Rossum (@gvanrossum) <guido@python.org>
|
||||
- Dmytro Sadovnychyi (@sadovnychyi) <jedi@dmit.ro>
|
||||
- Cristi Burcă (@scribu)
|
||||
- bstaint (@bstaint)
|
||||
- Mathias Rav (@Mortal) <rav@cs.au.dk>
|
||||
- Daniel Fiterman (@dfit99) <fitermandaniel2@gmail.com>
|
||||
- Simon Ruggier (@sruggier)
|
||||
- Élie Gouzien (@ElieGouzien)
|
||||
- Robin Roth (@robinro)
|
||||
- Malte Plath (@langsamer)
|
||||
- Anton Zub (@zabulazza)
|
||||
- Maksim Novikov (@m-novikov) <mnovikov.work@gmail.com>
|
||||
- Tobias Rzepka (@TobiasRzepka)
|
||||
- micbou (@micbou)
|
||||
- Dima Gerasimov (@karlicoss) <karlicoss@gmail.com>
|
||||
- Max Woerner Chase (@mwchase) <max.chase@gmail.com>
|
||||
- Johannes Maria Frank (@jmfrank63) <jmfrank63@gmail.com>
|
||||
- Shane Steinert-Threlkeld (@shanest) <ssshanest@gmail.com>
|
||||
- Tim Gates (@timgates42) <tim.gates@iress.com>
|
||||
- Lior Goldberg (@goldberglior)
|
||||
- Ryan Clary (@mrclary)
|
||||
- Max Mäusezahl (@mmaeusezahl) <maxmaeusezahl@googlemail.com>
|
||||
- Vladislav Serebrennikov (@endilll)
|
||||
- Andrii Kolomoiets (@muffinmad)
|
||||
- Leo Ryu (@Leo-Ryu)
|
||||
|
||||
And a few more "anonymous" contributors.
|
||||
|
||||
Note: (@user) means a github user name.
|
||||
|
||||
170
CHANGELOG.rst
170
CHANGELOG.rst
@@ -3,6 +3,168 @@
|
||||
Changelog
|
||||
---------
|
||||
|
||||
Unreleased
|
||||
++++++++++
|
||||
|
||||
0.18.0 (2020-12-25)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Dropped Python 2 and Python 3.5
|
||||
- Using ``pathlib.Path()`` as an output instead of ``str`` in most places:
|
||||
- ``Project.path``
|
||||
- ``Script.path``
|
||||
- ``Definition.module_path``
|
||||
- ``Refactoring.get_renames``
|
||||
- ``Refactoring.get_changed_files``
|
||||
- Functions with ``@property`` now return ``property`` instead of ``function``
|
||||
in ``Name().type``
|
||||
- Started using annotations
|
||||
- Better support for the walrus operator
|
||||
- Project attributes are now read accessible
|
||||
- Removed all deprecations
|
||||
|
||||
This is likely going to be the last minor release before 1.0.
|
||||
|
||||
0.17.2 (2020-07-17)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Added an option to pass environment variables to ``Environment``
|
||||
- ``Project(...).path`` exists now
|
||||
- Support for Python 3.9
|
||||
- A few bugfixes
|
||||
|
||||
This will be the last release that supports Python 2 and Python 3.5.
|
||||
``0.18.0`` will be Python 3.6+.
|
||||
|
||||
0.17.1 (2020-06-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Django ``Model`` meta class support
|
||||
- Django Manager support (completion on Managers/QuerySets)
|
||||
- Added Django Stubs to Jedi, thanks to all contributors of the
|
||||
`Django Stubs <https://github.com/typeddjango/django-stubs>`_ project
|
||||
- Added ``SyntaxError.get_message``
|
||||
- Python 3.9 support
|
||||
- Bugfixes (mostly towards Generics)
|
||||
|
||||
0.17.0 (2020-04-14)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Added ``Project`` support. This allows a user to specify which folders Jedi
|
||||
should work with.
|
||||
- Added support for Refactoring. The following refactorings have been
|
||||
implemented: ``Script.rename``, ``Script.inline``,
|
||||
``Script.extract_variable`` and ``Script.extract_function``.
|
||||
- Added ``Script.get_syntax_errors`` to display syntax errors in the current
|
||||
script.
|
||||
- Added code search capabilities both for individual files and projects. The
|
||||
new functions are ``Project.search``, ``Project.complete_search``,
|
||||
``Script.search`` and ``Script.complete_search``.
|
||||
- Added ``Script.help`` to make it easier to display a help window to people.
|
||||
Now returns pydoc information as well for Python keywords/operators. This
|
||||
means that on the class keyword it will now return the docstring of Python's
|
||||
builtin function ``help('class')``.
|
||||
- The API documentation is now way more readable and complete. Check it out
|
||||
under https://jedi.readthedocs.io. A lot of it has been rewritten.
|
||||
- Removed Python 3.4 support
|
||||
- Many bugfixes
|
||||
|
||||
This is likely going to be the last minor version that supports Python 2 and
|
||||
Python3.5. Bugfixes will be provided in 0.17.1+. The next minor/major version
|
||||
will probably be Jedi 1.0.0.
|
||||
|
||||
0.16.0 (2020-01-26)
|
||||
+++++++++++++++++++
|
||||
|
||||
- **Added** ``Script.get_context`` to get information where you currently are.
|
||||
- Completions/type inference of **Pytest fixtures**.
|
||||
- Tensorflow, Numpy and Pandas completions should now be about **4-10x faster**
|
||||
after the first time they are used.
|
||||
- Dict key completions are working now. e.g. ``d = {1000: 3}; d[10`` will
|
||||
expand to ``1000``.
|
||||
- Completion for "proxies" works now. These are classes that have a
|
||||
``__getattr__(self, name)`` method that does a ``return getattr(x, name)``.
|
||||
after loading them initially.
|
||||
- Goto on a function/attribute in a class now goes to the definition in its
|
||||
super class.
|
||||
- Big **Script API Changes**:
|
||||
- The line and column parameters of ``jedi.Script`` are now deprecated
|
||||
- ``completions`` deprecated, use ``complete`` instead
|
||||
- ``goto_assignments`` deprecated, use ``goto`` instead
|
||||
- ``goto_definitions`` deprecated, use ``infer`` instead
|
||||
- ``call_signatures`` deprecated, use ``get_signatures`` instead
|
||||
- ``usages`` deprecated, use ``get_references`` instead
|
||||
- ``jedi.names`` deprecated, use ``jedi.Script(...).get_names()``
|
||||
- ``BaseName.goto_assignments`` renamed to ``BaseName.goto``
|
||||
- Add follow_imports to ``Name.goto``. Now its signature matches
|
||||
``Script.goto``.
|
||||
- **Python 2 support deprecated**. For this release it is best effort. Python 2
|
||||
has reached the end of its life and now it's just about a smooth transition.
|
||||
Bugs for Python 2 will not be fixed anymore and a third of the tests are
|
||||
already skipped.
|
||||
- Removed ``settings.no_completion_duplicates``. It wasn't tested and nobody
|
||||
was probably using it anyway.
|
||||
- Removed ``settings.use_filesystem_cache`` and
|
||||
``settings.additional_dynamic_modules``, they have no usage anymore. Pretty
|
||||
much nobody was probably using them.
|
||||
|
||||
0.15.2 (2019-12-20)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Signatures are now detected a lot better
|
||||
- Add fuzzy completions with ``Script(...).completions(fuzzy=True)``
|
||||
- Files bigger than one MB (about 20kLOC) get cropped to avoid getting
|
||||
stuck completely.
|
||||
- Many small Bugfixes
|
||||
- A big refactoring around contexts/values
|
||||
|
||||
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`` now:
|
||||
``path``. 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:
|
||||
|
||||
- ``Name.get_signatures() -> List[Signature]``. Signatures are similar to
|
||||
``CallSignature``. ``Name.params`` is therefore deprecated.
|
||||
- ``Signature.to_string()`` to format signatures.
|
||||
- ``Signature.params -> List[ParamName]``, ParamName has the
|
||||
following additional attributes ``infer_default()``, ``infer_annotation()``,
|
||||
``to_string()``, and ``kind``.
|
||||
- ``Name.execute() -> List[Name]``, 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)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Added ``goto_*(prefer_stubs=True)`` as well as ``goto_*(prefer_stubs=True)``
|
||||
- Stubs are used now for type inference
|
||||
- Typeshed is used for better type inference
|
||||
- Reworked Name.full_name, should have more correct return values
|
||||
|
||||
0.13.3 (2019-02-24)
|
||||
+++++++++++++++++++
|
||||
|
||||
- Fixed an issue with embedded Python, see https://github.com/davidhalter/jedi-vim/issues/870
|
||||
|
||||
0.13.2 (2018-12-15)
|
||||
+++++++++++++++++++
|
||||
|
||||
@@ -76,7 +238,7 @@ Changelog
|
||||
- Actual semantic completions for the complete Python syntax.
|
||||
- Basic type inference for ``yield from`` PEP 380.
|
||||
- PEP 484 support (most of the important features of it). Thanks Claude! (@reinhrst)
|
||||
- Added ``get_line_code`` to ``Definition`` and ``Completion`` objects.
|
||||
- Added ``get_line_code`` to ``Name`` and ``Completion`` objects.
|
||||
- Completely rewritten the type inference engine.
|
||||
- A new and better parser for (fast) parsing diffs of Python code.
|
||||
|
||||
@@ -84,12 +246,12 @@ Changelog
|
||||
++++++++++++++++++
|
||||
|
||||
- The import logic has been rewritten to look more like Python's. There is now
|
||||
an ``Evaluator.modules`` import cache, which resembles ``sys.modules``.
|
||||
an ``InferState.modules`` import cache, which resembles ``sys.modules``.
|
||||
- Integrated the parser of 2to3. This will make refactoring possible. It will
|
||||
also be possible to check for error messages (like compiling an AST would give)
|
||||
in the future.
|
||||
- With the new parser, the evaluation also completely changed. It's now simpler
|
||||
and more readable.
|
||||
- With the new parser, the type inference also completely changed. It's now
|
||||
simpler and more readable.
|
||||
- Completely rewritten REPL completion.
|
||||
- Added ``jedi.names``, a command to do static analysis. Thanks to that
|
||||
sourcegraph guys for sponsoring this!
|
||||
|
||||
@@ -6,10 +6,10 @@ include .coveragerc
|
||||
include sith.py
|
||||
include conftest.py
|
||||
include pytest.ini
|
||||
include tox.ini
|
||||
include requirements.txt
|
||||
include jedi/evaluate/compiled/fake/*.pym
|
||||
include jedi/parser/python/grammar*.txt
|
||||
recursive-include jedi/third_party *.pyi
|
||||
include jedi/third_party/typeshed/LICENSE
|
||||
include jedi/third_party/django-stubs/LICENSE.txt
|
||||
include jedi/third_party/typeshed/README
|
||||
recursive-include test *
|
||||
recursive-include docs *
|
||||
recursive-exclude * *.pyc
|
||||
|
||||
199
README.rst
199
README.rst
@@ -1,14 +1,14 @@
|
||||
###################################################################
|
||||
Jedi - an awesome autocompletion/static analysis library for Python
|
||||
###################################################################
|
||||
####################################################################################
|
||||
Jedi - an awesome autocompletion, static analysis and refactoring library for Python
|
||||
####################################################################################
|
||||
|
||||
.. image:: https://img.shields.io/pypi/v/jedi.svg?style=flat
|
||||
:target: https://pypi.python.org/pypi/jedi
|
||||
:alt: PyPI version
|
||||
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
|
||||
:target: https://github.com/davidhalter/jedi/issues
|
||||
:alt: The percentage of open issues and pull requests
|
||||
|
||||
.. image:: https://img.shields.io/pypi/pyversions/jedi.svg
|
||||
:target: https://pypi.python.org/pypi/jedi
|
||||
:alt: Supported Python versions
|
||||
.. image:: http://isitmaintained.com/badge/resolution/davidhalter/jedi.svg
|
||||
:target: https://github.com/davidhalter/jedi/issues
|
||||
:alt: The resolution time is the median time an issue or pull request stays open.
|
||||
|
||||
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master
|
||||
:target: https://travis-ci.org/davidhalter/jedi
|
||||
@@ -22,98 +22,93 @@ Jedi - an awesome autocompletion/static analysis library for Python
|
||||
:target: https://coveralls.io/r/davidhalter/jedi
|
||||
:alt: Coverage status
|
||||
|
||||
|
||||
*If you have specific questions, please add an issue or ask on* `Stack Overflow
|
||||
<https://stackoverflow.com/questions/tagged/python-jedi>`_ *with the label* ``python-jedi``.
|
||||
.. image:: https://pepy.tech/badge/jedi
|
||||
:target: https://pepy.tech/project/jedi
|
||||
:alt: PyPI Downloads
|
||||
|
||||
|
||||
Jedi is a static analysis tool for Python that can be used in IDEs/editors. Its
|
||||
historic focus is autocompletion, but does static analysis for now as well.
|
||||
Jedi is fast and is very well tested. It understands Python on a deeper level
|
||||
than all other static analysis frameworks for Python.
|
||||
Jedi is a static analysis tool for Python that is typically used in
|
||||
IDEs/editors plugins. Jedi has a focus on autocompletion and goto
|
||||
functionality. Other features include refactoring, code search and finding
|
||||
references.
|
||||
|
||||
Jedi has support for two different goto functions. It's possible to search for
|
||||
related names and to list all names in a Python file and infer them. Jedi
|
||||
understands docstrings and you can use Jedi autocompletion in your REPL as
|
||||
well.
|
||||
|
||||
Jedi uses a very simple API to connect with IDEs. There's a reference
|
||||
implementation as a `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_,
|
||||
which uses Jedi's autocompletion. We encourage you to use Jedi in your IDEs.
|
||||
It's really easy.
|
||||
Jedi has a simple API to work with. There is a reference implementation as a
|
||||
`VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
|
||||
REPL is also possible, IPython uses it natively and for the CPython REPL you
|
||||
can install it. Jedi is well tested and bugs should be rare.
|
||||
|
||||
Jedi can currently be used with the following editors/projects:
|
||||
|
||||
- Vim (jedi-vim_, YouCompleteMe_, deoplete-jedi_, completor.vim_)
|
||||
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_)
|
||||
- Emacs (Jedi.el_, company-mode_, elpy_, anaconda-mode_, ycmd_)
|
||||
- Sublime Text (SublimeJEDI_ [ST2 + ST3], anaconda_ [only ST3])
|
||||
- TextMate_ (Not sure if it's actually working)
|
||||
- Kate_ version 4.13+ supports it natively, you have to enable it, though. [`proof
|
||||
- Kate_ version 4.13+ supports it natively, you have to enable it, though. [`see
|
||||
<https://projects.kde.org/projects/kde/applications/kate/repository/show?rev=KDE%2F4.13>`_]
|
||||
- Atom_ (autocomplete-python-jedi_)
|
||||
- `GNOME Builder`_ (with support for GObject Introspection)
|
||||
- `Visual Studio Code`_ (via `Python Extension <https://marketplace.visualstudio.com/items?itemName=ms-python.python>`_)
|
||||
- Gedit (gedi_)
|
||||
- wdb_ - Web Debugger
|
||||
- `Eric IDE`_ (Available as a plugin)
|
||||
- `IPython 6.0.0+ <https://ipython.readthedocs.io/en/stable/whatsnew/version6.html>`_
|
||||
- `xonsh shell <https://xon.sh/contents.html>`_ has `jedi extension <https://xon.sh/xontribs.html#jedi>`_
|
||||
|
||||
and many more!
|
||||
|
||||
There are a few language servers that use Jedi:
|
||||
|
||||
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
|
||||
- `python-language-server <https://github.com/palantir/python-language-server>`_
|
||||
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
|
||||
|
||||
Here are some pictures taken from jedi-vim_:
|
||||
|
||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_complete.png
|
||||
|
||||
Completion for almost anything (Ctrl+Space).
|
||||
Completion for almost anything:
|
||||
|
||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_function.png
|
||||
|
||||
Display of function/class bodies, docstrings.
|
||||
Documentation:
|
||||
|
||||
.. image:: https://github.com/davidhalter/jedi/raw/master/docs/_screenshots/screenshot_pydoc.png
|
||||
|
||||
Pydoc support (Shift+k).
|
||||
|
||||
There is also support for goto and renaming.
|
||||
|
||||
Get the latest version from `github <https://github.com/davidhalter/jedi>`_
|
||||
(master branch should always be kind of stable/working).
|
||||
|
||||
Docs are available at `https://jedi.readthedocs.org/en/latest/
|
||||
<https://jedi.readthedocs.org/en/latest/>`_. Pull requests with documentation
|
||||
enhancements and/or fixes are awesome and most welcome. Jedi uses `semantic
|
||||
versioning <https://semver.org/>`_.
|
||||
<https://jedi.readthedocs.org/en/latest/>`_. Pull requests with enhancements
|
||||
and/or fixes are awesome and most welcome. Jedi uses `semantic versioning
|
||||
<https://semver.org/>`_.
|
||||
|
||||
If you want to stay up-to-date (News / RFCs), please subscribe to this `github
|
||||
thread <https://github.com/davidhalter/jedi/issues/1063>`_.:
|
||||
If you want to stay **up-to-date** with releases, please **subscribe** to this
|
||||
mailing list: https://groups.google.com/g/jedi-announce. To subscribe you can
|
||||
simply send an empty email to ``jedi-announce+subscribe@googlegroups.com``.
|
||||
|
||||
Issues & Questions
|
||||
==================
|
||||
|
||||
You can file issues and questions in the `issue tracker
|
||||
<https://github.com/davidhalter/jedi/>`. Alternatively you can also ask on
|
||||
`Stack Overflow <https://stackoverflow.com/questions/tagged/python-jedi>`_ with
|
||||
the label ``python-jedi``.
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
pip install jedi
|
||||
`Check out the docs <https://jedi.readthedocs.org/en/latest/docs/installation.html>`_.
|
||||
|
||||
Note: This just installs the Jedi library, not the editor plugins. For
|
||||
information about how to make it work with your editor, refer to the
|
||||
corresponding documentation.
|
||||
Features and Limitations
|
||||
========================
|
||||
|
||||
You don't want to use ``pip``? Please refer to the `manual
|
||||
<https://jedi.readthedocs.org/en/latest/docs/installation.html>`_.
|
||||
Jedi's features are listed here:
|
||||
`Features <https://jedi.readthedocs.org/en/latest/docs/features.html>`_.
|
||||
|
||||
|
||||
Feature Support and Caveats
|
||||
===========================
|
||||
|
||||
Jedi really understands your Python code. For a comprehensive list what Jedi
|
||||
understands, see: `Features
|
||||
<https://jedi.readthedocs.org/en/latest/docs/features.html>`_. A list of
|
||||
caveats can be found on the same page.
|
||||
|
||||
You can run Jedi on CPython 2.7 or 3.4+ but it should also
|
||||
understand/parse code older than those versions. Additionally you should be able
|
||||
to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
|
||||
You can run Jedi on Python 3.6+ but it should also
|
||||
understand code that is older than those versions. Additionally you should be
|
||||
able to use `Virtualenvs <https://jedi.readthedocs.org/en/latest/docs/api.html#environments>`_
|
||||
very well.
|
||||
|
||||
Tips on how to use Jedi efficiently can be found `here
|
||||
@@ -122,51 +117,62 @@ Tips on how to use Jedi efficiently can be found `here
|
||||
API
|
||||
---
|
||||
|
||||
You can find the documentation for the `API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_.
|
||||
You can find a comprehensive documentation for the
|
||||
`API here <https://jedi.readthedocs.org/en/latest/docs/api.html>`_.
|
||||
|
||||
Autocompletion / Goto / Documentation
|
||||
-------------------------------------
|
||||
|
||||
Autocompletion / Goto / Pydoc
|
||||
-----------------------------
|
||||
There are the following commands:
|
||||
|
||||
Please check the API for a good explanation. There are the following commands:
|
||||
|
||||
- ``jedi.Script.goto_assignments``
|
||||
- ``jedi.Script.completions``
|
||||
- ``jedi.Script.usages``
|
||||
|
||||
The returned objects are very powerful and really all you might need.
|
||||
- ``jedi.Script.goto``
|
||||
- ``jedi.Script.infer``
|
||||
- ``jedi.Script.help``
|
||||
- ``jedi.Script.complete``
|
||||
- ``jedi.Script.get_references``
|
||||
- ``jedi.Script.get_signatures``
|
||||
- ``jedi.Script.get_context``
|
||||
|
||||
The returned objects are very powerful and are really all you might need.
|
||||
|
||||
Autocompletion in your REPL (IPython, etc.)
|
||||
-------------------------------------------
|
||||
|
||||
Starting with IPython `6.0.0` Jedi is a dependency of IPython. Autocompletion
|
||||
in IPython is therefore possible without additional configuration.
|
||||
Jedi is a dependency of IPython. Autocompletion in IPython with Jedi is
|
||||
therefore possible without additional configuration.
|
||||
|
||||
It's possible to have Jedi autocompletion in REPL modes - `example video <https://vimeo.com/122332037>`_.
|
||||
This means that in Python you can enable tab completion in a `REPL
|
||||
Here is an `example video <https://vimeo.com/122332037>`_ how REPL completion
|
||||
can look like.
|
||||
For the ``python`` shell you can enable tab completion in a `REPL
|
||||
<https://jedi.readthedocs.org/en/latest/docs/usage.html#tab-completion-in-the-python-shell>`_.
|
||||
|
||||
Static Analysis
|
||||
---------------
|
||||
|
||||
Static Analysis / Linter
|
||||
------------------------
|
||||
|
||||
To do all forms of static analysis, please try to use ``jedi.names``. It will
|
||||
return a list of names that you can use to infer types and so on.
|
||||
|
||||
Linting is another thing that is going to be part of Jedi. For now you can try
|
||||
an alpha version ``python -m jedi linter``. The API might change though and
|
||||
it's still buggy. It's Jedi's goal to be smarter than classic linter and
|
||||
understand ``AttributeError`` and other code issues.
|
||||
For a lot of forms of static analysis, you can try to use
|
||||
``jedi.Script(...).get_names``. It will return a list of names that you can
|
||||
then filter and work with. There is also a way to list the syntax errors in a
|
||||
file: ``jedi.Script.get_syntax_errors``.
|
||||
|
||||
|
||||
Refactoring
|
||||
-----------
|
||||
|
||||
Jedi's parser would support refactoring, but there's no API to use it right
|
||||
now. If you're interested in helping out here, let me know. With the latest
|
||||
parser changes, it should be very easy to actually make it work.
|
||||
Jedi supports the following refactorings:
|
||||
|
||||
- ``jedi.Script.inline``
|
||||
- ``jedi.Script.rename``
|
||||
- ``jedi.Script.extract_function``
|
||||
- ``jedi.Script.extract_variable``
|
||||
|
||||
Code Search
|
||||
-----------
|
||||
|
||||
There is support for module search with ``jedi.Script.search``, and project
|
||||
search for ``jedi.Project.search``. The way to search is either by providing a
|
||||
name like ``foo`` or by using dotted syntax like ``foo.bar``. Additionally you
|
||||
can provide the API type like ``class foo.bar.Bar``. There are also the
|
||||
functions ``jedi.Script.complete_search`` and ``jedi.Project.complete_search``.
|
||||
|
||||
Development
|
||||
===========
|
||||
@@ -174,43 +180,30 @@ Development
|
||||
There's a pretty good and extensive `development documentation
|
||||
<https://jedi.readthedocs.org/en/latest/docs/development.html>`_.
|
||||
|
||||
|
||||
Testing
|
||||
=======
|
||||
|
||||
The test suite depends on ``tox`` and ``pytest``::
|
||||
The test suite uses ``pytest``::
|
||||
|
||||
pip install tox pytest
|
||||
pip install pytest
|
||||
|
||||
To run the tests for all supported Python versions::
|
||||
If you want to test only a specific Python version (e.g. Python 3.8), it is as
|
||||
easy as::
|
||||
|
||||
tox
|
||||
|
||||
If you want to test only a specific Python version (e.g. Python 2.7), it's as
|
||||
easy as ::
|
||||
|
||||
tox -e py27
|
||||
|
||||
Tests are also run automatically on `Travis CI
|
||||
<https://travis-ci.org/davidhalter/jedi/>`_.
|
||||
python3.8 -m pytest
|
||||
|
||||
For more detailed information visit the `testing documentation
|
||||
<https://jedi.readthedocs.org/en/latest/docs/testing.html>`_.
|
||||
|
||||
|
||||
Acknowledgements
|
||||
================
|
||||
|
||||
- Takafumi Arakaki (@tkf) for creating a solid test environment and a lot of
|
||||
other things.
|
||||
- Danilo Bargen (@dbrgn) for general housekeeping and being a good friend :).
|
||||
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
|
||||
(originally used in lib2to3).
|
||||
|
||||
Thanks a lot to all the
|
||||
`contributors <https://jedi.readthedocs.org/en/latest/docs/acknowledgements.html>`_!
|
||||
|
||||
|
||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||
.. _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
|
||||
|
||||
59
appveyor.yml
59
appveyor.yml
@@ -1,58 +1,17 @@
|
||||
environment:
|
||||
matrix:
|
||||
- TOXENV: py27
|
||||
PYTHON_PATH: C:\Python27
|
||||
JEDI_TEST_ENVIRONMENT: 27
|
||||
- TOXENV: py27
|
||||
PYTHON_PATH: C:\Python27
|
||||
JEDI_TEST_ENVIRONMENT: 34
|
||||
- TOXENV: py27
|
||||
PYTHON_PATH: C:\Python27
|
||||
JEDI_TEST_ENVIRONMENT: 35
|
||||
- TOXENV: py27
|
||||
PYTHON_PATH: C:\Python27
|
||||
- PYTHON_PATH: C:\Python37
|
||||
JEDI_TEST_ENVIRONMENT: 37
|
||||
- PYTHON_PATH: C:\Python37
|
||||
JEDI_TEST_ENVIRONMENT: 36
|
||||
|
||||
- TOXENV: py34
|
||||
PYTHON_PATH: C:\Python34
|
||||
JEDI_TEST_ENVIRONMENT: 27
|
||||
- TOXENV: py34
|
||||
PYTHON_PATH: C:\Python34
|
||||
JEDI_TEST_ENVIRONMENT: 34
|
||||
- TOXENV: py34
|
||||
PYTHON_PATH: C:\Python34
|
||||
JEDI_TEST_ENVIRONMENT: 35
|
||||
- TOXENV: py34
|
||||
PYTHON_PATH: C:\Python34
|
||||
JEDI_TEST_ENVIRONMENT: 36
|
||||
|
||||
- TOXENV: py35
|
||||
PYTHON_PATH: C:\Python35
|
||||
JEDI_TEST_ENVIRONMENT: 27
|
||||
- TOXENV: py35
|
||||
PYTHON_PATH: C:\Python35
|
||||
JEDI_TEST_ENVIRONMENT: 34
|
||||
- TOXENV: py35
|
||||
PYTHON_PATH: C:\Python35
|
||||
JEDI_TEST_ENVIRONMENT: 35
|
||||
- TOXENV: py35
|
||||
PYTHON_PATH: C:\Python35
|
||||
JEDI_TEST_ENVIRONMENT: 36
|
||||
|
||||
- TOXENV: py36
|
||||
PYTHON_PATH: C:\Python36
|
||||
JEDI_TEST_ENVIRONMENT: 27
|
||||
- TOXENV: py36
|
||||
PYTHON_PATH: C:\Python36
|
||||
JEDI_TEST_ENVIRONMENT: 34
|
||||
- TOXENV: py36
|
||||
PYTHON_PATH: C:\Python36
|
||||
JEDI_TEST_ENVIRONMENT: 35
|
||||
- TOXENV: py36
|
||||
PYTHON_PATH: C:\Python36
|
||||
- PYTHON_PATH: C:\Python36
|
||||
JEDI_TEST_ENVIRONMENT: 37
|
||||
- PYTHON_PATH: C:\Python36
|
||||
JEDI_TEST_ENVIRONMENT: 36
|
||||
install:
|
||||
- git submodule update --init --recursive
|
||||
- set PATH=%PYTHON_PATH%;%PYTHON_PATH%\Scripts;%PATH%
|
||||
- pip install tox
|
||||
- pip install .[testing]
|
||||
build_script:
|
||||
- tox
|
||||
- pytest
|
||||
|
||||
76
conftest.py
76
conftest.py
@@ -1,20 +1,22 @@
|
||||
import tempfile
|
||||
import shutil
|
||||
import os
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
import pytest
|
||||
|
||||
import jedi
|
||||
from jedi.api.environment import get_system_environment, InterpreterEnvironment
|
||||
from jedi._compatibility import py_version
|
||||
from test.helpers import test_dir
|
||||
|
||||
collect_ignore = [
|
||||
'setup.py',
|
||||
'__main__.py',
|
||||
'jedi/evaluate/compiled/subprocess/__main__.py',
|
||||
'jedi/__main__.py',
|
||||
'jedi/inference/compiled/subprocess/__main__.py',
|
||||
'build/',
|
||||
'test/examples',
|
||||
'sith.py',
|
||||
]
|
||||
|
||||
|
||||
@@ -22,7 +24,7 @@ collect_ignore = [
|
||||
# to modify `jedi.settings.cache_directory` because `clean_jedi_cache`
|
||||
# has no effect during doctests. Without these hooks, doctests uses
|
||||
# user's cache (e.g., ~/.cache/jedi/). We should remove this
|
||||
# workaround once the problem is fixed in py.test.
|
||||
# workaround once the problem is fixed in pytest.
|
||||
#
|
||||
# See:
|
||||
# - https://github.com/davidhalter/jedi/pull/168
|
||||
@@ -40,7 +42,7 @@ def pytest_addoption(parser):
|
||||
help="Warnings are treated as errors.")
|
||||
|
||||
parser.addoption("--env", action='store',
|
||||
help="Execute the tests in that environment (e.g. 35 for python3.5).")
|
||||
help="Execute the tests in that environment (e.g. 39 for python3.9).")
|
||||
parser.addoption("--interpreter-env", "-I", action='store_true',
|
||||
help="Don't use subprocesses to guarantee having safe "
|
||||
"code execution. Useful for debugging.")
|
||||
@@ -90,12 +92,13 @@ def clean_jedi_cache(request):
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def environment(request):
|
||||
if request.config.option.interpreter_env:
|
||||
return InterpreterEnvironment()
|
||||
|
||||
version = request.config.option.env
|
||||
if version is None:
|
||||
version = os.environ.get('JEDI_TEST_ENVIRONMENT', str(py_version))
|
||||
v = str(sys.version_info[0]) + str(sys.version_info[1])
|
||||
version = os.environ.get('JEDI_TEST_ENVIRONMENT', v)
|
||||
|
||||
if request.config.option.interpreter_env or version == 'interpreter':
|
||||
return InterpreterEnvironment()
|
||||
|
||||
return get_system_environment(version[0] + '.' + version[1:])
|
||||
|
||||
@@ -106,16 +109,57 @@ def Script(environment):
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def has_typing(environment):
|
||||
if environment.version_info >= (3, 5, 0):
|
||||
# This if is just needed to avoid that tests ever skip way more than
|
||||
# they should for all Python versions.
|
||||
return True
|
||||
def ScriptWithProject(Script):
|
||||
project = jedi.Project(test_dir)
|
||||
return partial(jedi.Script, project=project)
|
||||
|
||||
script = jedi.Script('import typing', environment=environment)
|
||||
return bool(script.goto_definitions())
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def get_names(Script):
|
||||
return lambda code, **kwargs: Script(code).get_names(**kwargs)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', params=['goto', 'infer'])
|
||||
def goto_or_infer(request, Script):
|
||||
return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', params=['goto', 'help'])
|
||||
def goto_or_help(request, Script):
|
||||
return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session', params=['goto', 'help', 'infer'])
|
||||
def goto_or_help_or_infer(request, Script):
|
||||
def do(code, *args, **kwargs):
|
||||
return getattr(Script(code), request.param)(*args, **kwargs)
|
||||
|
||||
do.type = request.param
|
||||
return do
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def has_django(environment):
|
||||
script = jedi.Script('import django', environment=environment)
|
||||
return bool(script.infer())
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def jedi_path():
|
||||
return os.path.dirname(__file__)
|
||||
|
||||
|
||||
@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()
|
||||
|
||||
@@ -21,12 +21,13 @@ rm -rf $PROJECT_NAME
|
||||
git clone .. $PROJECT_NAME
|
||||
cd $PROJECT_NAME
|
||||
git checkout $BRANCH
|
||||
git submodule update --init
|
||||
|
||||
# Test first.
|
||||
tox
|
||||
pytest
|
||||
|
||||
# Create tag
|
||||
tag=v$(python -c "import $PROJECT_NAME; print($PROJECT_NAME.__version__)")
|
||||
tag=v$(python3 -c "import $PROJECT_NAME; print($PROJECT_NAME.__version__)")
|
||||
|
||||
master_ref=$(git show-ref -s heads/$BRANCH)
|
||||
tag_ref=$(git show-ref -s $tag || true)
|
||||
@@ -43,7 +44,7 @@ fi
|
||||
# Package and upload to PyPI
|
||||
#rm -rf dist/ - Not needed anymore, because the folder is never reused.
|
||||
echo `pwd`
|
||||
python setup.py sdist bdist_wheel
|
||||
python3 setup.py sdist bdist_wheel
|
||||
# Maybe do a pip install twine before.
|
||||
twine upload dist/*
|
||||
|
||||
|
||||
9
docs/_static/custom_style.css
vendored
Normal file
9
docs/_static/custom_style.css
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
div.version {
|
||||
color: black !important;
|
||||
margin-top: -1.2em !important;
|
||||
margin-bottom: .6em !important;
|
||||
}
|
||||
|
||||
div.wy-side-nav-search {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
37
docs/_themes/flask/LICENSE
vendored
37
docs/_themes/flask/LICENSE
vendored
@@ -1,37 +0,0 @@
|
||||
Copyright (c) 2010 by Armin Ronacher.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Flask and Flask-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
27
docs/_themes/flask/layout.html
vendored
27
docs/_themes/flask/layout.html
vendored
@@ -1,27 +0,0 @@
|
||||
{%- extends "basic/layout.html" %}
|
||||
{%- block extrahead %}
|
||||
{{ super() }}
|
||||
{% if theme_touch_icon %}
|
||||
<link rel="apple-touch-icon" href="{{ pathto('_static/' ~ theme_touch_icon, 1) }}" />
|
||||
{% endif %}
|
||||
<link media="only screen and (max-device-width: 480px)" href="{{
|
||||
pathto('_static/small_flask.css', 1) }}" type= "text/css" rel="stylesheet" />
|
||||
<a href="https://github.com/davidhalter/jedi">
|
||||
<img style="position: absolute; top: 0; right: 0; border: 0;" src="https://s3.amazonaws.com/github/ribbons/forkme_right_red_aa0000.png" alt="Fork me on GitHub">
|
||||
</a>
|
||||
{% endblock %}
|
||||
{%- block relbar2 %}{% endblock %}
|
||||
{% block header %}
|
||||
{{ super() }}
|
||||
{% if pagename == 'index' %}
|
||||
<div class=indexwrapper>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{%- block footer %}
|
||||
<div class="footer">
|
||||
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>.
|
||||
</div>
|
||||
{% if pagename == 'index' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{%- endblock %}
|
||||
19
docs/_themes/flask/relations.html
vendored
19
docs/_themes/flask/relations.html
vendored
@@ -1,19 +0,0 @@
|
||||
<h3>Related Topics</h3>
|
||||
<ul>
|
||||
<li><a href="{{ pathto(master_doc) }}">Documentation overview</a><ul>
|
||||
{%- for parent in parents %}
|
||||
<li><a href="{{ parent.link|e }}">{{ parent.title }}</a><ul>
|
||||
{%- endfor %}
|
||||
{%- if prev %}
|
||||
<li>Previous: <a href="{{ prev.link|e }}" title="{{ _('previous chapter')
|
||||
}}">{{ prev.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- if next %}
|
||||
<li>Next: <a href="{{ next.link|e }}" title="{{ _('next chapter')
|
||||
}}">{{ next.title }}</a></li>
|
||||
{%- endif %}
|
||||
{%- for parent in parents %}
|
||||
</ul></li>
|
||||
{%- endfor %}
|
||||
</ul></li>
|
||||
</ul>
|
||||
394
docs/_themes/flask/static/flasky.css_t
vendored
394
docs/_themes/flask/static/flasky.css_t
vendored
@@ -1,394 +0,0 @@
|
||||
/*
|
||||
* flasky.css_t
|
||||
* ~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
{% set page_width = '940px' %}
|
||||
{% set sidebar_width = '220px' %}
|
||||
|
||||
@import url("basic.css");
|
||||
|
||||
/* -- page layout ----------------------------------------------------------- */
|
||||
|
||||
body {
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 17px;
|
||||
background-color: white;
|
||||
color: #000;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: {{ page_width }};
|
||||
margin: 30px auto 0 auto;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0 0 0 {{ sidebar_width }};
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
width: {{ sidebar_width }};
|
||||
}
|
||||
|
||||
hr {
|
||||
border: 1px solid #B1B4B6;
|
||||
}
|
||||
|
||||
div.body {
|
||||
background-color: #ffffff;
|
||||
color: #3E4349;
|
||||
padding: 0 30px 0 30px;
|
||||
}
|
||||
|
||||
img.floatingflask {
|
||||
padding: 0 0 10px 10px;
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
width: {{ page_width }};
|
||||
margin: 20px auto 30px auto;
|
||||
font-size: 14px;
|
||||
color: #888;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div.footer a {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a:hover {
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper {
|
||||
padding: 18px 10px;
|
||||
}
|
||||
|
||||
div.sphinxsidebarwrapper p.logo {
|
||||
padding: 0 0 20px 0;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3,
|
||||
div.sphinxsidebar h4 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
color: #444;
|
||||
font-size: 24px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h4 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3 a {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo a,
|
||||
div.sphinxsidebar h3 a,
|
||||
div.sphinxsidebar p.logo a:hover,
|
||||
div.sphinxsidebar h3 a:hover {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p {
|
||||
color: #555;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
div.sphinxsidebar ul {
|
||||
margin: 10px 0;
|
||||
padding: 0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div.sphinxsidebar input {
|
||||
border: 1px solid #ccc;
|
||||
font-family: 'Georgia', serif;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
/* -- body styles ----------------------------------------------------------- */
|
||||
|
||||
a {
|
||||
color: #004B6B;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #6D4100;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div.body h1,
|
||||
div.body h2,
|
||||
div.body h3,
|
||||
div.body h4,
|
||||
div.body h5,
|
||||
div.body h6 {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
margin: 30px 0px 10px 0px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
{% if theme_index_logo %}
|
||||
div.indexwrapper h1 {
|
||||
text-indent: -999999px;
|
||||
background: url({{ theme_index_logo }}) no-repeat center center;
|
||||
height: {{ theme_index_logo_height }};
|
||||
}
|
||||
{% endif %}
|
||||
|
||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
||||
div.body h2 { font-size: 180%; }
|
||||
div.body h3 { font-size: 150%; }
|
||||
div.body h4 { font-size: 130%; }
|
||||
div.body h5 { font-size: 100%; }
|
||||
div.body h6 { font-size: 100%; }
|
||||
|
||||
a.headerlink {
|
||||
color: #ddd;
|
||||
padding: 0 4px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a.headerlink:hover {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div.body p, div.body dd, div.body li {
|
||||
line-height: 1.4em;
|
||||
}
|
||||
|
||||
div.admonition {
|
||||
background: #fafafa;
|
||||
margin: 20px -30px;
|
||||
padding: 10px 30px;
|
||||
border-top: 1px solid #ccc;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.admonition tt.xref, div.admonition a tt {
|
||||
border-bottom: 1px solid #fafafa;
|
||||
}
|
||||
|
||||
dd div.admonition {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
div.admonition p.admonition-title {
|
||||
font-family: 'Garamond', 'Georgia', serif;
|
||||
font-weight: normal;
|
||||
font-size: 24px;
|
||||
margin: 0 0 10px 0;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
div.admonition p.last {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
div.highlight {
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
dt:target, .highlight {
|
||||
background: #FAF3E8;
|
||||
}
|
||||
|
||||
div.note {
|
||||
background-color: #eee;
|
||||
border: 1px solid #ccc;
|
||||
}
|
||||
|
||||
div.seealso {
|
||||
background-color: #ffc;
|
||||
border: 1px solid #ff6;
|
||||
}
|
||||
|
||||
div.topic {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
p.admonition-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
p.admonition-title:after {
|
||||
content: ":";
|
||||
}
|
||||
|
||||
pre, tt {
|
||||
font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
}
|
||||
|
||||
tt.descname, tt.descclassname {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
tt.descname {
|
||||
padding-right: 0.08em;
|
||||
}
|
||||
|
||||
img.screenshot {
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils {
|
||||
border: 1px solid #888;
|
||||
-moz-box-shadow: 2px 2px 4px #eee;
|
||||
-webkit-box-shadow: 2px 2px 4px #eee;
|
||||
box-shadow: 2px 2px 4px #eee;
|
||||
}
|
||||
|
||||
table.docutils td, table.docutils th {
|
||||
border: 1px solid #888;
|
||||
padding: 0.25em 0.7em;
|
||||
}
|
||||
|
||||
table.field-list, table.footnote {
|
||||
border: none;
|
||||
-moz-box-shadow: none;
|
||||
-webkit-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
table.footnote {
|
||||
margin: 15px 0;
|
||||
width: 100%;
|
||||
border: 1px solid #eee;
|
||||
background: #fdfdfd;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
table.footnote + table.footnote {
|
||||
margin-top: -15px;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table.field-list th {
|
||||
padding: 0 0.8em 0 0;
|
||||
}
|
||||
|
||||
table.field-list td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
table.footnote td.label {
|
||||
width: 0px;
|
||||
padding: 0.3em 0 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.footnote td {
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
dl {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
dl dd {
|
||||
margin-left: 30px;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 0 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin: 10px 0 10px 30px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #eee;
|
||||
padding: 7px 30px;
|
||||
margin: 15px -30px;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
|
||||
dl pre, blockquote pre, li pre {
|
||||
margin-left: -60px;
|
||||
padding-left: 60px;
|
||||
}
|
||||
|
||||
dl dl pre {
|
||||
margin-left: -90px;
|
||||
padding-left: 90px;
|
||||
}
|
||||
|
||||
tt {
|
||||
background-color: #ecf0f3;
|
||||
color: #222;
|
||||
/* padding: 1px 2px; */
|
||||
}
|
||||
|
||||
tt.xref, a tt {
|
||||
background-color: #FBFBFB;
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
|
||||
a.reference {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a.footnote-reference {
|
||||
text-decoration: none;
|
||||
font-size: 0.7em;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px dotted #004B6B;
|
||||
}
|
||||
|
||||
a.footnote-reference:hover {
|
||||
border-bottom: 1px solid #6D4100;
|
||||
}
|
||||
|
||||
a:hover tt {
|
||||
background: #EEE;
|
||||
}
|
||||
70
docs/_themes/flask/static/small_flask.css
vendored
70
docs/_themes/flask/static/small_flask.css
vendored
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* small_flask.css_t
|
||||
* ~~~~~~~~~~~~~~~~~
|
||||
*
|
||||
* :copyright: Copyright 2010 by Armin Ronacher.
|
||||
* :license: Flask Design License, see LICENSE for details.
|
||||
*/
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px 30px;
|
||||
}
|
||||
|
||||
div.documentwrapper {
|
||||
float: none;
|
||||
background: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar {
|
||||
display: block;
|
||||
float: none;
|
||||
width: 102.5%;
|
||||
margin: 50px -30px -20px -30px;
|
||||
padding: 10px 20px;
|
||||
background: #333;
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
||||
div.sphinxsidebar h3 a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
div.sphinxsidebar a {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
div.sphinxsidebar p.logo {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.document {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.related {
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 10px 0 20px 0;
|
||||
}
|
||||
|
||||
div.related ul,
|
||||
div.related ul li {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div.footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
div.bodywrapper {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
div.body {
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
9
docs/_themes/flask/theme.conf
vendored
9
docs/_themes/flask/theme.conf
vendored
@@ -1,9 +0,0 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = flasky.css
|
||||
pygments_style = flask_theme_support.FlaskyStyle
|
||||
|
||||
[options]
|
||||
index_logo =
|
||||
index_logo_height = 120px
|
||||
touch_icon =
|
||||
125
docs/_themes/flask_theme_support.py
vendored
125
docs/_themes/flask_theme_support.py
vendored
@@ -1,125 +0,0 @@
|
||||
"""
|
||||
Copyright (c) 2010 by Armin Ronacher.
|
||||
|
||||
Some rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms of the theme, with or
|
||||
without modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* The names of the contributors may not be used to endorse or
|
||||
promote products derived from this software without specific
|
||||
prior written permission.
|
||||
|
||||
We kindly ask you to only use these themes in an unmodified manner just
|
||||
for Flask and Flask-related products, not for unrelated projects. If you
|
||||
like the visual style and want to use it for your own projects, please
|
||||
consider making some larger changes to the themes (such as changing
|
||||
font faces, sizes, colors or margins).
|
||||
|
||||
THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
# flasky extensions. flasky pygments style based on tango style
|
||||
from pygments.style import Style
|
||||
from pygments.token import Keyword, Name, Comment, String, Error, \
|
||||
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
|
||||
|
||||
|
||||
class FlaskyStyle(Style):
|
||||
background_color = "#f8f8f8"
|
||||
default_style = ""
|
||||
|
||||
styles = {
|
||||
# No corresponding class for the following:
|
||||
#Text: "", # class: ''
|
||||
Whitespace: "underline #f8f8f8", # class: 'w'
|
||||
Error: "#a40000 border:#ef2929", # class: 'err'
|
||||
Other: "#000000", # class 'x'
|
||||
|
||||
Comment: "italic #8f5902", # class: 'c'
|
||||
Comment.Preproc: "noitalic", # class: 'cp'
|
||||
|
||||
Keyword: "bold #004461", # class: 'k'
|
||||
Keyword.Constant: "bold #004461", # class: 'kc'
|
||||
Keyword.Declaration: "bold #004461", # class: 'kd'
|
||||
Keyword.Namespace: "bold #004461", # class: 'kn'
|
||||
Keyword.Pseudo: "bold #004461", # class: 'kp'
|
||||
Keyword.Reserved: "bold #004461", # class: 'kr'
|
||||
Keyword.Type: "bold #004461", # class: 'kt'
|
||||
|
||||
Operator: "#582800", # class: 'o'
|
||||
Operator.Word: "bold #004461", # class: 'ow' - like keywords
|
||||
|
||||
Punctuation: "bold #000000", # class: 'p'
|
||||
|
||||
# because special names such as Name.Class, Name.Function, etc.
|
||||
# are not recognized as such later in the parsing, we choose them
|
||||
# to look the same as ordinary variables.
|
||||
Name: "#000000", # class: 'n'
|
||||
Name.Attribute: "#c4a000", # class: 'na' - to be revised
|
||||
Name.Builtin: "#004461", # class: 'nb'
|
||||
Name.Builtin.Pseudo: "#3465a4", # class: 'bp'
|
||||
Name.Class: "#000000", # class: 'nc' - to be revised
|
||||
Name.Constant: "#000000", # class: 'no' - to be revised
|
||||
Name.Decorator: "#888", # class: 'nd' - to be revised
|
||||
Name.Entity: "#ce5c00", # class: 'ni'
|
||||
Name.Exception: "bold #cc0000", # class: 'ne'
|
||||
Name.Function: "#000000", # class: 'nf'
|
||||
Name.Property: "#000000", # class: 'py'
|
||||
Name.Label: "#f57900", # class: 'nl'
|
||||
Name.Namespace: "#000000", # class: 'nn' - to be revised
|
||||
Name.Other: "#000000", # class: 'nx'
|
||||
Name.Tag: "bold #004461", # class: 'nt' - like a keyword
|
||||
Name.Variable: "#000000", # class: 'nv' - to be revised
|
||||
Name.Variable.Class: "#000000", # class: 'vc' - to be revised
|
||||
Name.Variable.Global: "#000000", # class: 'vg' - to be revised
|
||||
Name.Variable.Instance: "#000000", # class: 'vi' - to be revised
|
||||
|
||||
Number: "#990000", # class: 'm'
|
||||
|
||||
Literal: "#000000", # class: 'l'
|
||||
Literal.Date: "#000000", # class: 'ld'
|
||||
|
||||
String: "#4e9a06", # class: 's'
|
||||
String.Backtick: "#4e9a06", # class: 'sb'
|
||||
String.Char: "#4e9a06", # class: 'sc'
|
||||
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
|
||||
String.Double: "#4e9a06", # class: 's2'
|
||||
String.Escape: "#4e9a06", # class: 'se'
|
||||
String.Heredoc: "#4e9a06", # class: 'sh'
|
||||
String.Interpol: "#4e9a06", # class: 'si'
|
||||
String.Other: "#4e9a06", # class: 'sx'
|
||||
String.Regex: "#4e9a06", # class: 'sr'
|
||||
String.Single: "#4e9a06", # class: 's1'
|
||||
String.Symbol: "#4e9a06", # class: 'ss'
|
||||
|
||||
Generic: "#000000", # class: 'g'
|
||||
Generic.Deleted: "#a40000", # class: 'gd'
|
||||
Generic.Emph: "italic #000000", # class: 'ge'
|
||||
Generic.Error: "#ef2929", # class: 'gr'
|
||||
Generic.Heading: "bold #000080", # class: 'gh'
|
||||
Generic.Inserted: "#00A000", # class: 'gi'
|
||||
Generic.Output: "#888", # class: 'go'
|
||||
Generic.Prompt: "#745334", # class: 'gp'
|
||||
Generic.Strong: "bold #000000", # class: 'gs'
|
||||
Generic.Subheading: "bold #800080", # class: 'gu'
|
||||
Generic.Traceback: "bold #a40000", # class: 'gt'
|
||||
}
|
||||
49
docs/conf.py
49
docs/conf.py
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Jedi documentation build configuration file, created by
|
||||
# sphinx-quickstart on Wed Dec 26 00:11:34 2012.
|
||||
#
|
||||
@@ -13,13 +11,11 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
import datetime
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
sys.path.append(os.path.abspath('_themes'))
|
||||
|
||||
# -- General configuration -----------------------------------------------------
|
||||
|
||||
@@ -29,7 +25,8 @@ sys.path.append(os.path.abspath('_themes'))
|
||||
# Add any Sphinx extension module names here, as strings. They can be extensions
|
||||
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo',
|
||||
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram']
|
||||
'sphinx.ext.intersphinx', 'sphinx.ext.inheritance_diagram',
|
||||
'sphinx_rtd_theme', 'sphinx.ext.autosummary']
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
@@ -44,8 +41,8 @@ source_encoding = 'utf-8'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Jedi'
|
||||
copyright = u'jedi contributors'
|
||||
project = 'Jedi'
|
||||
copyright = 'jedi contributors'
|
||||
|
||||
import jedi
|
||||
from jedi.utils import version_info
|
||||
@@ -54,8 +51,8 @@ from jedi.utils import version_info
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = '.'.join(str(x) for x in version_info()[:2])
|
||||
# The short X.Y.Z version.
|
||||
version = '.'.join(str(x) for x in version_info()[:3])
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = jedi.__version__
|
||||
|
||||
@@ -98,12 +95,15 @@ pygments_style = 'sphinx'
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'flask'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
html_theme_options = {
|
||||
'logo_only': True,
|
||||
'style_nav_header_background': 'white',
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
html_theme_path = ['_themes']
|
||||
@@ -117,7 +117,7 @@ html_theme_path = ['_themes']
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
#html_logo = None
|
||||
html_logo = '_static/logo.png'
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
@@ -129,6 +129,8 @@ html_theme_path = ['_themes']
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
html_css_files = ['custom_style.css']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
#html_last_updated_fmt = '%b %d, %Y'
|
||||
@@ -145,7 +147,7 @@ html_sidebars = {
|
||||
#'relations.html',
|
||||
'ghbuttons.html',
|
||||
#'sourcelink.html',
|
||||
#'searchbox.html'
|
||||
'searchbox.html'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -163,13 +165,13 @@ html_sidebars = {
|
||||
#html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
html_show_sourcelink = False
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#html_show_sphinx = True
|
||||
html_show_sphinx = False
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#html_show_copyright = True
|
||||
html_show_copyright = False
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
@@ -201,8 +203,8 @@ latex_elements = {
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Jedi.tex', u'Jedi Documentation',
|
||||
u'Jedi contributors', 'manual'),
|
||||
('index', 'Jedi.tex', 'Jedi Documentation',
|
||||
'Jedi contributors', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
@@ -231,8 +233,8 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'jedi', u'Jedi Documentation',
|
||||
[u'Jedi contributors'], 1)
|
||||
('index', 'jedi', 'Jedi Documentation',
|
||||
['Jedi contributors'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
@@ -245,8 +247,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Jedi', u'Jedi Documentation',
|
||||
u'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.',
|
||||
('index', 'Jedi', 'Jedi Documentation',
|
||||
'Jedi contributors', 'Jedi', 'Awesome Python autocompletion library.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
@@ -274,7 +276,8 @@ autodoc_default_flags = []
|
||||
# -- Options for intersphinx module --------------------------------------------
|
||||
|
||||
intersphinx_mapping = {
|
||||
'https://docs.python.org/': None,
|
||||
'python': ('https://docs.python.org/', None),
|
||||
'parso': ('https://parso.readthedocs.io/en/latest/', None),
|
||||
}
|
||||
|
||||
|
||||
|
||||
66
docs/docs/acknowledgements.rst
Normal file
66
docs/docs/acknowledgements.rst
Normal file
@@ -0,0 +1,66 @@
|
||||
.. include global.rst
|
||||
|
||||
History & Acknowledgements
|
||||
==========================
|
||||
|
||||
Acknowledgements
|
||||
----------------
|
||||
|
||||
- Dave Halter for creating and maintaining Jedi & Parso.
|
||||
- Takafumi Arakaki (@tkf) for creating a solid test environment and a lot of
|
||||
other things.
|
||||
- Danilo Bargen (@dbrgn) for general housekeeping and being a good friend :).
|
||||
- Guido van Rossum (@gvanrossum) for creating the parser generator pgen2
|
||||
(originally used in lib2to3).
|
||||
- Thanks to all the :ref:`contributors <contributors>`.
|
||||
|
||||
A Little Bit of History
|
||||
-----------------------
|
||||
|
||||
Written by Dave.
|
||||
|
||||
The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit
|
||||
of the precognition the Jedi have. There's even an awesome `scene
|
||||
<https://youtu.be/yHRJLIf7wMU>`_ of Monty Python Jedis :-).
|
||||
|
||||
But actually the name has not much to do with Star Wars. It's part of my
|
||||
second name Jedidjah.
|
||||
|
||||
I actually started Jedi back in 2012, because there were no good solutions
|
||||
available for VIM. Most auto-completion solutions just did not work well. The
|
||||
only good solution was PyCharm. But I liked my good old VIM very much. There
|
||||
was also a solution called Rope that did not work at all for me. So I decided
|
||||
to write my own version of a completion engine.
|
||||
|
||||
The first idea was to execute non-dangerous code. But I soon realized, that
|
||||
this would not work. So I started to build a static analysis tool.
|
||||
The biggest problem that I had at the time was that I did not know a thing
|
||||
about parsers.I did not did not even know the word static analysis. It turns
|
||||
out they are the foundation of a good static analysis tool. I of course did not
|
||||
know that and tried to write my own poor version of a parser that I ended up
|
||||
throwing away two years later.
|
||||
|
||||
Because of my lack of knowledge, everything after 2012 and before 2020 was
|
||||
basically refactoring. I rewrote the core parts of Jedi probably like 5-10
|
||||
times. The last big rewrite (that I did twice) was the inclusion of
|
||||
gradual typing and stubs.
|
||||
|
||||
I learned during that time that it is crucial to have a good understanding of
|
||||
your problem. Otherwise you just end up doing it again. I only wrote features
|
||||
in the beginning and in the end. Everything else was bugfixing and refactoring.
|
||||
However now I am really happy with the result. It works well, bugfixes can be
|
||||
quick and is pretty much feature complete.
|
||||
|
||||
--------
|
||||
|
||||
I will leave you with a small annectote that happend in 2012, if I remember
|
||||
correctly. After I explained Guido van Rossum, how some parts of my
|
||||
auto-completion work, he said:
|
||||
|
||||
*"Oh, that worries me..."*
|
||||
|
||||
Now that it is finished, I hope he likes it :-).
|
||||
|
||||
.. _contributors:
|
||||
|
||||
.. include:: ../../AUTHORS.txt
|
||||
@@ -5,6 +5,49 @@
|
||||
API Return Classes
|
||||
------------------
|
||||
|
||||
.. automodule:: jedi.api.classes
|
||||
Abstract Base Class
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
.. autoclass:: jedi.api.classes.BaseName
|
||||
:members:
|
||||
:undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
Name
|
||||
~~~~
|
||||
.. autoclass:: jedi.api.classes.Name
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Completion
|
||||
~~~~~~~~~~
|
||||
.. autoclass:: jedi.api.classes.Completion
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
BaseSignature
|
||||
~~~~~~~~~~~~~
|
||||
.. autoclass:: jedi.api.classes.BaseSignature
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Signature
|
||||
~~~~~~~~~
|
||||
.. autoclass:: jedi.api.classes.Signature
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
ParamName
|
||||
~~~~~~~~~
|
||||
.. autoclass:: jedi.api.classes.ParamName
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Refactoring
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: jedi.api.refactoring.Refactoring
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: jedi.api.errors.SyntaxError
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -3,58 +3,74 @@
|
||||
API Overview
|
||||
============
|
||||
|
||||
.. currentmodule:: jedi
|
||||
|
||||
Note: This documentation is for Plugin developers, who want to improve their
|
||||
editors/IDE autocompletion
|
||||
|
||||
If you want to use |jedi|, you first need to ``import jedi``. You then have
|
||||
direct access to the :class:`.Script`. You can then call the functions
|
||||
documented here. These functions return :ref:`API classes
|
||||
<api-classes>`.
|
||||
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
The deprecation process is as follows:
|
||||
|
||||
1. A deprecation is announced in the next major/minor release.
|
||||
2. We wait either at least a year & at least two minor releases until we remove
|
||||
the deprecated functionality.
|
||||
|
||||
|
||||
API Documentation
|
||||
-----------------
|
||||
|
||||
The API consists of a few different parts:
|
||||
|
||||
- The main starting points for completions/goto: :class:`.Script` and :class:`.Interpreter`
|
||||
- Helpful functions: :func:`.names`, :func:`.preload_module` and
|
||||
:func:`.set_debug_function`
|
||||
- :ref:`API Result Classes <api-classes>`
|
||||
- :ref:`Python Versions/Virtualenv Support <environments>` with functions like
|
||||
:func:`.find_system_environments` and :func:`.find_virtualenvs`
|
||||
.. note:: This documentation is mostly for Plugin developers, who want to
|
||||
improve their editors/IDE with Jedi.
|
||||
|
||||
.. _api:
|
||||
|
||||
Static Analysis Interface
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
The API consists of a few different parts:
|
||||
|
||||
.. automodule:: jedi
|
||||
- The main starting points for complete/goto: :class:`.Script` and
|
||||
:class:`.Interpreter`. If you work with Jedi you want to understand these
|
||||
classes first.
|
||||
- :ref:`API Result Classes <api-classes>`
|
||||
- :ref:`Python Versions/Virtualenv Support <environments>` with functions like
|
||||
:func:`.find_system_environments` and :func:`.find_virtualenvs`
|
||||
- A way to work with different :ref:`Folders / Projects <projects>`
|
||||
- Helpful functions: :func:`.preload_module` and :func:`.set_debug_function`
|
||||
|
||||
The methods that you are most likely going to use to work with Jedi are the
|
||||
following ones:
|
||||
|
||||
.. currentmodule:: jedi
|
||||
|
||||
.. autosummary::
|
||||
:nosignatures:
|
||||
|
||||
Script.complete
|
||||
Script.goto
|
||||
Script.infer
|
||||
Script.help
|
||||
Script.get_signatures
|
||||
Script.get_references
|
||||
Script.get_context
|
||||
Script.get_names
|
||||
Script.get_syntax_errors
|
||||
Script.rename
|
||||
Script.inline
|
||||
Script.extract_variable
|
||||
Script.extract_function
|
||||
Script.search
|
||||
Script.complete_search
|
||||
Project.search
|
||||
Project.complete_search
|
||||
|
||||
Script
|
||||
------
|
||||
|
||||
.. autoclass:: jedi.Script
|
||||
:members:
|
||||
|
||||
Interpreter
|
||||
-----------
|
||||
.. autoclass:: jedi.Interpreter
|
||||
:members:
|
||||
.. autofunction:: jedi.names
|
||||
.. autofunction:: jedi.preload_module
|
||||
.. autofunction:: jedi.set_debug_function
|
||||
|
||||
.. _projects:
|
||||
|
||||
Projects
|
||||
--------
|
||||
|
||||
.. automodule:: jedi.api.project
|
||||
|
||||
.. autofunction:: jedi.get_default_project
|
||||
.. autoclass:: jedi.Project
|
||||
:members:
|
||||
|
||||
.. _environments:
|
||||
|
||||
Environments
|
||||
~~~~~~~~~~~~
|
||||
------------
|
||||
|
||||
.. automodule:: jedi.api.environment
|
||||
|
||||
@@ -67,19 +83,32 @@ Environments
|
||||
.. autoclass:: jedi.api.environment.Environment
|
||||
:members:
|
||||
|
||||
Helper Functions
|
||||
----------------
|
||||
|
||||
.. autofunction:: jedi.preload_module
|
||||
.. autofunction:: jedi.set_debug_function
|
||||
|
||||
Errors
|
||||
------
|
||||
|
||||
.. autoexception:: jedi.InternalError
|
||||
.. autoexception:: jedi.RefactoringError
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Completions:
|
||||
Completions
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''import json; json.l'''
|
||||
>>> script = jedi.Script(source, 1, 19, '')
|
||||
>>> code = '''import json; json.l'''
|
||||
>>> script = jedi.Script(code, path='example.py')
|
||||
>>> script
|
||||
<jedi.api.Script object at 0x2121b10>
|
||||
>>> completions = script.completions()
|
||||
<Script: 'example.py' <SameEnvironment: 3.9.0 in /usr>>
|
||||
>>> completions = script.complete(1, 19)
|
||||
>>> completions
|
||||
[<Completion: load>, <Completion: loads>]
|
||||
>>> completions[1]
|
||||
@@ -89,12 +118,14 @@ Completions:
|
||||
>>> completions[1].name
|
||||
'loads'
|
||||
|
||||
Definitions / Goto:
|
||||
Type Inference / Goto
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''def my_func():
|
||||
>>> code = '''\
|
||||
... def my_func():
|
||||
... print 'called'
|
||||
...
|
||||
... alias = my_func
|
||||
@@ -102,31 +133,41 @@ Definitions / Goto:
|
||||
... inception = my_list[2]
|
||||
...
|
||||
... inception()'''
|
||||
>>> script = jedi.Script(source, 8, 1, '')
|
||||
>>> script = jedi.Script(code)
|
||||
>>>
|
||||
>>> script.goto_assignments()
|
||||
[<Definition inception=my_list[2]>]
|
||||
>>> script.goto(8, 1)
|
||||
[<Name full_name='__main__.inception', description='inception = my_list[2]'>]
|
||||
>>>
|
||||
>>> script.goto_definitions()
|
||||
[<Definition def my_func>]
|
||||
>>> script.infer(8, 1)
|
||||
[<Name full_name='__main__.my_func', description='def my_func'>]
|
||||
|
||||
Related names:
|
||||
References
|
||||
~~~~~~~~~~
|
||||
|
||||
.. sourcecode:: python
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''x = 3
|
||||
>>> code = '''\
|
||||
... x = 3
|
||||
... if 1 == 2:
|
||||
... x = 4
|
||||
... else:
|
||||
... del x'''
|
||||
>>> script = jedi.Script(source, 5, 8, '')
|
||||
>>> rns = script.related_names()
|
||||
>>> script = jedi.Script(code)
|
||||
>>> rns = script.get_references(5, 8)
|
||||
>>> rns
|
||||
[<RelatedName x@3,4>, <RelatedName x@1,0>]
|
||||
>>> rns[0].start_pos
|
||||
(3, 4)
|
||||
>>> rns[0].is_keyword
|
||||
False
|
||||
>>> rns[0].text
|
||||
'x'
|
||||
[<Name full_name='__main__.x', description='x = 3'>,
|
||||
<Name full_name='__main__.x', description='x = 4'>,
|
||||
<Name full_name='__main__.x', description='del x'>]
|
||||
>>> rns[1].line
|
||||
3
|
||||
>>> rns[1].column
|
||||
4
|
||||
|
||||
Deprecations
|
||||
------------
|
||||
|
||||
The deprecation process is as follows:
|
||||
|
||||
1. A deprecation is announced in any release.
|
||||
2. The next major release removes the deprecated functionality.
|
||||
|
||||
1
docs/docs/changelog.rst
Normal file
1
docs/docs/changelog.rst
Normal file
@@ -0,0 +1 @@
|
||||
.. include:: ../../CHANGELOG.rst
|
||||
@@ -8,6 +8,8 @@ Jedi Development
|
||||
.. note:: This documentation is for Jedi developers who want to improve Jedi
|
||||
itself, but have no idea how Jedi works. If you want to use Jedi for
|
||||
your IDE, look at the `plugin api <api.html>`_.
|
||||
It is also important to note that it's a pretty old version and some things
|
||||
might not apply anymore.
|
||||
|
||||
|
||||
Introduction
|
||||
@@ -20,16 +22,12 @@ couldn't get rid of complexity. I know that **simple is better than complex**,
|
||||
but unfortunately it sometimes requires complex solutions to understand complex
|
||||
systems.
|
||||
|
||||
Since most of the Jedi internals have been written by me (David Halter), this
|
||||
introduction will be written mostly by me, because no one else understands to
|
||||
the same level how Jedi works. Actually this is also the reason for exactly this
|
||||
part of the documentation. To make multiple people able to edit the Jedi core.
|
||||
|
||||
In five chapters I'm trying to describe the internals of |jedi|:
|
||||
In six chapters I'm trying to describe the internals of |jedi|:
|
||||
|
||||
- :ref:`The Jedi Core <core>`
|
||||
- :ref:`Core Extensions <core-extensions>`
|
||||
- :ref:`Imports & Modules <imports-modules>`
|
||||
- :ref:`Stubs & Annotations <stubs>`
|
||||
- :ref:`Caching & Recursions <caching-recursions>`
|
||||
- :ref:`Helper modules <dev-helpers>`
|
||||
|
||||
@@ -45,51 +43,51 @@ The Jedi Core
|
||||
The core of Jedi consists of three parts:
|
||||
|
||||
- :ref:`Parser <parser>`
|
||||
- :ref:`Python code evaluation <evaluate>`
|
||||
- :ref:`Python type inference <inference>`
|
||||
- :ref:`API <dev-api>`
|
||||
|
||||
Most people are probably interested in :ref:`code evaluation <evaluate>`,
|
||||
Most people are probably interested in :ref:`type inference <inference>`,
|
||||
because that's where all the magic happens. I need to introduce the :ref:`parser
|
||||
<parser>` first, because :mod:`jedi.evaluate` uses it extensively.
|
||||
<parser>` first, because :mod:`jedi.inference` uses it extensively.
|
||||
|
||||
.. _parser:
|
||||
|
||||
Parser
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Jedi used to have it's internal parser, however this is now a separate project
|
||||
Jedi used to have its internal parser, however this is now a separate project
|
||||
and is called `parso <http://parso.readthedocs.io>`_.
|
||||
|
||||
The parser creates a syntax tree that |jedi| analyses and tries to understand.
|
||||
The grammar that this parsers uses is very similar to the official Python
|
||||
The grammar that this parser uses is very similar to the official Python
|
||||
`grammar files <https://docs.python.org/3/reference/grammar.html>`_.
|
||||
|
||||
.. _evaluate:
|
||||
.. _inference:
|
||||
|
||||
Evaluation of python code (evaluate/__init__.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Type inference of python code (inference/__init__.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate
|
||||
.. automodule:: jedi.inference
|
||||
|
||||
Evaluation Contexts (evaluate/base_context.py)
|
||||
Inference Values (inference/base_value.py)
|
||||
++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. automodule:: jedi.evaluate.base_context
|
||||
.. automodule:: jedi.inference.base_value
|
||||
|
||||
.. inheritance-diagram::
|
||||
jedi.evaluate.context.instance.TreeInstance
|
||||
jedi.evaluate.context.klass.ClassContext
|
||||
jedi.evaluate.context.function.FunctionContext
|
||||
jedi.evaluate.context.function.FunctionExecutionContext
|
||||
jedi.inference.value.instance.TreeInstance
|
||||
jedi.inference.value.klass.ClassValue
|
||||
jedi.inference.value.function.FunctionValue
|
||||
jedi.inference.value.function.FunctionExecutionContext
|
||||
:parts: 1
|
||||
|
||||
|
||||
.. _name_resolution:
|
||||
|
||||
Name resolution (evaluate/finder.py)
|
||||
++++++++++++++++++++++++++++++++++++
|
||||
Name resolution (inference/finder.py)
|
||||
+++++++++++++++++++++++++++++++++++++
|
||||
|
||||
.. automodule:: jedi.evaluate.finder
|
||||
.. automodule:: jedi.inference.finder
|
||||
|
||||
|
||||
.. _dev-api:
|
||||
@@ -112,7 +110,7 @@ Core Extensions
|
||||
Core Extensions is a summary of the following topics:
|
||||
|
||||
- :ref:`Iterables & Dynamic Arrays <iterables>`
|
||||
- :ref:`Dynamic Parameters <dynamic>`
|
||||
- :ref:`Dynamic Parameters <dynamic_params>`
|
||||
- :ref:`Docstrings <docstrings>`
|
||||
- :ref:`Refactoring <refactoring>`
|
||||
|
||||
@@ -122,42 +120,42 @@ without some features.
|
||||
|
||||
.. _iterables:
|
||||
|
||||
Iterables & Dynamic Arrays (evaluate/context/iterable.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Iterables & Dynamic Arrays (inference/value/iterable.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
To understand Python on a deeper level, |jedi| needs to understand some of the
|
||||
dynamic features of Python like lists that are filled after creation:
|
||||
|
||||
.. automodule:: jedi.evaluate.context.iterable
|
||||
.. automodule:: jedi.inference.value.iterable
|
||||
|
||||
|
||||
.. _dynamic:
|
||||
.. _dynamic_params:
|
||||
|
||||
Parameter completion (evaluate/dynamic.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Parameter completion (inference/dynamic_params.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.dynamic
|
||||
.. automodule:: jedi.inference.dynamic_params
|
||||
|
||||
|
||||
.. _docstrings:
|
||||
|
||||
Docstrings (evaluate/docstrings.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Docstrings (inference/docstrings.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.docstrings
|
||||
.. automodule:: jedi.inference.docstrings
|
||||
|
||||
.. _refactoring:
|
||||
|
||||
Refactoring (evaluate/refactoring.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Refactoring (api/refactoring.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.refactoring
|
||||
.. automodule:: jedi.api.refactoring
|
||||
|
||||
|
||||
.. _imports-modules:
|
||||
|
||||
Imports & Modules
|
||||
-------------------
|
||||
-----------------
|
||||
|
||||
|
||||
- :ref:`Modules <modules>`
|
||||
@@ -167,19 +165,25 @@ Imports & Modules
|
||||
|
||||
.. _builtin:
|
||||
|
||||
Compiled Modules (evaluate/compiled.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Compiled Modules (inference/compiled.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.compiled
|
||||
.. automodule:: jedi.inference.compiled
|
||||
|
||||
|
||||
.. _imports:
|
||||
|
||||
Imports (evaluate/imports.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Imports (inference/imports.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.imports
|
||||
.. automodule:: jedi.inference.imports
|
||||
|
||||
.. _stubs:
|
||||
|
||||
Stubs & Annotations (inference/gradual)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.inference.gradual
|
||||
|
||||
.. _caching-recursions:
|
||||
|
||||
@@ -202,19 +206,14 @@ Caching (cache.py)
|
||||
Recursions (recursion.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi.evaluate.recursion
|
||||
.. automodule:: jedi.inference.recursion
|
||||
|
||||
|
||||
.. _dev-helpers:
|
||||
|
||||
Helper Modules
|
||||
---------------
|
||||
--------------
|
||||
|
||||
Most other modules are not really central to how Jedi works. They all contain
|
||||
relevant code, but you if you understand the modules above, you pretty much
|
||||
understand Jedi.
|
||||
|
||||
Python 2/3 compatibility (_compatibility.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: jedi._compatibility
|
||||
|
||||
@@ -1,31 +1,30 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
Features and Caveats
|
||||
====================
|
||||
Features and Limitations
|
||||
========================
|
||||
|
||||
Jedi obviously supports autocompletion. It's also possible to get it working in
|
||||
(:ref:`your REPL (IPython, etc.) <repl-completion>`).
|
||||
Jedi's main API calls and features are:
|
||||
|
||||
Static analysis is also possible by using the command ``jedi.names``.
|
||||
- Autocompletion: :meth:`.Script.complete`; It's also possible to get it
|
||||
working in :ref:`your REPL (IPython, etc.) <repl-completion>`
|
||||
- Goto/Type Inference: :meth:`.Script.goto` and :meth:`.Script.infer`
|
||||
- Static Analysis: :meth:`.Script.get_names` and :meth:`.Script.get_syntax_errors`
|
||||
- Refactorings: :meth:`.Script.rename`, :meth:`.Script.inline`,
|
||||
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`
|
||||
- Code Search: :meth:`.Script.search` and :meth:`.Project.search`
|
||||
|
||||
The Jedi Linter is currently in an alpha version and can be tested by calling
|
||||
``python -m jedi linter``.
|
||||
Basic Features
|
||||
--------------
|
||||
|
||||
Jedi would in theory support refactoring, but we have never publicized it,
|
||||
because it's not production ready. If you're interested in helping out here,
|
||||
let me know. With the latest parser changes, it should be very easy to actually
|
||||
make it work.
|
||||
|
||||
|
||||
General Features
|
||||
----------------
|
||||
|
||||
- Python 2.7 and 3.4+ support
|
||||
- Python 3.6+ support
|
||||
- Ignores syntax errors and wrong indentation
|
||||
- Can deal with complex module / function / class structures
|
||||
- Great Virtualenv support
|
||||
- Can infer function arguments from sphinx, epydoc and basic numpydoc docstrings,
|
||||
and PEP0484-style type hints (:ref:`type hinting <type-hinting>`)
|
||||
- Great ``virtualenv``/``venv`` support
|
||||
- Works great with Python's :ref:`type hinting <type-hinting>`,
|
||||
- Understands stub files
|
||||
- Can infer function arguments for sphinx, epydoc and basic numpydoc docstrings
|
||||
- Is overall a very solid piece of software that has been refined for a long
|
||||
time. Bug reports are very welcome and are usually fixed within a few weeks.
|
||||
|
||||
|
||||
Supported Python Features
|
||||
@@ -40,7 +39,7 @@ Supported Python Features
|
||||
- ``*args`` / ``**kwargs``
|
||||
- decorators / lambdas / closures
|
||||
- generators / iterators
|
||||
- some descriptors: property / staticmethod / classmethod
|
||||
- descriptors: property / staticmethod / classmethod / custom descriptors
|
||||
- some magic methods: ``__call__``, ``__iter__``, ``__next__``, ``__get__``,
|
||||
``__getitem__``, ``__init__``
|
||||
- ``list.append()``, ``set.add()``, ``list.extend()``, etc.
|
||||
@@ -48,198 +47,64 @@ Supported Python Features
|
||||
- relative imports
|
||||
- ``getattr()`` / ``__getattr__`` / ``__getattribute__``
|
||||
- function annotations
|
||||
- class decorators (py3k feature, are being ignored too, until I find a use
|
||||
case, that doesn't work with |jedi|)
|
||||
- simple/usual ``sys.path`` modifications
|
||||
- simple/typical ``sys.path`` modifications
|
||||
- ``isinstance`` checks for if/while/assert
|
||||
- namespace packages (includes ``pkgutil``, ``pkg_resources`` and PEP420 namespaces)
|
||||
- Django / Flask / Buildout support
|
||||
- Understands Pytest fixtures
|
||||
|
||||
|
||||
Not Supported
|
||||
-------------
|
||||
Limitations
|
||||
-----------
|
||||
|
||||
Not yet implemented:
|
||||
In general Jedi's limit are quite high, but for very big projects or very
|
||||
complex code, sometimes Jedi intentionally stops type inference, to avoid
|
||||
hanging for a long time.
|
||||
|
||||
- manipulations of instances outside the instance variables without using
|
||||
methods
|
||||
Additionally there are some Python patterns Jedi does not support. This is
|
||||
intentional and below should be a complete list:
|
||||
|
||||
Will probably never be implemented:
|
||||
|
||||
- metaclasses (how could an auto-completion ever support this)
|
||||
- Arbitrary metaclasses: Some metaclasses like enums and dataclasses are
|
||||
reimplemented in Jedi to make them work. Most of the time stubs are good
|
||||
enough to get type inference working, even when metaclasses are involved.
|
||||
- ``setattr()``, ``__import__()``
|
||||
- writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
|
||||
- evaluating ``if`` / ``while`` / ``del``
|
||||
- Writing to some dicts: ``globals()``, ``locals()``, ``object.__dict__``
|
||||
- Manipulations of instances outside the instance variables without using
|
||||
methods
|
||||
|
||||
|
||||
Caveats
|
||||
-------
|
||||
|
||||
**Slow Performance**
|
||||
Performance Issues
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Importing ``numpy`` can be quite slow sometimes, as well as loading the
|
||||
builtins the first time. If you want to speed things up, you could write import
|
||||
hooks in |jedi|, which preload stuff. However, once loaded, this is not a
|
||||
problem anymore. The same is true for huge modules like ``PySide``, ``wx``,
|
||||
etc.
|
||||
builtins the first time. If you want to speed things up, you could preload
|
||||
libriaries in |jedi|, with :func:`.preload_module`. However, once loaded, this
|
||||
should not be a problem anymore. The same is true for huge modules like
|
||||
``PySide``, ``wx``, ``tensorflow``, ``pandas``, etc.
|
||||
|
||||
**Security**
|
||||
Jedi does not have a very good cache layer. This is probably the biggest and
|
||||
only architectural `issue <https://github.com/davidhalter/jedi/issues/1059>`_ in
|
||||
Jedi. Unfortunately it is not easy to change that. Dave Halter is thinking
|
||||
about rewriting Jedi in Rust, but it has taken Jedi more than 8 years to reach
|
||||
version 1.0, a rewrite will probably also take years.
|
||||
|
||||
Security is an important issue for |jedi|. Therefore no Python code is
|
||||
executed. As long as you write pure Python, everything is evaluated
|
||||
statically. But: If you use builtin modules (``c_builtin``) there is no other
|
||||
option than to execute those modules. However: Execute isn't that critical (as
|
||||
e.g. in pythoncomplete, which used to execute *every* import!), because it
|
||||
means one import and no more. So basically the only dangerous thing is using
|
||||
the import itself. If your ``c_builtin`` uses some strange initializations, it
|
||||
might be dangerous. But if it does you're screwed anyways, because eventually
|
||||
you're going to execute your code, which executes the import.
|
||||
Security
|
||||
--------
|
||||
|
||||
For :class:`.Script`
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Recipes
|
||||
-------
|
||||
Security is an important topic for |jedi|. By default, no code is executed
|
||||
within Jedi. As long as you write pure Python, everything is inferred
|
||||
statically. If you enable ``load_unsafe_extensions=True`` for your
|
||||
:class:`.Project` and you use builtin modules (``c_builtin``) Jedi will execute
|
||||
those modules. If you don't trust a code base, please do not enable that
|
||||
option. It might lead to arbitrary code execution.
|
||||
|
||||
Here are some tips on how to use |jedi| efficiently.
|
||||
For :class:`.Interpreter`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
||||
.. _type-hinting:
|
||||
|
||||
Type Hinting
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If |jedi| cannot detect the type of a function argument correctly (due to the
|
||||
dynamic nature of Python), you can help it by hinting the type using
|
||||
one of the following docstring/annotation syntax styles:
|
||||
|
||||
**PEP-0484 style**
|
||||
|
||||
https://www.python.org/dev/peps/pep-0484/
|
||||
|
||||
function annotations
|
||||
|
||||
::
|
||||
|
||||
def myfunction(node: ProgramNode, foo: str) -> None:
|
||||
"""Do something with a ``node``.
|
||||
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
|
||||
assignment, for-loop and with-statement type hints (all Python versions).
|
||||
Note that the type hints must be on the same line as the statement
|
||||
|
||||
::
|
||||
|
||||
x = foo() # type: int
|
||||
x, y = 2, 3 # type: typing.Optional[int], typing.Union[int, str] # typing module is mostly supported
|
||||
for key, value in foo.items(): # type: str, Employee # note that Employee must be in scope
|
||||
pass
|
||||
with foo() as f: # type: int
|
||||
print(f + 3)
|
||||
|
||||
Most of the features in PEP-0484 are supported including the typing module
|
||||
(for Python < 3.5 you have to do ``pip install typing`` to use these),
|
||||
and forward references.
|
||||
|
||||
Things that are missing (and this is not an exhaustive list; some of these
|
||||
are planned, others might be hard to implement and provide little worth):
|
||||
|
||||
- annotating functions with comments: https://www.python.org/dev/peps/pep-0484/#suggested-syntax-for-python-2-7-and-straddling-code
|
||||
- understanding ``typing.cast()``
|
||||
- stub files: https://www.python.org/dev/peps/pep-0484/#stub-files
|
||||
- ``typing.Callable``
|
||||
- ``typing.TypeVar``
|
||||
- User defined generic types: https://www.python.org/dev/peps/pep-0484/#user-defined-generic-types
|
||||
|
||||
**Sphinx style**
|
||||
|
||||
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
|
||||
|
||||
::
|
||||
|
||||
def myfunction(node, foo):
|
||||
"""Do something with a ``node``.
|
||||
|
||||
:type node: ProgramNode
|
||||
:param str foo: foo parameter description
|
||||
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
**Epydoc**
|
||||
|
||||
http://epydoc.sourceforge.net/manual-fields.html
|
||||
|
||||
::
|
||||
|
||||
def myfunction(node):
|
||||
"""Do something with a ``node``.
|
||||
|
||||
@type node: ProgramNode
|
||||
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
**Numpydoc**
|
||||
|
||||
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
|
||||
|
||||
In order to support the numpydoc format, you need to install the `numpydoc
|
||||
<https://pypi.python.org/pypi/numpydoc>`__ package.
|
||||
|
||||
::
|
||||
|
||||
def foo(var1, var2, long_var_name='hi'):
|
||||
r"""A one-line summary that does not use variable names or the
|
||||
function name.
|
||||
|
||||
...
|
||||
|
||||
Parameters
|
||||
----------
|
||||
var1 : array_like
|
||||
Array_like means all those objects -- lists, nested lists,
|
||||
etc. -- that can be converted to an array. We can also
|
||||
refer to variables like `var1`.
|
||||
var2 : int
|
||||
The type above can either refer to an actual Python type
|
||||
(e.g. ``int``), or describe the type of the variable in more
|
||||
detail, e.g. ``(N,) ndarray`` or ``array_like``.
|
||||
long_variable_name : {'hi', 'ho'}, optional
|
||||
Choices in brackets, default first when optional.
|
||||
|
||||
...
|
||||
|
||||
"""
|
||||
var2.| # complete here
|
||||
|
||||
A little history
|
||||
----------------
|
||||
|
||||
The Star Wars Jedi are awesome. My Jedi software tries to imitate a little bit
|
||||
of the precognition the Jedi have. There's even an awesome `scene
|
||||
<https://youtu.be/yHRJLIf7wMU>`_ of Monty Python Jedis :-).
|
||||
|
||||
But actually the name hasn't so much to do with Star Wars. It's part of my
|
||||
second name.
|
||||
|
||||
After I explained Guido van Rossum, how some parts of my auto-completion work,
|
||||
he said (we drank a beer or two):
|
||||
|
||||
*"Oh, that worries me..."*
|
||||
|
||||
When it's finished, I hope he'll like it :-)
|
||||
|
||||
I actually started Jedi, because there were no good solutions available for VIM.
|
||||
Most auto-completions just didn't work well. The only good solution was PyCharm.
|
||||
But I like my good old VIM. Rope was never really intended to be an
|
||||
auto-completion (and also I really hate project folders for my Python scripts).
|
||||
It's more of a refactoring suite. So I decided to do my own version of a
|
||||
completion, which would execute non-dangerous code. But I soon realized, that
|
||||
this wouldn't work. So I built an extremely recursive thing which understands
|
||||
many of Python's key features.
|
||||
|
||||
By the way, I really tried to program it as understandable as possible. But I
|
||||
think understanding it might need quite some time, because of its recursive
|
||||
nature.
|
||||
If you want security for :class:`.Interpreter`, ``do not`` use it. Jedi does
|
||||
execute properties and in general is not very careful to avoid code execution.
|
||||
This is intentional: Most people trust the code bases they have imported,
|
||||
because at that point a malicious code base would have had code execution
|
||||
already.
|
||||
|
||||
@@ -3,6 +3,15 @@
|
||||
Installation and Configuration
|
||||
==============================
|
||||
|
||||
.. warning:: Most people will want to install Jedi as a submodule/vendored and
|
||||
not through pip/system wide. The reason for this is that it makes sense that
|
||||
the plugin that uses Jedi has always access to it. Otherwise Jedi will not
|
||||
work properly when virtualenvs are activated. So please read the
|
||||
documentation of your editor/IDE plugin to install Jedi.
|
||||
|
||||
For plugin developers, Jedi works best if it is always available. Vendoring
|
||||
is a pretty good option for that.
|
||||
|
||||
You can either include |jedi| as a submodule in your text editor plugin (like
|
||||
jedi-vim_ does by default), or you can install it systemwide.
|
||||
|
||||
@@ -11,8 +20,16 @@ jedi-vim_ does by default), or you can install it systemwide.
|
||||
editor, refer to the corresponding documentation.
|
||||
|
||||
|
||||
The preferred way
|
||||
-----------------
|
||||
The normal way
|
||||
--------------
|
||||
|
||||
Most people use Jedi with a :ref:`editor plugins<editor-plugins>`. Typically
|
||||
you install Jedi by installing an editor plugin. No necessary steps are needed.
|
||||
Just take a look at the instructions for the plugin.
|
||||
|
||||
|
||||
With pip
|
||||
--------
|
||||
|
||||
On any system you can install |jedi| directly from the Python package index
|
||||
using pip::
|
||||
@@ -33,14 +50,6 @@ Arch Linux
|
||||
You can install |jedi| directly from official Arch Linux packages:
|
||||
|
||||
- `python-jedi <https://www.archlinux.org/packages/community/any/python-jedi/>`__
|
||||
(Python 3)
|
||||
- `python2-jedi <https://www.archlinux.org/packages/community/any/python2-jedi/>`__
|
||||
(Python 2)
|
||||
|
||||
The specified Python version just refers to the *runtime environment* for
|
||||
|jedi|. Use the Python 2 version if you're running vim (or whatever editor you
|
||||
use) under Python 2. Otherwise, use the Python 3 version. But whatever version
|
||||
you choose, both are able to complete both Python 2 and 3 *code*.
|
||||
|
||||
(There is also a packaged version of the vim plugin available:
|
||||
`vim-jedi at Arch Linux <https://www.archlinux.org/packages/community/any/vim-jedi/>`__.)
|
||||
@@ -57,19 +66,15 @@ Others
|
||||
We are in the discussion of adding |jedi| to the Fedora repositories.
|
||||
|
||||
|
||||
Manual installation from a downloaded package
|
||||
Manual installation from GitHub
|
||||
---------------------------------------------
|
||||
|
||||
If you prefer not to use an automated package installer, you can `download
|
||||
<https://github.com/davidhalter/jedi/archive/master.zip>`__ a current copy of
|
||||
|jedi| and install it manually.
|
||||
|
||||
To install it, navigate to the directory containing `setup.py` on your console
|
||||
and type::
|
||||
If you prefer not to use an automated package installer, you can clone the source from GitHub and install it manually. To install it, run these commands::
|
||||
|
||||
git clone --recurse-submodules https://github.com/davidhalter/jedi
|
||||
cd jedi
|
||||
sudo python setup.py install
|
||||
|
||||
|
||||
Inclusion as a submodule
|
||||
------------------------
|
||||
|
||||
|
||||
@@ -3,18 +3,14 @@
|
||||
Jedi Testing
|
||||
============
|
||||
|
||||
The test suite depends on ``tox`` and ``pytest``::
|
||||
The test suite depends on ``pytest``::
|
||||
|
||||
pip install tox pytest
|
||||
pip install pytest
|
||||
|
||||
To run the tests for all supported Python versions::
|
||||
|
||||
tox
|
||||
|
||||
If you want to test only a specific Python version (e.g. Python 2.7), it's as
|
||||
If you want to test only a specific Python version (e.g. Python 3.8), it is as
|
||||
easy as::
|
||||
|
||||
tox -e py27
|
||||
python3.8 -m pytest
|
||||
|
||||
Tests are also run automatically on `Travis CI
|
||||
<https://travis-ci.org/davidhalter/jedi/>`_.
|
||||
@@ -28,8 +24,8 @@ simple and readable testing structure.
|
||||
|
||||
.. _blackbox:
|
||||
|
||||
Blackbox Tests (run.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Integration Tests (run.py)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. automodule:: test.run
|
||||
|
||||
|
||||
@@ -1,79 +1,108 @@
|
||||
.. include:: ../global.rst
|
||||
|
||||
End User Usage
|
||||
==============
|
||||
Using Jedi
|
||||
==========
|
||||
|
||||
If you are a not an IDE Developer, the odds are that you just want to use
|
||||
|jedi| as a browser plugin or in the shell. Yes that's :ref:`also possible
|
||||
<repl-completion>`!
|
||||
|jedi| is can be used with a variety of :ref:`plugins <editor-plugins>`,
|
||||
`language servers <language-servers>` and other software.
|
||||
It is also possible to use |jedi| in the :ref:`Python shell or with IPython
|
||||
<repl-completion>`.
|
||||
|
||||
|jedi| is relatively young and can be used in a variety of Plugins and
|
||||
Software. If your Editor/IDE is not among them, recommend |jedi| to your IDE
|
||||
developers.
|
||||
Below you can also find a list of :ref:`recipes for type hinting <recipes>`.
|
||||
|
||||
.. _language-servers:
|
||||
|
||||
Language Servers
|
||||
--------------
|
||||
|
||||
- `jedi-language-server <https://github.com/pappasam/jedi-language-server>`_
|
||||
- `python-language-server <https://github.com/palantir/python-language-server>`_
|
||||
- `anakin-language-server <https://github.com/muffinmad/anakin-language-server>`_
|
||||
|
||||
.. _editor-plugins:
|
||||
|
||||
Editor Plugins
|
||||
--------------
|
||||
|
||||
Vim:
|
||||
Vim
|
||||
~~~
|
||||
|
||||
- jedi-vim_
|
||||
- YouCompleteMe_
|
||||
- deoplete-jedi_
|
||||
|
||||
Emacs:
|
||||
Visual Studio Code
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
- `Python Extension`_
|
||||
|
||||
Emacs
|
||||
~~~~~
|
||||
|
||||
- Jedi.el_
|
||||
- elpy_
|
||||
- anaconda-mode_
|
||||
|
||||
Sublime Text 2/3:
|
||||
Sublime Text 2/3
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
- SublimeJEDI_ (ST2 & ST3)
|
||||
- anaconda_ (only ST3)
|
||||
|
||||
SynWrite:
|
||||
SynWrite
|
||||
~~~~~~~~
|
||||
|
||||
- SynJedi_
|
||||
|
||||
TextMate:
|
||||
TextMate
|
||||
~~~~~~~~
|
||||
|
||||
- Textmate_ (Not sure if it's actually working)
|
||||
|
||||
Kate:
|
||||
Kate
|
||||
~~~~
|
||||
|
||||
- Kate_ version 4.13+ `supports it natively
|
||||
<https://projects.kde.org/projects/kde/applications/kate/repository/entry/addons/kate/pate/src/plugins/python_autocomplete_jedi.py?rev=KDE%2F4.13>`__,
|
||||
you have to enable it, though.
|
||||
|
||||
Visual Studio Code:
|
||||
|
||||
- `Python Extension`_
|
||||
|
||||
Atom:
|
||||
Atom
|
||||
~~~~
|
||||
|
||||
- autocomplete-python-jedi_
|
||||
|
||||
GNOME Builder:
|
||||
GNOME Builder
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
- `GNOME Builder`_ `supports it natively
|
||||
<https://git.gnome.org/browse/gnome-builder/tree/plugins/jedi>`__,
|
||||
and is enabled by default.
|
||||
|
||||
Gedit:
|
||||
Gedit
|
||||
~~~~~
|
||||
|
||||
- gedi_
|
||||
|
||||
Eric IDE:
|
||||
Eric IDE
|
||||
~~~~~~~~
|
||||
|
||||
- `Eric IDE`_ (Available as a plugin)
|
||||
|
||||
Web Debugger:
|
||||
Web Debugger
|
||||
~~~~~~~~~~~~
|
||||
|
||||
- wdb_
|
||||
|
||||
xonsh shell
|
||||
~~~~~~~~~~~
|
||||
|
||||
Jedi is a preinstalled extension in `xonsh shell <https://xon.sh/contents.html>`_.
|
||||
Run the following command to enable:
|
||||
|
||||
::
|
||||
|
||||
xontrib load jedi
|
||||
|
||||
and many more!
|
||||
|
||||
.. _repl-completion:
|
||||
@@ -81,11 +110,14 @@ and many more!
|
||||
Tab Completion in the Python Shell
|
||||
----------------------------------
|
||||
|
||||
Starting with Ipython `6.0.0` Jedi is a dependency of IPython. Autocompletion
|
||||
in IPython is therefore possible without additional configuration.
|
||||
Jedi is a dependency of IPython. Autocompletion in IPython is therefore
|
||||
possible without additional configuration.
|
||||
|
||||
Here is an `example video <https://vimeo.com/122332037>`_ how REPL completion
|
||||
can look like in a different shell.
|
||||
|
||||
There are two different options how you can use Jedi autocompletion in
|
||||
your Python interpreter. One with your custom ``$HOME/.pythonrc.py`` file
|
||||
your ``python`` interpreter. One with your custom ``$HOME/.pythonrc.py`` file
|
||||
and one that uses ``PYTHONSTARTUP``.
|
||||
|
||||
Using ``PYTHONSTARTUP``
|
||||
@@ -93,11 +125,137 @@ Using ``PYTHONSTARTUP``
|
||||
|
||||
.. automodule:: jedi.api.replstartup
|
||||
|
||||
Using a custom ``$HOME/.pythonrc.py``
|
||||
Using a Custom ``$HOME/.pythonrc.py``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autofunction:: jedi.utils.setup_readline
|
||||
|
||||
.. _recipes:
|
||||
|
||||
Recipes
|
||||
-------
|
||||
|
||||
Here are some tips on how to use |jedi| efficiently.
|
||||
|
||||
|
||||
.. _type-hinting:
|
||||
|
||||
Type Hinting
|
||||
~~~~~~~~~~~~
|
||||
|
||||
If |jedi| cannot detect the type of a function argument correctly (due to the
|
||||
dynamic nature of Python), you can help it by hinting the type using
|
||||
one of the docstring/annotation styles below. **Only gradual typing will
|
||||
always work**, all the docstring solutions are glorified hacks and more
|
||||
complicated cases will probably not work.
|
||||
|
||||
Official Gradual Typing (Recommended)
|
||||
+++++++++++++++++++++++++++++++++++++
|
||||
|
||||
You can read a lot about Python's gradual typing system in the corresponding
|
||||
PEPs like:
|
||||
|
||||
- `PEP 484 <https://www.python.org/dev/peps/pep-0484/>`_ as an introduction
|
||||
- `PEP 526 <https://www.python.org/dev/peps/pep-0526/>`_ for variable annotations
|
||||
- `PEP 589 <https://www.python.org/dev/peps/pep-0589/>`_ for ``TypeDict``
|
||||
- There are probably more :)
|
||||
|
||||
Below you can find a few examples how you can use this feature.
|
||||
|
||||
Function annotations::
|
||||
|
||||
def myfunction(node: ProgramNode, foo: str) -> None:
|
||||
"""Do something with a ``node``.
|
||||
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
|
||||
Assignment, for-loop and with-statement type hints::
|
||||
|
||||
import typing
|
||||
x: int = foo()
|
||||
y: typing.Optional[int] = 3
|
||||
|
||||
key: str
|
||||
value: Employee
|
||||
for key, value in foo.items():
|
||||
pass
|
||||
|
||||
f: Union[int, float]
|
||||
with foo() as f:
|
||||
print(f + 3)
|
||||
|
||||
PEP-0484 should be supported in its entirety. Feel free to open issues if that
|
||||
is not the case. You can also use stub files.
|
||||
|
||||
|
||||
Sphinx style
|
||||
++++++++++++
|
||||
|
||||
http://www.sphinx-doc.org/en/stable/domains.html#info-field-lists
|
||||
|
||||
::
|
||||
|
||||
def myfunction(node, foo):
|
||||
"""
|
||||
Do something with a ``node``.
|
||||
|
||||
:type node: ProgramNode
|
||||
:param str foo: foo parameter description
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
Epydoc
|
||||
++++++
|
||||
|
||||
http://epydoc.sourceforge.net/manual-fields.html
|
||||
|
||||
::
|
||||
|
||||
def myfunction(node):
|
||||
"""
|
||||
Do something with a ``node``.
|
||||
|
||||
@type node: ProgramNode
|
||||
"""
|
||||
node.| # complete here
|
||||
|
||||
Numpydoc
|
||||
++++++++
|
||||
|
||||
https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt
|
||||
|
||||
In order to support the numpydoc format, you need to install the `numpydoc
|
||||
<https://pypi.python.org/pypi/numpydoc>`__ package.
|
||||
|
||||
::
|
||||
|
||||
def foo(var1, var2, long_var_name='hi'):
|
||||
r"""
|
||||
A one-line summary that does not use variable names or the
|
||||
function name.
|
||||
|
||||
...
|
||||
|
||||
Parameters
|
||||
----------
|
||||
var1 : array_like
|
||||
Array_like means all those objects -- lists, nested lists,
|
||||
etc. -- that can be converted to an array. We can also
|
||||
refer to variables like `var1`.
|
||||
var2 : int
|
||||
The type above can either refer to an actual Python type
|
||||
(e.g. ``int``), or describe the type of the variable in more
|
||||
detail, e.g. ``(N,) ndarray`` or ``array_like``.
|
||||
long_variable_name : {'hi', 'ho'}, optional
|
||||
Choices in brackets, default first when optional.
|
||||
|
||||
...
|
||||
|
||||
"""
|
||||
var2.| # complete here
|
||||
|
||||
.. _jedi-vim: https://github.com/davidhalter/jedi-vim
|
||||
.. _youcompleteme: https://valloric.github.io/YouCompleteMe/
|
||||
.. _deoplete-jedi: https://github.com/zchee/deoplete-jedi
|
||||
@@ -114,4 +272,4 @@ Using a custom ``$HOME/.pythonrc.py``
|
||||
.. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/
|
||||
.. _gedi: https://github.com/isamert/gedi
|
||||
.. _Eric IDE: https://eric-ide.python-projects.org
|
||||
.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python
|
||||
.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=ms-python.python
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
:orphan:
|
||||
|
||||
.. |jedi| replace:: *Jedi*
|
||||
.. |jedi| replace:: Jedi
|
||||
|
||||
@@ -1,13 +1,44 @@
|
||||
.. include global.rst
|
||||
|
||||
Jedi - an awesome autocompletion/static analysis library for Python
|
||||
===================================================================
|
||||
.. meta::
|
||||
:github_url: https://github.com/davidhalter/jedi
|
||||
|
||||
Release v\ |release|. (:doc:`Installation <docs/installation>`)
|
||||
Jedi - an awesome autocompletion, static analysis and refactoring library for Python
|
||||
====================================================================================
|
||||
|
||||
.. image:: https://img.shields.io/github/stars/davidhalter/jedi.svg?style=social&label=Star&maxAge=2592000
|
||||
:target: https://github.com/davidhalter/jedi
|
||||
:alt: GitHub stars
|
||||
|
||||
.. image:: http://isitmaintained.com/badge/open/davidhalter/jedi.svg
|
||||
:target: https://github.com/davidhalter/jedi/issues
|
||||
:alt: The percentage of open issues and pull requests
|
||||
|
||||
.. image:: http://isitmaintained.com/badge/resolution/davidhalter/jedi.svg
|
||||
:target: https://github.com/davidhalter/jedi/issues
|
||||
:alt: The resolution time is the median time an issue or pull request stays open.
|
||||
|
||||
.. image:: https://travis-ci.org/davidhalter/jedi.svg?branch=master
|
||||
:target: https://travis-ci.org/davidhalter/jedi
|
||||
:alt: Linux Tests
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/mgva3bbawyma1new/branch/master?svg=true
|
||||
:target: https://ci.appveyor.com/project/davidhalter/jedi/branch/master
|
||||
:alt: Windows Tests
|
||||
|
||||
.. image:: https://coveralls.io/repos/davidhalter/jedi/badge.svg?branch=master
|
||||
:target: https://coveralls.io/r/davidhalter/jedi
|
||||
:alt: Coverage status
|
||||
|
||||
.. image:: https://pepy.tech/badge/jedi
|
||||
:target: https://pepy.tech/project/jedi
|
||||
:alt: PyPI Downloads
|
||||
|
||||
`Github Repository <https://github.com/davidhalter/jedi>`_
|
||||
|
||||
.. automodule:: jedi
|
||||
|
||||
Autocompletion can look like this (e.g. VIM plugin):
|
||||
Autocompletion can for example look like this in jedi-vim:
|
||||
|
||||
.. figure:: _screenshots/screenshot_complete.png
|
||||
|
||||
@@ -18,16 +49,18 @@ Docs
|
||||
----
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:maxdepth: 1
|
||||
|
||||
docs/usage
|
||||
docs/installation
|
||||
docs/features
|
||||
docs/api
|
||||
docs/api-classes
|
||||
docs/installation
|
||||
docs/settings
|
||||
docs/development
|
||||
docs/testing
|
||||
docs/acknowledgements
|
||||
docs/changelog
|
||||
|
||||
|
||||
.. _resources:
|
||||
@@ -35,6 +68,10 @@ Docs
|
||||
Resources
|
||||
---------
|
||||
|
||||
If you want to stay **up-to-date** with releases, please **subscribe** to this
|
||||
mailing list: https://groups.google.com/g/jedi-announce. To subscribe you can
|
||||
simply send an empty email to ``jedi-announce+subscribe@googlegroups.com``.
|
||||
|
||||
- `Source Code on Github <https://github.com/davidhalter/jedi>`_
|
||||
- `Travis Testing <https://travis-ci.org/davidhalter/jedi>`_
|
||||
- `Python Package Index <https://pypi.python.org/pypi/jedi/>`_
|
||||
|
||||
@@ -1,47 +1,42 @@
|
||||
"""
|
||||
Jedi is a static analysis tool for Python that can be used in IDEs/editors. Its
|
||||
historic focus is autocompletion, but does static analysis for now as well.
|
||||
Jedi is fast and is very well tested. It understands Python on a deeper level
|
||||
than all other static analysis frameworks for Python.
|
||||
Jedi is a static analysis tool for Python that is typically used in
|
||||
IDEs/editors plugins. Jedi has a focus on autocompletion and goto
|
||||
functionality. Other features include refactoring, code search and finding
|
||||
references.
|
||||
|
||||
Jedi has support for two different goto functions. It's possible to search for
|
||||
related names and to list all names in a Python file and infer them. Jedi
|
||||
understands docstrings and you can use Jedi autocompletion in your REPL as
|
||||
well.
|
||||
Jedi has a simple API to work with. There is a reference implementation as a
|
||||
`VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_. Autocompletion in your
|
||||
REPL is also possible, IPython uses it natively and for the CPython REPL you
|
||||
can install it. Jedi is well tested and bugs should be rare.
|
||||
|
||||
Jedi uses a very simple API to connect with IDE's. There's a reference
|
||||
implementation as a `VIM-Plugin <https://github.com/davidhalter/jedi-vim>`_,
|
||||
which uses Jedi's autocompletion. We encourage you to use Jedi in your IDEs.
|
||||
It's really easy.
|
||||
|
||||
To give you a simple example how you can use the Jedi library, here is an
|
||||
example for the autocompletion feature:
|
||||
Here's a simple example of the autocompletion feature:
|
||||
|
||||
>>> import jedi
|
||||
>>> source = '''
|
||||
... import datetime
|
||||
... datetime.da'''
|
||||
>>> script = jedi.Script(source, 3, len('datetime.da'), 'example.py')
|
||||
... import json
|
||||
... json.lo'''
|
||||
>>> script = jedi.Script(source, path='example.py')
|
||||
>>> script
|
||||
<Script: 'example.py' ...>
|
||||
>>> completions = script.completions()
|
||||
>>> completions #doctest: +ELLIPSIS
|
||||
[<Completion: date>, <Completion: datetime>, ...]
|
||||
>>> completions = script.complete(3, len('json.lo'))
|
||||
>>> completions
|
||||
[<Completion: load>, <Completion: loads>]
|
||||
>>> print(completions[0].complete)
|
||||
te
|
||||
ad
|
||||
>>> print(completions[0].name)
|
||||
date
|
||||
|
||||
As you see Jedi is pretty simple and allows you to concentrate on writing a
|
||||
good text editor, while still having very good IDE features for Python.
|
||||
load
|
||||
"""
|
||||
|
||||
__version__ = '0.13.2'
|
||||
__version__ = '0.18.0'
|
||||
|
||||
from jedi.api import Script, Interpreter, set_debug_function, \
|
||||
preload_module, names
|
||||
from jedi.api import Script, Interpreter, set_debug_function, preload_module
|
||||
from jedi import settings
|
||||
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
|
||||
get_system_environment, InterpreterEnvironment
|
||||
from jedi.api.project import Project, get_default_project
|
||||
from jedi.api.exceptions import InternalError, RefactoringError
|
||||
|
||||
# Finally load the internal plugins. This is only internal.
|
||||
from jedi.plugins import registry
|
||||
del registry
|
||||
|
||||
@@ -27,8 +27,8 @@ def _start_linter():
|
||||
paths = [path]
|
||||
|
||||
try:
|
||||
for path in paths:
|
||||
for error in jedi.Script(path=path)._analysis():
|
||||
for p in paths:
|
||||
for error in jedi.Script(path=p)._analysis():
|
||||
print(error)
|
||||
except Exception:
|
||||
if '--pdb' in sys.argv:
|
||||
@@ -40,9 +40,33 @@ def _start_linter():
|
||||
raise
|
||||
|
||||
|
||||
def _complete():
|
||||
import jedi
|
||||
import pdb
|
||||
|
||||
if '-d' in sys.argv:
|
||||
sys.argv.remove('-d')
|
||||
jedi.set_debug_function()
|
||||
|
||||
try:
|
||||
completions = jedi.Script(sys.argv[2]).complete()
|
||||
for c in completions:
|
||||
c.docstring()
|
||||
c.type
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
pdb.post_mortem()
|
||||
else:
|
||||
print(completions)
|
||||
|
||||
|
||||
if len(sys.argv) == 2 and sys.argv[1] == 'repl':
|
||||
# don't want to use __main__ only for repl yet, maybe we want to use it for
|
||||
# something else. So just use the keyword ``repl`` for now.
|
||||
print(join(dirname(abspath(__file__)), 'api', 'replstartup.py'))
|
||||
elif len(sys.argv) > 1 and sys.argv[1] == 'linter':
|
||||
elif len(sys.argv) > 1 and sys.argv[1] == '_linter':
|
||||
_start_linter()
|
||||
elif len(sys.argv) > 1 and sys.argv[1] == '_complete':
|
||||
_complete()
|
||||
else:
|
||||
print('Command not implemented: %s' % sys.argv[1])
|
||||
|
||||
@@ -1,306 +1,13 @@
|
||||
"""
|
||||
To ensure compatibility from Python ``2.7`` - ``3.x``, a module has been
|
||||
created. Clearly there is huge need to use conforming syntax.
|
||||
This module is here to ensure compatibility of Windows/Linux/MacOS and
|
||||
different Python versions.
|
||||
"""
|
||||
import errno
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import pkgutil
|
||||
import warnings
|
||||
import inspect
|
||||
import subprocess
|
||||
try:
|
||||
import importlib
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
is_py3 = sys.version_info[0] >= 3
|
||||
is_py35 = is_py3 and sys.version_info[1] >= 5
|
||||
py_version = int(str(sys.version_info[0]) + str(sys.version_info[1]))
|
||||
import pickle
|
||||
|
||||
|
||||
class DummyFile(object):
|
||||
def __init__(self, loader, string):
|
||||
self.loader = loader
|
||||
self.string = string
|
||||
|
||||
def read(self):
|
||||
return self.loader.get_source(self.string)
|
||||
|
||||
def close(self):
|
||||
del self.loader
|
||||
|
||||
|
||||
def find_module_py34(string, path=None, full_name=None, is_global_search=True):
|
||||
spec = None
|
||||
loader = None
|
||||
|
||||
for finder in sys.meta_path:
|
||||
if is_global_search and finder != importlib.machinery.PathFinder:
|
||||
p = None
|
||||
else:
|
||||
p = path
|
||||
try:
|
||||
find_spec = finder.find_spec
|
||||
except AttributeError:
|
||||
# These are old-school clases that still have a different API, just
|
||||
# ignore those.
|
||||
continue
|
||||
|
||||
spec = find_spec(string, p)
|
||||
if spec is not None:
|
||||
loader = spec.loader
|
||||
if loader is None and not spec.has_location:
|
||||
# This is a namespace package.
|
||||
full_name = string if not path else full_name
|
||||
implicit_ns_info = ImplicitNSInfo(full_name, spec.submodule_search_locations._path)
|
||||
return None, implicit_ns_info, False
|
||||
break
|
||||
|
||||
return find_module_py33(string, path, loader)
|
||||
|
||||
|
||||
def find_module_py33(string, path=None, loader=None, full_name=None, is_global_search=True):
|
||||
loader = loader or importlib.machinery.PathFinder.find_module(string, path)
|
||||
|
||||
if loader is None and path is None: # Fallback to find builtins
|
||||
try:
|
||||
with warnings.catch_warnings(record=True):
|
||||
# Mute "DeprecationWarning: Use importlib.util.find_spec()
|
||||
# instead." While we should replace that in the future, it's
|
||||
# probably good to wait until we deprecate Python 3.3, since
|
||||
# it was added in Python 3.4 and find_loader hasn't been
|
||||
# removed in 3.6.
|
||||
loader = importlib.find_loader(string)
|
||||
except ValueError as e:
|
||||
# See #491. Importlib might raise a ValueError, to avoid this, we
|
||||
# just raise an ImportError to fix the issue.
|
||||
raise ImportError("Originally " + repr(e))
|
||||
|
||||
if loader is None:
|
||||
raise ImportError("Couldn't find a loader for {}".format(string))
|
||||
|
||||
try:
|
||||
is_package = loader.is_package(string)
|
||||
if is_package:
|
||||
if hasattr(loader, 'path'):
|
||||
module_path = os.path.dirname(loader.path)
|
||||
else:
|
||||
# At least zipimporter does not have path attribute
|
||||
module_path = os.path.dirname(loader.get_filename(string))
|
||||
if hasattr(loader, 'archive'):
|
||||
module_file = DummyFile(loader, string)
|
||||
else:
|
||||
module_file = None
|
||||
else:
|
||||
module_path = loader.get_filename(string)
|
||||
module_file = DummyFile(loader, string)
|
||||
except AttributeError:
|
||||
# ExtensionLoader has not attribute get_filename, instead it has a
|
||||
# path attribute that we can use to retrieve the module path
|
||||
try:
|
||||
module_path = loader.path
|
||||
module_file = DummyFile(loader, string)
|
||||
except AttributeError:
|
||||
module_path = string
|
||||
module_file = None
|
||||
finally:
|
||||
is_package = False
|
||||
|
||||
if hasattr(loader, 'archive'):
|
||||
module_path = loader.archive
|
||||
|
||||
return module_file, module_path, is_package
|
||||
|
||||
|
||||
def find_module_pre_py34(string, path=None, full_name=None, is_global_search=True):
|
||||
# This import is here, because in other places it will raise a
|
||||
# DeprecationWarning.
|
||||
import imp
|
||||
try:
|
||||
module_file, module_path, description = imp.find_module(string, path)
|
||||
module_type = description[2]
|
||||
return module_file, module_path, module_type is imp.PKG_DIRECTORY
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
if path is None:
|
||||
path = sys.path
|
||||
for item in path:
|
||||
loader = pkgutil.get_importer(item)
|
||||
if loader:
|
||||
try:
|
||||
loader = loader.find_module(string)
|
||||
if loader:
|
||||
is_package = loader.is_package(string)
|
||||
is_archive = hasattr(loader, 'archive')
|
||||
module_path = loader.get_filename(string)
|
||||
if is_package:
|
||||
module_path = os.path.dirname(module_path)
|
||||
if is_archive:
|
||||
module_path = loader.archive
|
||||
file = None
|
||||
if not is_package or is_archive:
|
||||
file = DummyFile(loader, string)
|
||||
return file, module_path, is_package
|
||||
except ImportError:
|
||||
pass
|
||||
raise ImportError("No module named {}".format(string))
|
||||
|
||||
|
||||
find_module = find_module_py34 if is_py3 else find_module_pre_py34
|
||||
find_module.__doc__ = """
|
||||
Provides information about a module.
|
||||
|
||||
This function isolates the differences in importing libraries introduced with
|
||||
python 3.3 on; it gets a module name and optionally a path. It will return a
|
||||
tuple containin an open file for the module (if not builtin), the filename
|
||||
or the name of the module if it is a builtin one and a boolean indicating
|
||||
if the module is contained in a package.
|
||||
"""
|
||||
|
||||
|
||||
def _iter_modules(paths, prefix=''):
|
||||
# Copy of pkgutil.iter_modules adapted to work with namespaces
|
||||
|
||||
for path in paths:
|
||||
importer = pkgutil.get_importer(path)
|
||||
|
||||
if not isinstance(importer, importlib.machinery.FileFinder):
|
||||
# We're only modifying the case for FileFinder. All the other cases
|
||||
# still need to be checked (like zip-importing). Do this by just
|
||||
# calling the pkgutil version.
|
||||
for mod_info in pkgutil.iter_modules([path], prefix):
|
||||
yield mod_info
|
||||
continue
|
||||
|
||||
# START COPY OF pkutils._iter_file_finder_modules.
|
||||
if importer.path is None or not os.path.isdir(importer.path):
|
||||
return
|
||||
|
||||
yielded = {}
|
||||
|
||||
try:
|
||||
filenames = os.listdir(importer.path)
|
||||
except OSError:
|
||||
# ignore unreadable directories like import does
|
||||
filenames = []
|
||||
filenames.sort() # handle packages before same-named modules
|
||||
|
||||
for fn in filenames:
|
||||
modname = inspect.getmodulename(fn)
|
||||
if modname == '__init__' or modname in yielded:
|
||||
continue
|
||||
|
||||
# jedi addition: Avoid traversing special directories
|
||||
if fn.startswith('.') or fn == '__pycache__':
|
||||
continue
|
||||
|
||||
path = os.path.join(importer.path, fn)
|
||||
ispkg = False
|
||||
|
||||
if not modname and os.path.isdir(path) and '.' not in fn:
|
||||
modname = fn
|
||||
# A few jedi modifications: Don't check if there's an
|
||||
# __init__.py
|
||||
try:
|
||||
os.listdir(path)
|
||||
except OSError:
|
||||
# ignore unreadable directories like import does
|
||||
continue
|
||||
ispkg = True
|
||||
|
||||
if modname and '.' not in modname:
|
||||
yielded[modname] = 1
|
||||
yield importer, prefix + modname, ispkg
|
||||
# END COPY
|
||||
|
||||
|
||||
iter_modules = _iter_modules if py_version >= 34 else pkgutil.iter_modules
|
||||
|
||||
|
||||
class ImplicitNSInfo(object):
|
||||
"""Stores information returned from an implicit namespace spec"""
|
||||
def __init__(self, name, paths):
|
||||
self.name = name
|
||||
self.paths = paths
|
||||
|
||||
|
||||
if is_py3:
|
||||
all_suffixes = importlib.machinery.all_suffixes
|
||||
else:
|
||||
def all_suffixes():
|
||||
# Is deprecated and raises a warning in Python 3.6.
|
||||
import imp
|
||||
return [suffix for suffix, _, _ in imp.get_suffixes()]
|
||||
|
||||
|
||||
# unicode function
|
||||
try:
|
||||
unicode = unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
# re-raise function
|
||||
if is_py3:
|
||||
def reraise(exception, traceback):
|
||||
raise exception.with_traceback(traceback)
|
||||
else:
|
||||
eval(compile("""
|
||||
def reraise(exception, traceback):
|
||||
raise exception, None, traceback
|
||||
""", 'blub', 'exec'))
|
||||
|
||||
reraise.__doc__ = """
|
||||
Re-raise `exception` with a `traceback` object.
|
||||
|
||||
Usage::
|
||||
|
||||
reraise(Exception, sys.exc_info()[2])
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class Python3Method(object):
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __get__(self, obj, objtype):
|
||||
if obj is None:
|
||||
return lambda *args, **kwargs: self.func(*args, **kwargs)
|
||||
else:
|
||||
return lambda *args, **kwargs: self.func(obj, *args, **kwargs)
|
||||
|
||||
|
||||
def use_metaclass(meta, *bases):
|
||||
""" Create a class with a metaclass. """
|
||||
if not bases:
|
||||
bases = (object,)
|
||||
return meta("Py2CompatibilityMetaClass", bases, {})
|
||||
|
||||
|
||||
try:
|
||||
encoding = sys.stdout.encoding
|
||||
if encoding is None:
|
||||
encoding = 'utf-8'
|
||||
except AttributeError:
|
||||
encoding = 'ascii'
|
||||
|
||||
|
||||
def u(string, errors='strict'):
|
||||
"""Cast to unicode DAMMIT!
|
||||
Written because Python2 repr always implicitly casts to a string, so we
|
||||
have to cast back to a unicode (and we now that we always deal with valid
|
||||
unicode, because we check that in the beginning).
|
||||
"""
|
||||
if isinstance(string, bytes):
|
||||
return unicode(string, encoding='UTF-8', errors=errors)
|
||||
return string
|
||||
|
||||
|
||||
def cast_path(obj):
|
||||
def cast_path(string):
|
||||
"""
|
||||
Take a bytes or str path and cast it to unicode.
|
||||
|
||||
@@ -311,157 +18,13 @@ def cast_path(obj):
|
||||
Since this just really complicates everything and Python 2.7 will be EOL
|
||||
soon anyway, just go with always strings.
|
||||
"""
|
||||
return u(obj, errors='replace')
|
||||
|
||||
|
||||
def force_unicode(obj):
|
||||
# Intentionally don't mix those two up, because those two code paths might
|
||||
# be different in the future (maybe windows?).
|
||||
return cast_path(obj)
|
||||
|
||||
|
||||
try:
|
||||
import builtins # module name in python 3
|
||||
except ImportError:
|
||||
import __builtin__ as builtins # noqa: F401
|
||||
|
||||
|
||||
import ast # noqa: F401
|
||||
|
||||
|
||||
def literal_eval(string):
|
||||
return ast.literal_eval(string)
|
||||
|
||||
|
||||
try:
|
||||
from itertools import zip_longest
|
||||
except ImportError:
|
||||
from itertools import izip_longest as zip_longest # Python 2 # noqa: F401
|
||||
|
||||
try:
|
||||
FileNotFoundError = FileNotFoundError
|
||||
except NameError:
|
||||
FileNotFoundError = IOError
|
||||
|
||||
try:
|
||||
NotADirectoryError = NotADirectoryError
|
||||
except NameError:
|
||||
NotADirectoryError = IOError
|
||||
|
||||
|
||||
def no_unicode_pprint(dct):
|
||||
"""
|
||||
Python 2/3 dict __repr__ may be different, because of unicode differens
|
||||
(with or without a `u` prefix). Normally in doctests we could use `pprint`
|
||||
to sort dicts and check for equality, but here we have to write a separate
|
||||
function to do that.
|
||||
"""
|
||||
import pprint
|
||||
s = pprint.pformat(dct)
|
||||
print(re.sub("u'", "'", s))
|
||||
|
||||
|
||||
def print_to_stderr(*args):
|
||||
if is_py3:
|
||||
eval("print(*args, file=sys.stderr)")
|
||||
else:
|
||||
print >> sys.stderr, args
|
||||
sys.stderr.flush()
|
||||
|
||||
|
||||
def utf8_repr(func):
|
||||
"""
|
||||
``__repr__`` methods in Python 2 don't allow unicode objects to be
|
||||
returned. Therefore cast them to utf-8 bytes in this decorator.
|
||||
"""
|
||||
def wrapper(self):
|
||||
result = func(self)
|
||||
if isinstance(result, unicode):
|
||||
return result.encode('utf-8')
|
||||
else:
|
||||
return result
|
||||
|
||||
if is_py3:
|
||||
return func
|
||||
else:
|
||||
return wrapper
|
||||
|
||||
|
||||
if is_py3:
|
||||
import queue
|
||||
else:
|
||||
import Queue as queue # noqa: F401
|
||||
|
||||
try:
|
||||
# Attempt to load the C implementation of pickle on Python 2 as it is way
|
||||
# faster.
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
if sys.version_info[:2] == (3, 3):
|
||||
"""
|
||||
Monkeypatch the unpickler in Python 3.3. This is needed, because the
|
||||
argument `encoding='bytes'` is not supported in 3.3, but badly needed to
|
||||
communicate with Python 2.
|
||||
"""
|
||||
|
||||
class NewUnpickler(pickle._Unpickler):
|
||||
dispatch = dict(pickle._Unpickler.dispatch)
|
||||
|
||||
def _decode_string(self, value):
|
||||
# Used to allow strings from Python 2 to be decoded either as
|
||||
# bytes or Unicode strings. This should be used only with the
|
||||
# STRING, BINSTRING and SHORT_BINSTRING opcodes.
|
||||
if self.encoding == "bytes":
|
||||
return value
|
||||
else:
|
||||
return value.decode(self.encoding, self.errors)
|
||||
|
||||
def load_string(self):
|
||||
data = self.readline()[:-1]
|
||||
# Strip outermost quotes
|
||||
if len(data) >= 2 and data[0] == data[-1] and data[0] in b'"\'':
|
||||
data = data[1:-1]
|
||||
else:
|
||||
raise pickle.UnpicklingError("the STRING opcode argument must be quoted")
|
||||
self.append(self._decode_string(pickle.codecs.escape_decode(data)[0]))
|
||||
dispatch[pickle.STRING[0]] = load_string
|
||||
|
||||
def load_binstring(self):
|
||||
# Deprecated BINSTRING uses signed 32-bit length
|
||||
len, = pickle.struct.unpack('<i', self.read(4))
|
||||
if len < 0:
|
||||
raise pickle.UnpicklingError("BINSTRING pickle has negative byte count")
|
||||
data = self.read(len)
|
||||
self.append(self._decode_string(data))
|
||||
dispatch[pickle.BINSTRING[0]] = load_binstring
|
||||
|
||||
def load_short_binstring(self):
|
||||
len = self.read(1)[0]
|
||||
data = self.read(len)
|
||||
self.append(self._decode_string(data))
|
||||
dispatch[pickle.SHORT_BINSTRING[0]] = load_short_binstring
|
||||
|
||||
def load(file, fix_imports=True, encoding="ASCII", errors="strict"):
|
||||
return NewUnpickler(file, fix_imports=fix_imports,
|
||||
encoding=encoding, errors=errors).load()
|
||||
|
||||
def loads(s, fix_imports=True, encoding="ASCII", errors="strict"):
|
||||
if isinstance(s, str):
|
||||
raise TypeError("Can't load pickle from unicode string")
|
||||
file = pickle.io.BytesIO(s)
|
||||
return NewUnpickler(file, fix_imports=fix_imports,
|
||||
encoding=encoding, errors=errors).load()
|
||||
|
||||
pickle.Unpickler = NewUnpickler
|
||||
pickle.load = load
|
||||
pickle.loads = loads
|
||||
if isinstance(string, bytes):
|
||||
return str(string, encoding='UTF-8', errors='replace')
|
||||
return str(string)
|
||||
|
||||
|
||||
def pickle_load(file):
|
||||
try:
|
||||
if is_py3:
|
||||
return pickle.load(file, encoding='bytes')
|
||||
return pickle.load(file)
|
||||
# Python on Windows don't throw EOF errors for pipes. So reraise them with
|
||||
# the correct type, which is caught upwards.
|
||||
@@ -483,107 +46,3 @@ def pickle_dump(data, file, protocol):
|
||||
if sys.platform == 'win32':
|
||||
raise IOError(errno.EPIPE, "Broken pipe")
|
||||
raise
|
||||
|
||||
|
||||
# Determine the highest protocol version compatible for a given list of Python
|
||||
# versions.
|
||||
def highest_pickle_protocol(python_versions):
|
||||
protocol = 4
|
||||
for version in python_versions:
|
||||
if version[0] == 2:
|
||||
# The minimum protocol version for the versions of Python that we
|
||||
# support (2.7 and 3.3+) is 2.
|
||||
return 2
|
||||
if version[1] < 4:
|
||||
protocol = 3
|
||||
return protocol
|
||||
|
||||
|
||||
try:
|
||||
from inspect import Parameter
|
||||
except ImportError:
|
||||
class Parameter(object):
|
||||
POSITIONAL_ONLY = object()
|
||||
POSITIONAL_OR_KEYWORD = object()
|
||||
VAR_POSITIONAL = object()
|
||||
KEYWORD_ONLY = object()
|
||||
VAR_KEYWORD = object()
|
||||
|
||||
|
||||
class GeneralizedPopen(subprocess.Popen):
|
||||
def __init__(self, *args, **kwargs):
|
||||
if os.name == 'nt':
|
||||
try:
|
||||
# Was introduced in Python 3.7.
|
||||
CREATE_NO_WINDOW = subprocess.CREATE_NO_WINDOW
|
||||
except AttributeError:
|
||||
CREATE_NO_WINDOW = 0x08000000
|
||||
kwargs['creationflags'] = CREATE_NO_WINDOW
|
||||
# The child process doesn't need file descriptors except 0, 1, 2.
|
||||
# This is unix only.
|
||||
kwargs['close_fds'] = 'posix' in sys.builtin_module_names
|
||||
super(GeneralizedPopen, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
# shutil.which is not available on Python 2.7.
|
||||
def which(cmd, mode=os.F_OK | os.X_OK, path=None):
|
||||
"""Given a command, mode, and a PATH string, return the path which
|
||||
conforms to the given mode on the PATH, or None if there is no such
|
||||
file.
|
||||
|
||||
`mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
|
||||
of os.environ.get("PATH"), or can be overridden with a custom search
|
||||
path.
|
||||
|
||||
"""
|
||||
# Check that a given file can be accessed with the correct mode.
|
||||
# Additionally check that `file` is not a directory, as on Windows
|
||||
# directories pass the os.access check.
|
||||
def _access_check(fn, mode):
|
||||
return (os.path.exists(fn) and os.access(fn, mode)
|
||||
and not os.path.isdir(fn))
|
||||
|
||||
# If we're given a path with a directory part, look it up directly rather
|
||||
# than referring to PATH directories. This includes checking relative to the
|
||||
# current directory, e.g. ./script
|
||||
if os.path.dirname(cmd):
|
||||
if _access_check(cmd, mode):
|
||||
return cmd
|
||||
return None
|
||||
|
||||
if path is None:
|
||||
path = os.environ.get("PATH", os.defpath)
|
||||
if not path:
|
||||
return None
|
||||
path = path.split(os.pathsep)
|
||||
|
||||
if sys.platform == "win32":
|
||||
# The current directory takes precedence on Windows.
|
||||
if os.curdir not in path:
|
||||
path.insert(0, os.curdir)
|
||||
|
||||
# PATHEXT is necessary to check on Windows.
|
||||
pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
|
||||
# See if the given file matches any of the expected path extensions.
|
||||
# This will allow us to short circuit when given "python.exe".
|
||||
# If it does match, only test that one, otherwise we have to try
|
||||
# others.
|
||||
if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
|
||||
files = [cmd]
|
||||
else:
|
||||
files = [cmd + ext for ext in pathext]
|
||||
else:
|
||||
# On other platforms you don't have things like PATHEXT to tell you
|
||||
# what file suffixes are executable, so just pass on cmd as-is.
|
||||
files = [cmd]
|
||||
|
||||
seen = set()
|
||||
for dir in path:
|
||||
normdir = os.path.normcase(dir)
|
||||
if normdir not in seen:
|
||||
seen.add(normdir)
|
||||
for thefile in files:
|
||||
name = os.path.join(dir, thefile)
|
||||
if _access_check(name, mode):
|
||||
return name
|
||||
return None
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,75 +1,108 @@
|
||||
import re
|
||||
from textwrap import dedent
|
||||
from inspect import Parameter
|
||||
|
||||
from parso.python.token import PythonTokenTypes
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor, Leaf
|
||||
from parso import split_lines
|
||||
|
||||
from jedi._compatibility import Parameter
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.api import classes
|
||||
from jedi.api import helpers
|
||||
from jedi.evaluate import imports
|
||||
from jedi.api import keywords
|
||||
from jedi.evaluate.helpers import evaluate_call_of_leaf
|
||||
from jedi.evaluate.filters import get_global_filters
|
||||
from jedi.parser_utils import get_statement_of_position
|
||||
from jedi.api.strings import complete_dict
|
||||
from jedi.api.file_name import complete_file_name
|
||||
from jedi.inference import imports
|
||||
from jedi.inference.base_value import ValueSet
|
||||
from jedi.inference.helpers import infer_call_of_leaf, parse_dotted_names
|
||||
from jedi.inference.context import get_global_filters
|
||||
from jedi.inference.value import TreeInstance, ModuleValue
|
||||
from jedi.inference.names import ParamNameWrapper, SubModuleName
|
||||
from jedi.inference.gradual.conversion import convert_values, convert_names
|
||||
from jedi.parser_utils import cut_value_at_position
|
||||
from jedi.plugins import plugin_manager
|
||||
|
||||
|
||||
def get_call_signature_param_names(call_signatures):
|
||||
# add named params
|
||||
for call_sig in call_signatures:
|
||||
for p in call_sig.params:
|
||||
# Allow protected access, because it's a public API.
|
||||
if p._name.get_kind() in (Parameter.POSITIONAL_OR_KEYWORD,
|
||||
Parameter.KEYWORD_ONLY):
|
||||
yield p._name
|
||||
class ParamNameWithEquals(ParamNameWrapper):
|
||||
def get_public_name(self):
|
||||
return self.string_name + '='
|
||||
|
||||
|
||||
def filter_names(evaluator, completion_names, stack, like_name):
|
||||
comp_dct = {}
|
||||
def _get_signature_param_names(signatures, positional_count, used_kwargs):
|
||||
# Add named params
|
||||
for call_sig in signatures:
|
||||
for i, p in enumerate(call_sig.params):
|
||||
kind = p.kind
|
||||
if i < positional_count and kind == Parameter.POSITIONAL_OR_KEYWORD:
|
||||
continue
|
||||
if kind in (Parameter.POSITIONAL_OR_KEYWORD, Parameter.KEYWORD_ONLY) \
|
||||
and p.name not in used_kwargs:
|
||||
yield ParamNameWithEquals(p._name)
|
||||
|
||||
|
||||
def _must_be_kwarg(signatures, positional_count, used_kwargs):
|
||||
if used_kwargs:
|
||||
return True
|
||||
|
||||
must_be_kwarg = True
|
||||
for signature in signatures:
|
||||
for i, p in enumerate(signature.params):
|
||||
kind = p.kind
|
||||
if kind is Parameter.VAR_POSITIONAL:
|
||||
# In case there were not already kwargs, the next param can
|
||||
# always be a normal argument.
|
||||
return False
|
||||
|
||||
if i >= positional_count and kind in (Parameter.POSITIONAL_OR_KEYWORD,
|
||||
Parameter.POSITIONAL_ONLY):
|
||||
must_be_kwarg = False
|
||||
break
|
||||
if not must_be_kwarg:
|
||||
break
|
||||
return must_be_kwarg
|
||||
|
||||
|
||||
def filter_names(inference_state, completion_names, stack, like_name, fuzzy, cached_name):
|
||||
comp_dct = set()
|
||||
if settings.case_insensitive_completion:
|
||||
like_name = like_name.lower()
|
||||
for name in completion_names:
|
||||
string = name.string_name
|
||||
if settings.case_insensitive_completion:
|
||||
string = string.lower()
|
||||
|
||||
if string.startswith(like_name):
|
||||
if helpers.match(string, like_name, fuzzy=fuzzy):
|
||||
new = classes.Completion(
|
||||
evaluator,
|
||||
inference_state,
|
||||
name,
|
||||
stack,
|
||||
len(like_name)
|
||||
len(like_name),
|
||||
is_fuzzy=fuzzy,
|
||||
cached_name=cached_name,
|
||||
)
|
||||
k = (new.name, new.complete) # key
|
||||
if k in comp_dct and settings.no_completion_duplicates:
|
||||
comp_dct[k]._same_name_completions.append(new)
|
||||
else:
|
||||
comp_dct[k] = new
|
||||
if k not in comp_dct:
|
||||
comp_dct.add(k)
|
||||
tree_name = name.tree_name
|
||||
if tree_name is not None:
|
||||
definition = tree_name.get_definition()
|
||||
if definition is not None and definition.type == 'del_stmt':
|
||||
continue
|
||||
yield new
|
||||
|
||||
|
||||
def get_user_scope(module_context, position):
|
||||
def _remove_duplicates(completions, other_completions):
|
||||
names = {d.name for d in other_completions}
|
||||
return [c for c in completions if c.name not in names]
|
||||
|
||||
|
||||
def get_user_context(module_context, position):
|
||||
"""
|
||||
Returns the scope in which the user resides. This includes flows.
|
||||
"""
|
||||
user_stmt = get_statement_of_position(module_context.tree_node, position)
|
||||
if user_stmt is None:
|
||||
def scan(scope):
|
||||
for s in scope.children:
|
||||
if s.start_pos <= position <= s.end_pos:
|
||||
if isinstance(s, (tree.Scope, tree.Flow)) \
|
||||
or s.type in ('async_stmt', 'async_funcdef'):
|
||||
return scan(s) or s
|
||||
elif s.type in ('suite', 'decorated'):
|
||||
return scan(s)
|
||||
return None
|
||||
|
||||
scanned_node = scan(module_context.tree_node)
|
||||
if scanned_node:
|
||||
return module_context.create_context(scanned_node, node_is_context=True)
|
||||
return module_context
|
||||
else:
|
||||
return module_context.create_context(user_stmt)
|
||||
leaf = module_context.tree_node.get_leaf_for_position(position, include_prefixes=True)
|
||||
return module_context.create_context(leaf)
|
||||
|
||||
|
||||
def get_flow_scope_node(module_node, position):
|
||||
@@ -80,33 +113,76 @@ def get_flow_scope_node(module_node, position):
|
||||
return node
|
||||
|
||||
|
||||
@plugin_manager.decorate()
|
||||
def complete_param_names(context, function_name, decorator_nodes):
|
||||
# Basically there's no way to do param completion. The plugins are
|
||||
# responsible for this.
|
||||
return []
|
||||
|
||||
|
||||
class Completion:
|
||||
def __init__(self, evaluator, module, code_lines, position, call_signatures_method):
|
||||
self._evaluator = evaluator
|
||||
self._module_context = module
|
||||
self._module_node = module.tree_node
|
||||
def __init__(self, inference_state, module_context, code_lines, position,
|
||||
signatures_callback, fuzzy=False):
|
||||
self._inference_state = inference_state
|
||||
self._module_context = module_context
|
||||
self._module_node = module_context.tree_node
|
||||
self._code_lines = code_lines
|
||||
|
||||
# The first step of completions is to get the name
|
||||
self._like_name = helpers.get_on_completion_name(self._module_node, code_lines, position)
|
||||
# The actual cursor position is not what we need to calculate
|
||||
# everything. We want the start of the name we're on.
|
||||
self._position = position[0], position[1] - len(self._like_name)
|
||||
self._call_signatures_method = call_signatures_method
|
||||
self._original_position = position
|
||||
self._signatures_callback = signatures_callback
|
||||
|
||||
def completions(self):
|
||||
completion_names = self._get_context_completions()
|
||||
self._fuzzy = fuzzy
|
||||
|
||||
completions = filter_names(self._evaluator, completion_names,
|
||||
self.stack, self._like_name)
|
||||
def complete(self):
|
||||
leaf = self._module_node.get_leaf_for_position(
|
||||
self._original_position,
|
||||
include_prefixes=True
|
||||
)
|
||||
string, start_leaf, quote = _extract_string_while_in_string(leaf, self._original_position)
|
||||
|
||||
return sorted(completions, key=lambda x: (x.name.startswith('__'),
|
||||
x.name.startswith('_'),
|
||||
x.name.lower()))
|
||||
prefixed_completions = complete_dict(
|
||||
self._module_context,
|
||||
self._code_lines,
|
||||
start_leaf or leaf,
|
||||
self._original_position,
|
||||
None if string is None else quote + string,
|
||||
fuzzy=self._fuzzy,
|
||||
)
|
||||
|
||||
def _get_context_completions(self):
|
||||
if string is not None and not prefixed_completions:
|
||||
prefixed_completions = list(complete_file_name(
|
||||
self._inference_state, self._module_context, start_leaf, quote, string,
|
||||
self._like_name, self._signatures_callback,
|
||||
self._code_lines, self._original_position,
|
||||
self._fuzzy
|
||||
))
|
||||
if string is not None:
|
||||
if not prefixed_completions and '\n' in string:
|
||||
# Complete only multi line strings
|
||||
prefixed_completions = self._complete_in_string(start_leaf, string)
|
||||
return prefixed_completions
|
||||
|
||||
cached_name, completion_names = self._complete_python(leaf)
|
||||
|
||||
completions = list(filter_names(self._inference_state, completion_names,
|
||||
self.stack, self._like_name,
|
||||
self._fuzzy, cached_name=cached_name))
|
||||
|
||||
return (
|
||||
# Removing duplicates mostly to remove False/True/None duplicates.
|
||||
_remove_duplicates(prefixed_completions, completions)
|
||||
+ sorted(completions, key=lambda x: (x.name.startswith('__'),
|
||||
x.name.startswith('_'),
|
||||
x.name.lower()))
|
||||
)
|
||||
|
||||
def _complete_python(self, leaf):
|
||||
"""
|
||||
Analyzes the context that a completion is made in and decides what to
|
||||
Analyzes the current context of a completion and decides what to
|
||||
return.
|
||||
|
||||
Technically this works by generating a parser stack and analysing the
|
||||
@@ -119,21 +195,27 @@ class Completion:
|
||||
- In params (also lambda): no completion before =
|
||||
"""
|
||||
|
||||
grammar = self._evaluator.grammar
|
||||
grammar = self._inference_state.grammar
|
||||
self.stack = stack = None
|
||||
self._position = (
|
||||
self._original_position[0],
|
||||
self._original_position[1] - len(self._like_name)
|
||||
)
|
||||
cached_name = 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.
|
||||
return cached_name, []
|
||||
|
||||
return self._global_completions()
|
||||
# If we don't have a value, just use global completion.
|
||||
return cached_name, self._complete_global_scope()
|
||||
|
||||
allowed_transitions = \
|
||||
list(stack._allowed_transition_names_and_token_types())
|
||||
@@ -170,22 +252,22 @@ class Completion:
|
||||
elif type_ == 'for_stmt':
|
||||
allowed_transitions.append('else')
|
||||
|
||||
completion_names = list(self._get_keyword_completion_names(allowed_transitions))
|
||||
completion_names = []
|
||||
|
||||
kwargs_only = False
|
||||
if any(t in allowed_transitions for t in (PythonTokenTypes.NAME,
|
||||
PythonTokenTypes.INDENT)):
|
||||
# This means that we actually have to do type inference.
|
||||
|
||||
nonterminals = [stack_node.nonterminal for stack_node in stack]
|
||||
|
||||
nodes = [node for stack_node in stack for node in stack_node.nodes]
|
||||
|
||||
nodes = _gather_nodes(stack)
|
||||
if nodes and nodes[-1] in ('as', 'def', 'class'):
|
||||
# No completions for ``with x as foo`` and ``import x as foo``.
|
||||
# Also true for defining names as a class or function.
|
||||
return list(self._get_class_context_completions(is_function=True))
|
||||
return cached_name, list(self._complete_inherited(is_function=True))
|
||||
elif "import_stmt" in nonterminals:
|
||||
level, names = self._parse_dotted_names(nodes, "import_from" in nonterminals)
|
||||
level, names = parse_dotted_names(nodes, "import_from" in nonterminals)
|
||||
|
||||
only_modules = not ("import_from" in nonterminals and 'import' in nodes)
|
||||
completion_names += self._get_importer_names(
|
||||
@@ -195,98 +277,386 @@ class Completion:
|
||||
)
|
||||
elif nonterminals[-1] in ('trailer', 'dotted_name') and nodes[-1] == '.':
|
||||
dot = self._module_node.get_leaf_for_position(self._position)
|
||||
completion_names += self._trailer_completions(dot.get_previous_leaf())
|
||||
cached_name, n = self._complete_trailer(dot.get_previous_leaf())
|
||||
completion_names += n
|
||||
elif self._is_parameter_completion():
|
||||
completion_names += self._complete_params(leaf)
|
||||
else:
|
||||
completion_names += self._global_completions()
|
||||
completion_names += self._get_class_context_completions(is_function=False)
|
||||
# Apparently this looks like it's good enough to filter most cases
|
||||
# so that signature completions don't randomly appear.
|
||||
# To understand why this works, three things are important:
|
||||
# 1. trailer with a `,` in it is either a subscript or an arglist.
|
||||
# 2. If there's no `,`, it's at the start and only signatures start
|
||||
# with `(`. Other trailers could start with `.` or `[`.
|
||||
# 3. Decorators are very primitive and have an optional `(` with
|
||||
# optional arglist in them.
|
||||
if nodes[-1] in ['(', ','] \
|
||||
and nonterminals[-1] in ('trailer', 'arglist', 'decorator'):
|
||||
signatures = self._signatures_callback(*self._position)
|
||||
if signatures:
|
||||
call_details = signatures[0]._call_details
|
||||
used_kwargs = list(call_details.iter_used_keyword_arguments())
|
||||
positional_count = call_details.count_positional_arguments()
|
||||
|
||||
if 'trailer' in nonterminals:
|
||||
call_signatures = self._call_signatures_method()
|
||||
completion_names += get_call_signature_param_names(call_signatures)
|
||||
completion_names += _get_signature_param_names(
|
||||
signatures,
|
||||
positional_count,
|
||||
used_kwargs,
|
||||
)
|
||||
|
||||
return completion_names
|
||||
kwargs_only = _must_be_kwarg(signatures, positional_count, used_kwargs)
|
||||
|
||||
def _get_keyword_completion_names(self, allowed_transitions):
|
||||
if not kwargs_only:
|
||||
completion_names += self._complete_global_scope()
|
||||
completion_names += self._complete_inherited(is_function=False)
|
||||
|
||||
if not kwargs_only:
|
||||
current_line = self._code_lines[self._position[0] - 1][:self._position[1]]
|
||||
completion_names += self._complete_keywords(
|
||||
allowed_transitions,
|
||||
only_values=not (not current_line or current_line[-1] in ' \t.;'
|
||||
and current_line[-3:] != '...')
|
||||
)
|
||||
|
||||
return cached_name, completion_names
|
||||
|
||||
def _is_parameter_completion(self):
|
||||
tos = self.stack[-1]
|
||||
if tos.nonterminal == 'lambdef' and len(tos.nodes) == 1:
|
||||
# We are at the position `lambda `, where basically the next node
|
||||
# is a param.
|
||||
return True
|
||||
if tos.nonterminal in 'parameters':
|
||||
# Basically we are at the position `foo(`, there's nothing there
|
||||
# yet, so we have no `typedargslist`.
|
||||
return True
|
||||
# var args is for lambdas and typed args for normal functions
|
||||
return tos.nonterminal in ('typedargslist', 'varargslist') and tos.nodes[-1] == ','
|
||||
|
||||
def _complete_params(self, leaf):
|
||||
stack_node = self.stack[-2]
|
||||
if stack_node.nonterminal == 'parameters':
|
||||
stack_node = self.stack[-3]
|
||||
if stack_node.nonterminal == 'funcdef':
|
||||
context = get_user_context(self._module_context, self._position)
|
||||
node = search_ancestor(leaf, 'error_node', 'funcdef')
|
||||
if node is not None:
|
||||
if node.type == 'error_node':
|
||||
n = node.children[0]
|
||||
if n.type == 'decorators':
|
||||
decorators = n.children
|
||||
elif n.type == 'decorator':
|
||||
decorators = [n]
|
||||
else:
|
||||
decorators = []
|
||||
else:
|
||||
decorators = node.get_decorators()
|
||||
function_name = stack_node.nodes[1]
|
||||
|
||||
return complete_param_names(context, function_name.value, decorators)
|
||||
return []
|
||||
|
||||
def _complete_keywords(self, allowed_transitions, only_values):
|
||||
for k in allowed_transitions:
|
||||
if isinstance(k, str) and k.isalpha():
|
||||
yield keywords.KeywordName(self._evaluator, k)
|
||||
if not only_values or k in ('True', 'False', 'None'):
|
||||
yield keywords.KeywordName(self._inference_state, k)
|
||||
|
||||
def _global_completions(self):
|
||||
context = get_user_scope(self._module_context, self._position)
|
||||
def _complete_global_scope(self):
|
||||
context = get_user_context(self._module_context, self._position)
|
||||
debug.dbg('global completion scope: %s', context)
|
||||
flow_scope_node = get_flow_scope_node(self._module_node, self._position)
|
||||
filters = get_global_filters(
|
||||
self._evaluator,
|
||||
context,
|
||||
self._position,
|
||||
origin_scope=flow_scope_node
|
||||
flow_scope_node
|
||||
)
|
||||
completion_names = []
|
||||
for filter in filters:
|
||||
completion_names += filter.values()
|
||||
return completion_names
|
||||
|
||||
def _trailer_completions(self, previous_leaf):
|
||||
user_context = get_user_scope(self._module_context, self._position)
|
||||
evaluation_context = self._evaluator.create_context(
|
||||
self._module_context, previous_leaf
|
||||
)
|
||||
contexts = evaluate_call_of_leaf(evaluation_context, previous_leaf)
|
||||
completion_names = []
|
||||
debug.dbg('trailer completion contexts: %s', contexts)
|
||||
for context in contexts:
|
||||
for filter in context.get_filters(
|
||||
search_global=False, origin_scope=user_context.tree_node):
|
||||
completion_names += filter.values()
|
||||
return completion_names
|
||||
def _complete_trailer(self, previous_leaf):
|
||||
inferred_context = self._module_context.create_context(previous_leaf)
|
||||
values = infer_call_of_leaf(inferred_context, previous_leaf)
|
||||
debug.dbg('trailer completion values: %s', values, color='MAGENTA')
|
||||
|
||||
def _parse_dotted_names(self, nodes, is_import_from):
|
||||
level = 0
|
||||
names = []
|
||||
for node in nodes[1:]:
|
||||
if node in ('.', '...'):
|
||||
if not names:
|
||||
level += len(node.value)
|
||||
elif node.type == 'dotted_name':
|
||||
names += node.children[::2]
|
||||
elif node.type == 'name':
|
||||
names.append(node)
|
||||
elif node == ',':
|
||||
if not is_import_from:
|
||||
names = []
|
||||
else:
|
||||
# Here if the keyword `import` comes along it stops checking
|
||||
# for names.
|
||||
break
|
||||
return level, names
|
||||
# The cached name simply exists to make speed optimizations for certain
|
||||
# modules.
|
||||
cached_name = None
|
||||
if len(values) == 1:
|
||||
v, = values
|
||||
if v.is_module():
|
||||
if len(v.string_names) == 1:
|
||||
module_name = v.string_names[0]
|
||||
if module_name in ('numpy', 'tensorflow', 'matplotlib', 'pandas'):
|
||||
cached_name = module_name
|
||||
|
||||
return cached_name, self._complete_trailer_for_values(values)
|
||||
|
||||
def _complete_trailer_for_values(self, values):
|
||||
user_context = get_user_context(self._module_context, self._position)
|
||||
|
||||
return complete_trailer(user_context, values)
|
||||
|
||||
def _get_importer_names(self, names, level=0, only_modules=True):
|
||||
names = [n.value for n in names]
|
||||
i = imports.Importer(self._evaluator, names, self._module_context, level)
|
||||
return i.completion_names(self._evaluator, only_modules=only_modules)
|
||||
i = imports.Importer(self._inference_state, names, self._module_context, level)
|
||||
return i.completion_names(self._inference_state, only_modules=only_modules)
|
||||
|
||||
def _get_class_context_completions(self, is_function=True):
|
||||
def _complete_inherited(self, is_function=True):
|
||||
"""
|
||||
Autocomplete inherited methods when overriding in child class.
|
||||
"""
|
||||
leaf = self._module_node.get_leaf_for_position(self._position, include_prefixes=True)
|
||||
cls = tree.search_ancestor(leaf, 'classdef')
|
||||
if isinstance(cls, (tree.Class, tree.Function)):
|
||||
# Complete the methods that are defined in the super classes.
|
||||
random_context = self._module_context.create_context(
|
||||
cls,
|
||||
node_is_context=True
|
||||
)
|
||||
else:
|
||||
if cls is None:
|
||||
return
|
||||
|
||||
# Complete the methods that are defined in the super classes.
|
||||
class_value = self._module_context.create_value(cls)
|
||||
|
||||
if cls.start_pos[1] >= leaf.start_pos[1]:
|
||||
return
|
||||
|
||||
filters = random_context.get_filters(search_global=False, is_instance=True)
|
||||
filters = class_value.get_filters(is_instance=True)
|
||||
# The first dict is the dictionary of class itself.
|
||||
next(filters)
|
||||
for filter in filters:
|
||||
for name in filter.values():
|
||||
# TODO we should probably check here for properties
|
||||
if (name.api_type == 'function') == is_function:
|
||||
yield name
|
||||
|
||||
def _complete_in_string(self, start_leaf, string):
|
||||
"""
|
||||
To make it possible for people to have completions in doctests or
|
||||
generally in "Python" code in docstrings, we use the following
|
||||
heuristic:
|
||||
|
||||
- Having an indented block of code
|
||||
- Having some doctest code that starts with `>>>`
|
||||
- Having backticks that doesn't have whitespace inside it
|
||||
"""
|
||||
def iter_relevant_lines(lines):
|
||||
include_next_line = False
|
||||
for l in code_lines:
|
||||
if include_next_line or l.startswith('>>>') or l.startswith(' '):
|
||||
yield re.sub(r'^( *>>> ?| +)', '', l)
|
||||
else:
|
||||
yield None
|
||||
|
||||
include_next_line = bool(re.match(' *>>>', l))
|
||||
|
||||
string = dedent(string)
|
||||
code_lines = split_lines(string, keepends=True)
|
||||
relevant_code_lines = list(iter_relevant_lines(code_lines))
|
||||
if relevant_code_lines[-1] is not None:
|
||||
# Some code lines might be None, therefore get rid of that.
|
||||
relevant_code_lines = ['\n' if c is None else c for c in relevant_code_lines]
|
||||
return self._complete_code_lines(relevant_code_lines)
|
||||
match = re.search(r'`([^`\s]+)', code_lines[-1])
|
||||
if match:
|
||||
return self._complete_code_lines([match.group(1)])
|
||||
return []
|
||||
|
||||
def _complete_code_lines(self, code_lines):
|
||||
module_node = self._inference_state.grammar.parse(''.join(code_lines))
|
||||
module_value = ModuleValue(
|
||||
self._inference_state,
|
||||
module_node,
|
||||
code_lines=code_lines,
|
||||
)
|
||||
module_value.parent_context = self._module_context
|
||||
return Completion(
|
||||
self._inference_state,
|
||||
module_value.as_context(),
|
||||
code_lines=code_lines,
|
||||
position=module_node.end_pos,
|
||||
signatures_callback=lambda *args, **kwargs: [],
|
||||
fuzzy=self._fuzzy
|
||||
).complete()
|
||||
|
||||
|
||||
def _gather_nodes(stack):
|
||||
nodes = []
|
||||
for stack_node in stack:
|
||||
if stack_node.dfa.from_rule == 'small_stmt':
|
||||
nodes = []
|
||||
else:
|
||||
nodes += stack_node.nodes
|
||||
return nodes
|
||||
|
||||
|
||||
_string_start = re.compile(r'^\w*(\'{3}|"{3}|\'|")')
|
||||
|
||||
|
||||
def _extract_string_while_in_string(leaf, position):
|
||||
def return_part_of_leaf(leaf):
|
||||
kwargs = {}
|
||||
if leaf.line == position[0]:
|
||||
kwargs['endpos'] = position[1] - leaf.column
|
||||
match = _string_start.match(leaf.value, **kwargs)
|
||||
if not match:
|
||||
return None, None, None
|
||||
start = match.group(0)
|
||||
if leaf.line == position[0] and position[1] < leaf.column + match.end():
|
||||
return None, None, None
|
||||
return cut_value_at_position(leaf, position)[match.end():], leaf, start
|
||||
|
||||
if position < leaf.start_pos:
|
||||
return None, None, None
|
||||
|
||||
if leaf.type == 'string':
|
||||
return return_part_of_leaf(leaf)
|
||||
|
||||
leaves = []
|
||||
while leaf is not None:
|
||||
if leaf.type == 'error_leaf' and ('"' in leaf.value or "'" in leaf.value):
|
||||
if len(leaf.value) > 1:
|
||||
return return_part_of_leaf(leaf)
|
||||
prefix_leaf = None
|
||||
if not leaf.prefix:
|
||||
prefix_leaf = leaf.get_previous_leaf()
|
||||
if prefix_leaf is None or prefix_leaf.type != 'name' \
|
||||
or not all(c in 'rubf' for c in prefix_leaf.value.lower()):
|
||||
prefix_leaf = None
|
||||
|
||||
return (
|
||||
''.join(cut_value_at_position(l, position) for l in leaves),
|
||||
prefix_leaf or leaf,
|
||||
('' if prefix_leaf is None else prefix_leaf.value)
|
||||
+ cut_value_at_position(leaf, position),
|
||||
)
|
||||
if leaf.line != position[0]:
|
||||
# Multi line strings are always simple error leaves and contain the
|
||||
# whole string, single line error leaves are atherefore important
|
||||
# now and since the line is different, it's not really a single
|
||||
# line string anymore.
|
||||
break
|
||||
leaves.insert(0, leaf)
|
||||
leaf = leaf.get_previous_leaf()
|
||||
return None, None, None
|
||||
|
||||
|
||||
def complete_trailer(user_context, values):
|
||||
completion_names = []
|
||||
for value in values:
|
||||
for filter in value.get_filters(origin_scope=user_context.tree_node):
|
||||
completion_names += filter.values()
|
||||
|
||||
if not value.is_stub() and isinstance(value, TreeInstance):
|
||||
completion_names += _complete_getattr(user_context, value)
|
||||
|
||||
python_values = convert_values(values)
|
||||
for c in python_values:
|
||||
if c not in values:
|
||||
for filter in c.get_filters(origin_scope=user_context.tree_node):
|
||||
completion_names += filter.values()
|
||||
return completion_names
|
||||
|
||||
|
||||
def _complete_getattr(user_context, instance):
|
||||
"""
|
||||
A heuristic to make completion for proxy objects work. This is not
|
||||
intended to work in all cases. It works exactly in this case:
|
||||
|
||||
def __getattr__(self, name):
|
||||
...
|
||||
return getattr(any_object, name)
|
||||
|
||||
It is important that the return contains getattr directly, otherwise it
|
||||
won't work anymore. It's really just a stupid heuristic. It will not
|
||||
work if you write e.g. `return (getatr(o, name))`, because of the
|
||||
additional parentheses. It will also not work if you move the getattr
|
||||
to some other place that is not the return statement itself.
|
||||
|
||||
It is intentional that it doesn't work in all cases. Generally it's
|
||||
really hard to do even this case (as you can see below). Most people
|
||||
will write it like this anyway and the other ones, well they are just
|
||||
out of luck I guess :) ~dave.
|
||||
"""
|
||||
names = (instance.get_function_slot_names('__getattr__')
|
||||
or instance.get_function_slot_names('__getattribute__'))
|
||||
functions = ValueSet.from_sets(
|
||||
name.infer()
|
||||
for name in names
|
||||
)
|
||||
for func in functions:
|
||||
tree_node = func.tree_node
|
||||
if tree_node is None or tree_node.type != 'funcdef':
|
||||
continue
|
||||
|
||||
for return_stmt in tree_node.iter_return_stmts():
|
||||
# Basically until the next comment we just try to find out if a
|
||||
# return statement looks exactly like `return getattr(x, name)`.
|
||||
if return_stmt.type != 'return_stmt':
|
||||
continue
|
||||
atom_expr = return_stmt.children[1]
|
||||
if atom_expr.type != 'atom_expr':
|
||||
continue
|
||||
atom = atom_expr.children[0]
|
||||
trailer = atom_expr.children[1]
|
||||
if len(atom_expr.children) != 2 or atom.type != 'name' \
|
||||
or atom.value != 'getattr':
|
||||
continue
|
||||
arglist = trailer.children[1]
|
||||
if arglist.type != 'arglist' or len(arglist.children) < 3:
|
||||
continue
|
||||
context = func.as_context()
|
||||
object_node = arglist.children[0]
|
||||
|
||||
# Make sure it's a param: foo in __getattr__(self, foo)
|
||||
name_node = arglist.children[2]
|
||||
name_list = context.goto(name_node, name_node.start_pos)
|
||||
if not any(n.api_type == 'param' for n in name_list):
|
||||
continue
|
||||
|
||||
# Now that we know that these are most probably completion
|
||||
# objects, we just infer the object and return them as
|
||||
# completions.
|
||||
objects = context.infer_node(object_node)
|
||||
return complete_trailer(user_context, objects)
|
||||
return []
|
||||
|
||||
|
||||
def search_in_module(inference_state, module_context, names, wanted_names,
|
||||
wanted_type, complete=False, fuzzy=False,
|
||||
ignore_imports=False, convert=False):
|
||||
for s in wanted_names[:-1]:
|
||||
new_names = []
|
||||
for n in names:
|
||||
if s == n.string_name:
|
||||
if n.tree_name is not None and n.api_type == 'module' \
|
||||
and ignore_imports:
|
||||
continue
|
||||
new_names += complete_trailer(
|
||||
module_context,
|
||||
n.infer()
|
||||
)
|
||||
debug.dbg('dot lookup on search %s from %s', new_names, names[:10])
|
||||
names = new_names
|
||||
|
||||
last_name = wanted_names[-1].lower()
|
||||
for n in names:
|
||||
string = n.string_name.lower()
|
||||
if complete and helpers.match(string, last_name, fuzzy=fuzzy) \
|
||||
or not complete and string == last_name:
|
||||
if isinstance(n, SubModuleName):
|
||||
names = [v.name for v in n.infer()]
|
||||
else:
|
||||
names = [n]
|
||||
if convert:
|
||||
names = convert_names(names)
|
||||
for n2 in names:
|
||||
if complete:
|
||||
def_ = classes.Completion(
|
||||
inference_state, n2,
|
||||
stack=None,
|
||||
like_name_length=len(last_name),
|
||||
is_fuzzy=fuzzy,
|
||||
)
|
||||
else:
|
||||
def_ = classes.Name(inference_state, n2)
|
||||
if not wanted_type or wanted_type == def_.type:
|
||||
yield def_
|
||||
|
||||
31
jedi/api/completion_cache.py
Normal file
31
jedi/api/completion_cache.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from typing import Dict, Tuple, Callable
|
||||
|
||||
CacheValues = Tuple[str, str, str]
|
||||
CacheValuesCallback = Callable[[], CacheValues]
|
||||
|
||||
|
||||
_cache: Dict[str, Dict[str, CacheValues]] = {}
|
||||
|
||||
|
||||
def save_entry(module_name: str, name: str, cache: CacheValues) -> None:
|
||||
try:
|
||||
module_cache = _cache[module_name]
|
||||
except KeyError:
|
||||
module_cache = _cache[module_name] = {}
|
||||
module_cache[name] = cache
|
||||
|
||||
|
||||
def _create_get_from_cache(number: int) -> Callable[[str, str, CacheValuesCallback], str]:
|
||||
def _get_from_cache(module_name: str, name: str, get_cache_values: CacheValuesCallback) -> str:
|
||||
try:
|
||||
return _cache[module_name][name][number]
|
||||
except KeyError:
|
||||
v = get_cache_values()
|
||||
save_entry(module_name, name, v)
|
||||
return v[number]
|
||||
return _get_from_cache
|
||||
|
||||
|
||||
get_type = _create_get_from_cache(0)
|
||||
get_docstring_signature = _create_get_from_cache(1)
|
||||
get_docstring = _create_get_from_cache(2)
|
||||
@@ -7,18 +7,19 @@ import sys
|
||||
import hashlib
|
||||
import filecmp
|
||||
from collections import namedtuple
|
||||
from shutil import which
|
||||
|
||||
from jedi._compatibility import highest_pickle_protocol, which
|
||||
from jedi.cache import memoize_method, time_cache
|
||||
from jedi.evaluate.compiled.subprocess import CompiledSubprocess, \
|
||||
EvaluatorSameProcess, EvaluatorSubprocess
|
||||
from jedi.inference.compiled.subprocess import CompiledSubprocess, \
|
||||
InferenceStateSameProcess, InferenceStateSubprocess
|
||||
|
||||
import parso
|
||||
|
||||
_VersionInfo = namedtuple('VersionInfo', 'major minor micro')
|
||||
|
||||
_SUPPORTED_PYTHONS = ['3.7', '3.6', '3.5', '3.4', '3.3', '2.7']
|
||||
_SUPPORTED_PYTHONS = ['3.9', '3.8', '3.7', '3.6']
|
||||
_SAFE_PATHS = ['/usr/bin', '/usr/local/bin']
|
||||
_CONDA_VAR = 'CONDA_PREFIX'
|
||||
_CURRENT_VERSION = '%s.%s' % (sys.version_info.major, sys.version_info.minor)
|
||||
|
||||
|
||||
@@ -29,7 +30,7 @@ class InvalidPythonEnvironment(Exception):
|
||||
"""
|
||||
|
||||
|
||||
class _BaseEnvironment(object):
|
||||
class _BaseEnvironment:
|
||||
@memoize_method
|
||||
def get_grammar(self):
|
||||
version_string = '%s.%s' % (self.version_info.major, self.version_info.minor)
|
||||
@@ -60,8 +61,9 @@ class Environment(_BaseEnvironment):
|
||||
"""
|
||||
_subprocess = None
|
||||
|
||||
def __init__(self, executable):
|
||||
def __init__(self, executable, env_vars=None):
|
||||
self._start_executable = executable
|
||||
self._env_vars = env_vars
|
||||
# Initialize the environment
|
||||
self._get_subprocess()
|
||||
|
||||
@@ -70,7 +72,8 @@ class Environment(_BaseEnvironment):
|
||||
return self._subprocess
|
||||
|
||||
try:
|
||||
self._subprocess = CompiledSubprocess(self._start_executable)
|
||||
self._subprocess = CompiledSubprocess(self._start_executable,
|
||||
env_vars=self._env_vars)
|
||||
info = self._subprocess._send(None, _get_info)
|
||||
except Exception as exc:
|
||||
raise InvalidPythonEnvironment(
|
||||
@@ -90,33 +93,23 @@ class Environment(_BaseEnvironment):
|
||||
"""
|
||||
self.version_info = _VersionInfo(*info[2])
|
||||
"""
|
||||
Like ``sys.version_info``. A tuple to show the current Environment's
|
||||
Python version.
|
||||
Like :data:`sys.version_info`: a tuple to show the current
|
||||
Environment's Python version.
|
||||
"""
|
||||
|
||||
# py2 sends bytes via pickle apparently?!
|
||||
if self.version_info.major == 2:
|
||||
self.executable = self.executable.decode()
|
||||
self.path = self.path.decode()
|
||||
|
||||
# Adjust pickle protocol according to host and client version.
|
||||
self._subprocess._pickle_protocol = highest_pickle_protocol([
|
||||
sys.version_info, self.version_info])
|
||||
|
||||
return self._subprocess
|
||||
|
||||
def __repr__(self):
|
||||
version = '.'.join(str(i) for i in self.version_info)
|
||||
return '<%s: %s in %s>' % (self.__class__.__name__, version, self.path)
|
||||
|
||||
def get_evaluator_subprocess(self, evaluator):
|
||||
return EvaluatorSubprocess(evaluator, self._get_subprocess())
|
||||
def get_inference_state_subprocess(self, inference_state):
|
||||
return InferenceStateSubprocess(inference_state, self._get_subprocess())
|
||||
|
||||
@memoize_method
|
||||
def get_sys_path(self):
|
||||
"""
|
||||
The sys path for this environment. Does not include potential
|
||||
modifications like ``sys.path.append``.
|
||||
modifications from e.g. appending to :data:`sys.path`.
|
||||
|
||||
:returns: list of str
|
||||
"""
|
||||
@@ -128,34 +121,40 @@ class Environment(_BaseEnvironment):
|
||||
return self._get_subprocess().get_sys_path()
|
||||
|
||||
|
||||
class SameEnvironment(Environment):
|
||||
class _SameEnvironmentMixin:
|
||||
def __init__(self):
|
||||
self._start_executable = self.executable = sys.executable
|
||||
self.path = sys.prefix
|
||||
self.version_info = _VersionInfo(*sys.version_info[:3])
|
||||
self._env_vars = None
|
||||
|
||||
|
||||
class InterpreterEnvironment(_BaseEnvironment):
|
||||
def __init__(self):
|
||||
self.version_info = _VersionInfo(*sys.version_info[:3])
|
||||
class SameEnvironment(_SameEnvironmentMixin, Environment):
|
||||
pass
|
||||
|
||||
def get_evaluator_subprocess(self, evaluator):
|
||||
return EvaluatorSameProcess(evaluator)
|
||||
|
||||
class InterpreterEnvironment(_SameEnvironmentMixin, _BaseEnvironment):
|
||||
def get_inference_state_subprocess(self, inference_state):
|
||||
return InferenceStateSameProcess(inference_state)
|
||||
|
||||
def get_sys_path(self):
|
||||
return sys.path
|
||||
|
||||
|
||||
def _get_virtual_env_from_var():
|
||||
def _get_virtual_env_from_var(env_var='VIRTUAL_ENV'):
|
||||
"""Get virtualenv environment from VIRTUAL_ENV environment variable.
|
||||
|
||||
It uses `safe=False` with ``create_environment``, because the environment
|
||||
variable is considered to be safe / controlled by the user solely.
|
||||
"""
|
||||
var = os.environ.get('VIRTUAL_ENV')
|
||||
if var is not None:
|
||||
if var == sys.prefix:
|
||||
return SameEnvironment()
|
||||
var = os.environ.get(env_var)
|
||||
if var:
|
||||
# Under macOS in some cases - notably when using Pipenv - the
|
||||
# sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
|
||||
# /path/to/env so we need to fully resolve the paths in order to
|
||||
# compare them.
|
||||
if os.path.realpath(var) == os.path.realpath(sys.prefix):
|
||||
return _try_get_same_env()
|
||||
|
||||
try:
|
||||
return create_environment(var, safe=False)
|
||||
@@ -173,26 +172,74 @@ def _calculate_sha256_for_file(path):
|
||||
|
||||
def get_default_environment():
|
||||
"""
|
||||
Tries to return an active Virtualenv. If there is no VIRTUAL_ENV variable
|
||||
Tries to return an active Virtualenv or conda environment.
|
||||
If there is no VIRTUAL_ENV variable or no CONDA_PREFIX variable set
|
||||
set it will return the latest Python version installed on the system. This
|
||||
makes it possible to use as many new Python features as possible when using
|
||||
autocompletion and other functionality.
|
||||
|
||||
:returns: :class:`Environment`
|
||||
:returns: :class:`.Environment`
|
||||
"""
|
||||
virtual_env = _get_virtual_env_from_var()
|
||||
if virtual_env is not None:
|
||||
return virtual_env
|
||||
|
||||
# If no VirtualEnv is found, use the environment we're already
|
||||
conda_env = _get_virtual_env_from_var(_CONDA_VAR)
|
||||
if conda_env is not None:
|
||||
return conda_env
|
||||
|
||||
return _try_get_same_env()
|
||||
|
||||
|
||||
def _try_get_same_env():
|
||||
env = SameEnvironment()
|
||||
if not os.path.basename(env.executable).lower().startswith('python'):
|
||||
# This tries to counter issues with embedding. In some cases (e.g.
|
||||
# VIM's Python Mac/Windows, sys.executable is /foo/bar/vim. This
|
||||
# happens, because for Mac a function called `_NSGetExecutablePath` is
|
||||
# used and for Windows `GetModuleFileNameW`. These are both platform
|
||||
# specific functions. For all other systems sys.executable should be
|
||||
# alright. However here we try to generalize:
|
||||
#
|
||||
# 1. Check if the executable looks like python (heuristic)
|
||||
# 2. In case it's not try to find the executable
|
||||
# 3. In case we don't find it use an interpreter environment.
|
||||
#
|
||||
# The last option will always work, but leads to potential crashes of
|
||||
# Jedi - which is ok, because it happens very rarely and even less,
|
||||
# because the code below should work for most cases.
|
||||
if os.name == 'nt':
|
||||
# The first case would be a virtualenv and the second a normal
|
||||
# Python installation.
|
||||
checks = (r'Scripts\python.exe', 'python.exe')
|
||||
else:
|
||||
# For unix it looks like Python is always in a bin folder.
|
||||
checks = (
|
||||
'bin/python%s.%s' % (sys.version_info[0], sys.version[1]),
|
||||
'bin/python%s' % (sys.version_info[0]),
|
||||
'bin/python',
|
||||
)
|
||||
for check in checks:
|
||||
guess = os.path.join(sys.exec_prefix, check)
|
||||
if os.path.isfile(guess):
|
||||
# Bingo - We think we have our Python.
|
||||
return Environment(guess)
|
||||
# It looks like there is no reasonable Python to be found.
|
||||
return InterpreterEnvironment()
|
||||
# If no virtualenv is found, use the environment we're already
|
||||
# using.
|
||||
return SameEnvironment()
|
||||
return env
|
||||
|
||||
|
||||
def get_cached_default_environment():
|
||||
var = os.environ.get('VIRTUAL_ENV')
|
||||
var = os.environ.get('VIRTUAL_ENV') or os.environ.get(_CONDA_VAR)
|
||||
environment = _get_cached_default_environment()
|
||||
if var and var != environment.path:
|
||||
|
||||
# Under macOS in some cases - notably when using Pipenv - the
|
||||
# sys.prefix of the virtualenv is /path/to/env/bin/.. instead of
|
||||
# /path/to/env so we need to fully resolve the paths in order to
|
||||
# compare them.
|
||||
if var and os.path.realpath(var) != os.path.realpath(environment.path):
|
||||
_get_cached_default_environment.clear_cache()
|
||||
return _get_cached_default_environment()
|
||||
return environment
|
||||
@@ -200,58 +247,71 @@ def get_cached_default_environment():
|
||||
|
||||
@time_cache(seconds=10 * 60) # 10 Minutes
|
||||
def _get_cached_default_environment():
|
||||
return get_default_environment()
|
||||
try:
|
||||
return get_default_environment()
|
||||
except InvalidPythonEnvironment:
|
||||
# It's possible that `sys.executable` is wrong. Typically happens
|
||||
# when Jedi is used in an executable that embeds Python. For further
|
||||
# information, have a look at:
|
||||
# https://github.com/davidhalter/jedi/issues/1531
|
||||
return InterpreterEnvironment()
|
||||
|
||||
|
||||
def find_virtualenvs(paths=None, **kwargs):
|
||||
def find_virtualenvs(paths=None, *, safe=True, use_environment_vars=True):
|
||||
"""
|
||||
:param paths: A list of paths in your file system to be scanned for
|
||||
Virtualenvs. It will search in these paths and potentially execute the
|
||||
Python binaries. Also the VIRTUAL_ENV variable will be checked if it
|
||||
contains a valid Virtualenv.
|
||||
Python binaries.
|
||||
:param safe: Default True. In case this is False, it will allow this
|
||||
function to execute potential `python` environments. An attacker might
|
||||
be able to drop an executable in a path this function is searching by
|
||||
default. If the executable has not been installed by root, it will not
|
||||
be executed.
|
||||
:param use_environment_vars: Default True. If True, the VIRTUAL_ENV
|
||||
variable will be checked if it contains a valid VirtualEnv.
|
||||
CONDA_PREFIX will be checked to see if it contains a valid conda
|
||||
environment.
|
||||
|
||||
:yields: :class:`Environment`
|
||||
:yields: :class:`.Environment`
|
||||
"""
|
||||
def py27_comp(paths=None, safe=True):
|
||||
if paths is None:
|
||||
paths = []
|
||||
if paths is None:
|
||||
paths = []
|
||||
|
||||
_used_paths = set()
|
||||
_used_paths = set()
|
||||
|
||||
# Using this variable should be safe, because attackers might be able
|
||||
# to drop files (via git) but not environment variables.
|
||||
if use_environment_vars:
|
||||
# Using this variable should be safe, because attackers might be
|
||||
# able to drop files (via git) but not environment variables.
|
||||
virtual_env = _get_virtual_env_from_var()
|
||||
if virtual_env is not None:
|
||||
yield virtual_env
|
||||
_used_paths.add(virtual_env.path)
|
||||
|
||||
for directory in paths:
|
||||
if not os.path.isdir(directory):
|
||||
conda_env = _get_virtual_env_from_var(_CONDA_VAR)
|
||||
if conda_env is not None:
|
||||
yield conda_env
|
||||
_used_paths.add(conda_env.path)
|
||||
|
||||
for directory in paths:
|
||||
if not os.path.isdir(directory):
|
||||
continue
|
||||
|
||||
directory = os.path.abspath(directory)
|
||||
for path in os.listdir(directory):
|
||||
path = os.path.join(directory, path)
|
||||
if path in _used_paths:
|
||||
# A path shouldn't be inferred twice.
|
||||
continue
|
||||
_used_paths.add(path)
|
||||
|
||||
directory = os.path.abspath(directory)
|
||||
for path in os.listdir(directory):
|
||||
path = os.path.join(directory, path)
|
||||
if path in _used_paths:
|
||||
# A path shouldn't be evaluated twice.
|
||||
continue
|
||||
_used_paths.add(path)
|
||||
|
||||
try:
|
||||
executable = _get_executable_path(path, safe=safe)
|
||||
yield Environment(executable)
|
||||
except InvalidPythonEnvironment:
|
||||
pass
|
||||
|
||||
return py27_comp(paths, **kwargs)
|
||||
try:
|
||||
executable = _get_executable_path(path, safe=safe)
|
||||
yield Environment(executable)
|
||||
except InvalidPythonEnvironment:
|
||||
pass
|
||||
|
||||
|
||||
def find_system_environments():
|
||||
def find_system_environments(*, env_vars=None):
|
||||
"""
|
||||
Ignores virtualenvs and returns the Python versions that were installed on
|
||||
your system. This might return nothing, if you're running Python e.g. from
|
||||
@@ -259,24 +319,24 @@ def find_system_environments():
|
||||
|
||||
The environments are sorted from latest to oldest Python version.
|
||||
|
||||
:yields: :class:`Environment`
|
||||
:yields: :class:`.Environment`
|
||||
"""
|
||||
for version_string in _SUPPORTED_PYTHONS:
|
||||
try:
|
||||
yield get_system_environment(version_string)
|
||||
yield get_system_environment(version_string, env_vars=env_vars)
|
||||
except InvalidPythonEnvironment:
|
||||
pass
|
||||
|
||||
|
||||
# TODO: this function should probably return a list of environments since
|
||||
# multiple Python installations can be found on a system for the same version.
|
||||
def get_system_environment(version):
|
||||
def get_system_environment(version, *, env_vars=None):
|
||||
"""
|
||||
Return the first Python environment found for a string of the form 'X.Y'
|
||||
where X and Y are the major and minor versions of Python.
|
||||
|
||||
:raises: :exc:`.InvalidPythonEnvironment`
|
||||
:returns: :class:`Environment`
|
||||
:returns: :class:`.Environment`
|
||||
"""
|
||||
exe = which('python' + version)
|
||||
if exe:
|
||||
@@ -286,22 +346,25 @@ def get_system_environment(version):
|
||||
|
||||
if os.name == 'nt':
|
||||
for exe in _get_executables_from_windows_registry(version):
|
||||
return Environment(exe)
|
||||
try:
|
||||
return Environment(exe, env_vars=env_vars)
|
||||
except InvalidPythonEnvironment:
|
||||
pass
|
||||
raise InvalidPythonEnvironment("Cannot find executable python%s." % version)
|
||||
|
||||
|
||||
def create_environment(path, safe=True):
|
||||
def create_environment(path, *, safe=True, env_vars=None):
|
||||
"""
|
||||
Make it possible to manually create an Environment object by specifying a
|
||||
Virtualenv path or an executable path.
|
||||
Virtualenv path or an executable path and optional environment variables.
|
||||
|
||||
:raises: :exc:`.InvalidPythonEnvironment`
|
||||
:returns: :class:`Environment`
|
||||
:returns: :class:`.Environment`
|
||||
"""
|
||||
if os.path.isfile(path):
|
||||
_assert_safe(path, safe)
|
||||
return Environment(path)
|
||||
return Environment(_get_executable_path(path, safe=safe))
|
||||
return Environment(path, env_vars=env_vars)
|
||||
return Environment(_get_executable_path(path, safe=safe), env_vars=env_vars)
|
||||
|
||||
|
||||
def _get_executable_path(path, safe=True):
|
||||
@@ -321,11 +384,8 @@ def _get_executable_path(path, safe=True):
|
||||
|
||||
|
||||
def _get_executables_from_windows_registry(version):
|
||||
# The winreg module is named _winreg on Python 2.
|
||||
try:
|
||||
import winreg
|
||||
except ImportError:
|
||||
import _winreg as winreg
|
||||
# https://github.com/python/typeshed/pull/3794 adds winreg
|
||||
import winreg # type: ignore[import]
|
||||
|
||||
# TODO: support Python Anaconda.
|
||||
sub_keys = [
|
||||
@@ -395,8 +455,8 @@ def _is_unix_safe_simple(real_path):
|
||||
# 2. The repository has an innocent looking folder called foobar. jedi
|
||||
# searches for the folder and executes foobar/bin/python --version if
|
||||
# there's also a foobar/bin/activate.
|
||||
# 3. The bin/python is obviously not a python script but a bash script or
|
||||
# whatever the attacker wants.
|
||||
# 3. The attacker has gained code execution, since he controls
|
||||
# foobar/bin/python.
|
||||
return uid == 0
|
||||
|
||||
|
||||
|
||||
46
jedi/api/errors.py
Normal file
46
jedi/api/errors.py
Normal file
@@ -0,0 +1,46 @@
|
||||
"""
|
||||
This file is about errors in Python files and not about exception handling in
|
||||
Jedi.
|
||||
"""
|
||||
|
||||
|
||||
def parso_to_jedi_errors(grammar, module_node):
|
||||
return [SyntaxError(e) for e in grammar.iter_errors(module_node)]
|
||||
|
||||
|
||||
class SyntaxError:
|
||||
"""
|
||||
Syntax errors are generated by :meth:`.Script.get_syntax_errors`.
|
||||
"""
|
||||
def __init__(self, parso_error):
|
||||
self._parso_error = parso_error
|
||||
|
||||
@property
|
||||
def line(self):
|
||||
"""The line where the error starts (starting with 1)."""
|
||||
return self._parso_error.start_pos[0]
|
||||
|
||||
@property
|
||||
def column(self):
|
||||
"""The column where the error starts (starting with 0)."""
|
||||
return self._parso_error.start_pos[1]
|
||||
|
||||
@property
|
||||
def until_line(self):
|
||||
"""The line where the error ends (starting with 1)."""
|
||||
return self._parso_error.end_pos[0]
|
||||
|
||||
@property
|
||||
def until_column(self):
|
||||
"""The column where the error ends (starting with 0)."""
|
||||
return self._parso_error.end_pos[1]
|
||||
|
||||
def get_message(self):
|
||||
return self._parso_error.message
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s from=%s to=%s>' % (
|
||||
self.__class__.__name__,
|
||||
self._parso_error.start_pos,
|
||||
self._parso_error.end_pos,
|
||||
)
|
||||
@@ -3,8 +3,29 @@ class _JediError(Exception):
|
||||
|
||||
|
||||
class InternalError(_JediError):
|
||||
pass
|
||||
"""
|
||||
This error might happen a subprocess is crashing. The reason for this is
|
||||
usually broken C code in third party libraries. This is not a very common
|
||||
thing and it is safe to use Jedi again. However using the same calls might
|
||||
result in the same error again.
|
||||
"""
|
||||
|
||||
|
||||
class WrongVersion(_JediError):
|
||||
pass
|
||||
"""
|
||||
This error is reserved for the future, shouldn't really be happening at the
|
||||
moment.
|
||||
"""
|
||||
|
||||
|
||||
class RefactoringError(_JediError):
|
||||
"""
|
||||
Refactorings can fail for various reasons. So if you work with refactorings
|
||||
like :meth:`.Script.rename`, :meth:`.Script.inline`,
|
||||
:meth:`.Script.extract_variable` and :meth:`.Script.extract_function`, make
|
||||
sure to catch these. The descriptions in the errors are ususally valuable
|
||||
for end users.
|
||||
|
||||
A typical ``RefactoringError`` would tell the user that inlining is not
|
||||
possible if no name is under the cursor.
|
||||
"""
|
||||
|
||||
155
jedi/api/file_name.py
Normal file
155
jedi/api/file_name.py
Normal file
@@ -0,0 +1,155 @@
|
||||
import os
|
||||
|
||||
from jedi.api import classes
|
||||
from jedi.api.strings import StringName, get_quote_ending
|
||||
from jedi.api.helpers import match
|
||||
from jedi.inference.helpers import get_str_or_none
|
||||
|
||||
|
||||
class PathName(StringName):
|
||||
api_type = 'path'
|
||||
|
||||
|
||||
def complete_file_name(inference_state, module_context, start_leaf, quote, string,
|
||||
like_name, signatures_callback, code_lines, position, fuzzy):
|
||||
# First we want to find out what can actually be changed as a name.
|
||||
like_name_length = len(os.path.basename(string))
|
||||
|
||||
addition = _get_string_additions(module_context, start_leaf)
|
||||
if string.startswith('~'):
|
||||
string = os.path.expanduser(string)
|
||||
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)
|
||||
string = os.path.dirname(string)
|
||||
|
||||
sigs = signatures_callback(*position)
|
||||
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(inference_state.project.path, string)
|
||||
try:
|
||||
listed = sorted(os.scandir(base_path), key=lambda e: e.name)
|
||||
# OSError: [Errno 36] File name too long: '...'
|
||||
except (FileNotFoundError, OSError):
|
||||
return
|
||||
quote_ending = get_quote_ending(quote, code_lines, position)
|
||||
for entry in listed:
|
||||
name = entry.name
|
||||
if match(name, must_start_with, fuzzy=fuzzy):
|
||||
if is_in_os_path_join or not entry.is_dir():
|
||||
name += quote_ending
|
||||
else:
|
||||
name += os.path.sep
|
||||
|
||||
yield classes.Completion(
|
||||
inference_state,
|
||||
PathName(inference_state, name[len(must_start_with) - like_name_length:]),
|
||||
stack=None,
|
||||
like_name_length=like_name_length,
|
||||
is_fuzzy=fuzzy,
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
values = context.infer_node(child_node)
|
||||
if len(values) != 1:
|
||||
return None
|
||||
c, = values
|
||||
s = get_str_or_none(c)
|
||||
if s is None:
|
||||
return None
|
||||
if not first and add_slash:
|
||||
string += os.path.sep
|
||||
string += s
|
||||
first = False
|
||||
return string
|
||||
|
||||
|
||||
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('`
|
||||
value_node = start_leaf.parent
|
||||
index = value_node.children.index(start_leaf)
|
||||
if index > 0:
|
||||
error_node = value_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], [])
|
||||
@@ -4,23 +4,50 @@ Helpers for the API
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from textwrap import dedent
|
||||
from itertools import chain
|
||||
from functools import wraps
|
||||
from inspect import Parameter
|
||||
|
||||
from parso.python.parser import Parser
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import u
|
||||
from jedi.evaluate.syntax_tree import eval_atom
|
||||
from jedi.evaluate.helpers import evaluate_call_of_leaf
|
||||
from jedi.evaluate.compiled import get_string_context_set
|
||||
from jedi.cache import call_signature_time_cache
|
||||
from jedi.inference.base_value import NO_VALUES
|
||||
from jedi.inference.syntax_tree import infer_atom
|
||||
from jedi.inference.helpers import infer_call_of_leaf
|
||||
from jedi.inference.compiled import get_string_value_set
|
||||
from jedi.cache import signature_time_cache, memoize_method
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
CompletionParts = namedtuple('CompletionParts', ['path', 'has_dot', 'name'])
|
||||
|
||||
|
||||
def _start_match(string, like_name):
|
||||
return string.startswith(like_name)
|
||||
|
||||
|
||||
def _fuzzy_match(string, like_name):
|
||||
if len(like_name) <= 1:
|
||||
return like_name in string
|
||||
pos = string.find(like_name[0])
|
||||
if pos >= 0:
|
||||
return _fuzzy_match(string[pos + 1:], like_name[1:])
|
||||
return False
|
||||
|
||||
|
||||
def match(string, like_name, fuzzy=False):
|
||||
if fuzzy:
|
||||
return _fuzzy_match(string, like_name)
|
||||
else:
|
||||
return _start_match(string, like_name)
|
||||
|
||||
|
||||
def sorted_definitions(defs):
|
||||
# Note: `or ''` below is required because `module_path` could be
|
||||
return sorted(defs, key=lambda x: (x.module_path or '', x.line or 0, x.column or 0))
|
||||
return sorted(defs, key=lambda x: (str(x.module_path or ''),
|
||||
x.line or 0,
|
||||
x.column or 0,
|
||||
x.name))
|
||||
|
||||
|
||||
def get_on_completion_name(module_node, lines, position):
|
||||
@@ -53,26 +80,25 @@ 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:
|
||||
# If we're not on a comment simply get the previous leaf and proceed.
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return u('') # At the beginning of the file.
|
||||
return '' # At the beginning of the file.
|
||||
|
||||
is_after_newline = leaf.type == 'newline'
|
||||
while leaf.type == 'newline':
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return u('')
|
||||
return ''
|
||||
|
||||
if leaf.type == 'error_leaf' or leaf.type == 'string':
|
||||
if leaf.start_pos[0] < position[0]:
|
||||
# On a different line, we just begin anew.
|
||||
return u('')
|
||||
return ''
|
||||
|
||||
# Error leafs cannot be parsed, completion in strings is also
|
||||
# impossible.
|
||||
@@ -87,14 +113,14 @@ def _get_code_for_stack(code_lines, module_node, position):
|
||||
if is_after_newline:
|
||||
if user_stmt.start_pos[1] > position[1]:
|
||||
# This means that it's actually a dedent and that means that we
|
||||
# start without context (part of a suite).
|
||||
return u('')
|
||||
# start without value (part of a suite).
|
||||
return ''
|
||||
|
||||
# This is basically getting the relevant lines.
|
||||
return _get_code(code_lines, user_stmt.get_start_pos_of_prefix(), position)
|
||||
|
||||
|
||||
def get_stack_at_position(grammar, code_lines, module_node, pos):
|
||||
def get_stack_at_position(grammar, code_lines, leaf, pos):
|
||||
"""
|
||||
Returns the possible node names (e.g. import_from, xor_test or yield_stmt).
|
||||
"""
|
||||
@@ -118,7 +144,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.
|
||||
@@ -130,31 +156,202 @@ def get_stack_at_position(grammar, code_lines, module_node, pos):
|
||||
p.parse(tokens=tokenize_without_endmarker(code))
|
||||
except EndMarkerReached:
|
||||
return p.stack
|
||||
raise SystemError("This really shouldn't happen. There's a bug in Jedi.")
|
||||
raise SystemError(
|
||||
"This really shouldn't happen. There's a bug in Jedi:\n%s"
|
||||
% list(tokenize_without_endmarker(code))
|
||||
)
|
||||
|
||||
|
||||
def evaluate_goto_definition(evaluator, context, leaf):
|
||||
def infer(inference_state, context, leaf):
|
||||
if leaf.type == 'name':
|
||||
# In case of a name we can just use goto_definition which does all the
|
||||
# magic itself.
|
||||
return evaluator.goto_definitions(context, leaf)
|
||||
return inference_state.infer(context, leaf)
|
||||
|
||||
parent = leaf.parent
|
||||
definitions = NO_VALUES
|
||||
if parent.type == 'atom':
|
||||
return context.eval_node(leaf.parent)
|
||||
# e.g. `(a + b)`
|
||||
definitions = context.infer_node(leaf.parent)
|
||||
elif parent.type == 'trailer':
|
||||
return evaluate_call_of_leaf(context, leaf)
|
||||
# e.g. `a()`
|
||||
definitions = infer_call_of_leaf(context, leaf)
|
||||
elif isinstance(leaf, tree.Literal):
|
||||
return eval_atom(context, leaf)
|
||||
# e.g. `"foo"` or `1.0`
|
||||
return infer_atom(context, leaf)
|
||||
elif leaf.type in ('fstring_string', 'fstring_start', 'fstring_end'):
|
||||
return get_string_context_set(evaluator)
|
||||
return []
|
||||
return get_string_value_set(inference_state)
|
||||
return definitions
|
||||
|
||||
|
||||
CallSignatureDetails = namedtuple(
|
||||
'CallSignatureDetails',
|
||||
['bracket_leaf', 'call_index', 'keyword_name_str']
|
||||
)
|
||||
def filter_follow_imports(names, follow_builtin_imports=False):
|
||||
for name in names:
|
||||
if name.is_import():
|
||||
new_names = list(filter_follow_imports(
|
||||
name.goto(),
|
||||
follow_builtin_imports=follow_builtin_imports,
|
||||
))
|
||||
found_builtin = False
|
||||
if follow_builtin_imports:
|
||||
for new_name in new_names:
|
||||
if new_name.start_pos is None:
|
||||
found_builtin = True
|
||||
|
||||
if found_builtin:
|
||||
yield name
|
||||
else:
|
||||
yield from new_names
|
||||
else:
|
||||
yield name
|
||||
|
||||
|
||||
class CallDetails:
|
||||
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]
|
||||
|
||||
@memoize_method
|
||||
def _list_arguments(self):
|
||||
return list(_iter_arguments(self._children, self._position))
|
||||
|
||||
def calculate_index(self, param_names):
|
||||
positional_count = 0
|
||||
used_names = set()
|
||||
star_count = -1
|
||||
args = self._list_arguments()
|
||||
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_used_keyword_arguments(self):
|
||||
for star_count, key_start, had_equal in list(self._list_arguments()):
|
||||
if had_equal and key_start:
|
||||
yield key_start
|
||||
|
||||
def count_positional_arguments(self):
|
||||
count = 0
|
||||
for star_count, key_start, had_equal in self._list_arguments()[:-1]:
|
||||
if star_count:
|
||||
break
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
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':
|
||||
yield from _iter_arguments(nodes_before[-1].children, position)
|
||||
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 == 'testlist_star_expr':
|
||||
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):
|
||||
@@ -163,23 +360,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_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:
|
||||
@@ -190,53 +386,66 @@ 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):
|
||||
def get_signature_details(module, position):
|
||||
leaf = module.get_leaf_for_position(position, include_prefixes=True)
|
||||
# It's easier to deal with the previous token than the next one in this
|
||||
# case.
|
||||
if leaf.start_pos >= position:
|
||||
# Whitespace / comments after the leaf count towards the previous leaf.
|
||||
leaf = leaf.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
|
||||
if leaf == ')':
|
||||
if leaf.end_pos == position:
|
||||
leaf = leaf.get_next_leaf()
|
||||
|
||||
# Now that we know where we are in the syntax tree, we start to look at
|
||||
# parents for possible function definitions.
|
||||
node = leaf.parent
|
||||
while node is not None:
|
||||
if node.type in ('funcdef', 'classdef'):
|
||||
# Don't show call signatures if there's stuff before it that just
|
||||
# makes it feel strange to have a call signature.
|
||||
if node.type in ('funcdef', 'classdef', 'decorated', 'async_stmt'):
|
||||
# Don't show signatures if there's stuff before it that just
|
||||
# makes it feel strange to have a 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_signature_details_from_error_node(
|
||||
n, additional_children, position
|
||||
)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
if node.type == 'trailer' and node.children[0] == '(':
|
||||
leaf = node.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
return CallSignatureDetails(
|
||||
node.children[0], *_get_index_and_key(node.children, position))
|
||||
additional_children[0:0] = n.children
|
||||
continue
|
||||
additional_children.insert(0, n)
|
||||
|
||||
# Find a valid trailer
|
||||
if node.type == 'trailer' and node.children[0] == '(' \
|
||||
or node.type == 'decorator' and node.children[2] == '(':
|
||||
# Additionally we have to check that an ending parenthesis isn't
|
||||
# interpreted wrong. There are two cases:
|
||||
# 1. Cursor before paren -> The current signature is good
|
||||
# 2. Cursor after paren -> We need to skip the current signature
|
||||
if not (leaf is node.children[-1] and position >= leaf.end_pos):
|
||||
leaf = node.get_previous_leaf()
|
||||
if leaf is None:
|
||||
return None
|
||||
return CallDetails(
|
||||
node.children[0] if node.type == 'trailer' else node.children[2],
|
||||
node.children,
|
||||
position
|
||||
)
|
||||
|
||||
node = node.parent
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@call_signature_time_cache("call_signatures_validity")
|
||||
def cache_call_signatures(evaluator, context, bracket_leaf, code_lines, user_pos):
|
||||
@signature_time_cache("call_signatures_validity")
|
||||
def cache_signatures(inference_state, context, bracket_leaf, code_lines, user_pos):
|
||||
"""This function calculates the cache key."""
|
||||
line_index = user_pos[0] - 1
|
||||
|
||||
@@ -250,8 +459,65 @@ def cache_call_signatures(evaluator, context, bracket_leaf, code_lines, user_pos
|
||||
yield None # Don't cache!
|
||||
else:
|
||||
yield (module_path, before_bracket, bracket_leaf.start_pos)
|
||||
yield evaluate_goto_definition(
|
||||
evaluator,
|
||||
yield infer(
|
||||
inference_state,
|
||||
context,
|
||||
bracket_leaf.get_previous_leaf()
|
||||
bracket_leaf.get_previous_leaf(),
|
||||
)
|
||||
|
||||
|
||||
def validate_line_column(func):
|
||||
@wraps(func)
|
||||
def wrapper(self, line=None, column=None, *args, **kwargs):
|
||||
line = max(len(self._code_lines), 1) if line is None else line
|
||||
if not (0 < line <= len(self._code_lines)):
|
||||
raise ValueError('`line` parameter is not in a valid range.')
|
||||
|
||||
line_string = self._code_lines[line - 1]
|
||||
line_len = len(line_string)
|
||||
if line_string.endswith('\r\n'):
|
||||
line_len -= 2
|
||||
elif line_string.endswith('\n'):
|
||||
line_len -= 1
|
||||
|
||||
column = line_len if column is None else column
|
||||
if not (0 <= column <= line_len):
|
||||
raise ValueError('`column` parameter (%d) is not in a valid range '
|
||||
'(0-%d) for line %d (%r).' % (
|
||||
column, line_len, line, line_string))
|
||||
return func(self, line, column, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def get_module_names(module, all_scopes, definitions=True, references=False):
|
||||
"""
|
||||
Returns a dictionary with name parts as keys and their call paths as
|
||||
values.
|
||||
"""
|
||||
def def_ref_filter(name):
|
||||
is_def = name.is_definition()
|
||||
return definitions and is_def or references and not is_def
|
||||
|
||||
names = list(chain.from_iterable(module.get_used_names().values()))
|
||||
if not all_scopes:
|
||||
# We have to filter all the names that don't have the module as a
|
||||
# parent_scope. There's None as a parent, because nodes in the module
|
||||
# node have the parent module and not suite as all the others.
|
||||
# Therefore it's important to catch that case.
|
||||
|
||||
def is_module_scope_name(name):
|
||||
parent_scope = get_parent_scope(name)
|
||||
# async functions have an extra wrapper. Strip it.
|
||||
if parent_scope and parent_scope.type == 'async_stmt':
|
||||
parent_scope = parent_scope.parent
|
||||
return parent_scope in (module, None)
|
||||
|
||||
names = [n for n in names if is_module_scope_name(n)]
|
||||
return filter(def_ref_filter, names)
|
||||
|
||||
|
||||
def split_search_string(name):
|
||||
type, _, dotted_names = name.rpartition(' ')
|
||||
if type == 'def':
|
||||
type = 'function'
|
||||
return type, dotted_names.split('.')
|
||||
|
||||
@@ -2,60 +2,73 @@
|
||||
TODO Some parts of this module are still not well documented.
|
||||
"""
|
||||
|
||||
from jedi.evaluate.context import ModuleContext
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.compiled import mixed
|
||||
from jedi.evaluate.compiled.access import create_access_path
|
||||
from jedi.evaluate.base_context import Context
|
||||
from jedi.inference import compiled
|
||||
from jedi.inference.base_value import ValueSet
|
||||
from jedi.inference.filters import ParserTreeFilter, MergedFilter
|
||||
from jedi.inference.names import TreeNameDefinition
|
||||
from jedi.inference.compiled import mixed
|
||||
from jedi.inference.compiled.access import create_access_path
|
||||
from jedi.inference.context import ModuleContext
|
||||
|
||||
|
||||
def _create(evaluator, obj):
|
||||
def _create(inference_state, obj):
|
||||
return compiled.create_from_access_path(
|
||||
evaluator, create_access_path(evaluator, obj)
|
||||
inference_state, create_access_path(inference_state, obj)
|
||||
)
|
||||
|
||||
|
||||
class NamespaceObject(object):
|
||||
class NamespaceObject:
|
||||
def __init__(self, dct):
|
||||
self.__dict__ = dct
|
||||
|
||||
|
||||
class MixedModuleContext(Context):
|
||||
type = 'mixed_module'
|
||||
class MixedTreeName(TreeNameDefinition):
|
||||
def infer(self):
|
||||
"""
|
||||
In IPython notebook it is typical that some parts of the code that is
|
||||
provided was already executed. In that case if something is not properly
|
||||
inferred, it should still infer from the variables it already knows.
|
||||
"""
|
||||
inferred = super().infer()
|
||||
if not inferred:
|
||||
for compiled_value in self.parent_context.mixed_values:
|
||||
for f in compiled_value.get_filters():
|
||||
values = ValueSet.from_sets(
|
||||
n.infer() for n in f.get(self.string_name)
|
||||
)
|
||||
if values:
|
||||
return values
|
||||
return inferred
|
||||
|
||||
def __init__(self, evaluator, tree_module, namespaces, path, code_lines):
|
||||
self.evaluator = evaluator
|
||||
self._namespaces = namespaces
|
||||
|
||||
self._namespace_objects = [NamespaceObject(n) for n in namespaces]
|
||||
self._module_context = ModuleContext(
|
||||
evaluator, tree_module,
|
||||
path=path,
|
||||
code_lines=code_lines
|
||||
class MixedParserTreeFilter(ParserTreeFilter):
|
||||
name_class = MixedTreeName
|
||||
|
||||
|
||||
class MixedModuleContext(ModuleContext):
|
||||
def __init__(self, tree_module_value, namespaces):
|
||||
super().__init__(tree_module_value)
|
||||
self.mixed_values = [
|
||||
self._get_mixed_object(
|
||||
_create(self.inference_state, NamespaceObject(n))
|
||||
) for n in namespaces
|
||||
]
|
||||
|
||||
def _get_mixed_object(self, compiled_value):
|
||||
return mixed.MixedObject(
|
||||
compiled_value=compiled_value,
|
||||
tree_value=self._value
|
||||
)
|
||||
self.tree_node = tree_module
|
||||
|
||||
def get_node(self):
|
||||
return self.tree_node
|
||||
|
||||
def get_filters(self, *args, **kwargs):
|
||||
for filter in self._module_context.get_filters(*args, **kwargs):
|
||||
yield filter
|
||||
|
||||
for namespace_obj in self._namespace_objects:
|
||||
compiled_object = _create(self.evaluator, namespace_obj)
|
||||
mixed_object = mixed.MixedObject(
|
||||
self.evaluator,
|
||||
def get_filters(self, until_position=None, origin_scope=None):
|
||||
yield MergedFilter(
|
||||
MixedParserTreeFilter(
|
||||
parent_context=self,
|
||||
compiled_object=compiled_object,
|
||||
tree_context=self._module_context
|
||||
)
|
||||
for filter in mixed_object.get_filters(*args, **kwargs):
|
||||
yield filter
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
),
|
||||
self.get_global_filter(),
|
||||
)
|
||||
|
||||
@property
|
||||
def code_lines(self):
|
||||
return self._module_context.code_lines
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._module_context, name)
|
||||
for mixed_object in self.mixed_values:
|
||||
yield from mixed_object.get_filters(until_position, origin_scope)
|
||||
|
||||
@@ -1,54 +1,23 @@
|
||||
import pydoc
|
||||
from contextlib import suppress
|
||||
from typing import Dict, Optional
|
||||
|
||||
from jedi.evaluate.utils import ignored
|
||||
from jedi.evaluate.filters import AbstractNameDefinition
|
||||
from jedi.inference.names import AbstractArbitraryName
|
||||
|
||||
try:
|
||||
from pydoc_data import topics as pydoc_topics
|
||||
# https://github.com/python/typeshed/pull/4351 adds pydoc_data
|
||||
from pydoc_data import topics # type: ignore[import]
|
||||
pydoc_topics: Optional[Dict[str, str]] = topics.topics
|
||||
except ImportError:
|
||||
# Python 2
|
||||
try:
|
||||
import pydoc_topics
|
||||
except ImportError:
|
||||
# This is for Python 3 embeddable version, which dont have
|
||||
# pydoc_data module in its file python3x.zip.
|
||||
pydoc_topics = None
|
||||
# Python 3.6.8 embeddable does not have pydoc_data.
|
||||
pydoc_topics = None
|
||||
|
||||
|
||||
def get_operator(evaluator, string, pos):
|
||||
return Keyword(evaluator, string, pos)
|
||||
class KeywordName(AbstractArbitraryName):
|
||||
api_type = 'keyword'
|
||||
|
||||
|
||||
class KeywordName(AbstractNameDefinition):
|
||||
api_type = u'keyword'
|
||||
|
||||
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))]
|
||||
|
||||
|
||||
class Keyword(object):
|
||||
api_type = u'keyword'
|
||||
|
||||
def __init__(self, evaluator, name, pos):
|
||||
self.name = KeywordName(evaluator, name)
|
||||
self.start_pos = pos
|
||||
self.parent = evaluator.builtins_module
|
||||
|
||||
@property
|
||||
def names(self):
|
||||
""" For a `parsing.Name` like comparision """
|
||||
return [self.name]
|
||||
|
||||
def py__doc__(self, include_call_signature=False):
|
||||
return imitate_pydoc(self.name.string_name)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self.name)
|
||||
def py__doc__(self):
|
||||
return imitate_pydoc(self.string_name)
|
||||
|
||||
|
||||
def imitate_pydoc(string):
|
||||
@@ -59,16 +28,15 @@ def imitate_pydoc(string):
|
||||
if pydoc_topics is None:
|
||||
return ''
|
||||
|
||||
# str needed because of possible unicode stuff in py2k (pydoc doesn't work
|
||||
# with unicode strings)
|
||||
string = str(string)
|
||||
h = pydoc.help
|
||||
with ignored(KeyError):
|
||||
with suppress(KeyError):
|
||||
# try to access symbols
|
||||
string = h.symbols[string]
|
||||
string, _, related = string.partition(' ')
|
||||
|
||||
get_target = lambda s: h.topics.get(s, h.keywords.get(s))
|
||||
def get_target(s):
|
||||
return h.topics.get(s, h.keywords.get(s))
|
||||
|
||||
while isinstance(string, str):
|
||||
string = get_target(string)
|
||||
|
||||
@@ -79,6 +47,6 @@ def imitate_pydoc(string):
|
||||
return ''
|
||||
|
||||
try:
|
||||
return pydoc_topics.topics[label].strip() if pydoc_topics else ''
|
||||
return pydoc_topics[label].strip() if pydoc_topics else ''
|
||||
except KeyError:
|
||||
return ''
|
||||
|
||||
@@ -1,21 +1,52 @@
|
||||
import os
|
||||
import json
|
||||
"""
|
||||
Projects are a way to handle Python projects within Jedi. For simpler plugins
|
||||
you might not want to deal with projects, but if you want to give the user more
|
||||
flexibility to define sys paths and Python interpreters for a project,
|
||||
:class:`.Project` is the perfect way to allow for that.
|
||||
|
||||
from jedi._compatibility import FileNotFoundError, NotADirectoryError
|
||||
from jedi.api.environment import SameEnvironment, \
|
||||
get_cached_default_environment
|
||||
Projects can be saved to disk and loaded again, to allow project definitions to
|
||||
be used across repositories.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
from itertools import chain
|
||||
|
||||
from jedi import debug
|
||||
from jedi.api.environment import get_cached_default_environment, create_environment
|
||||
from jedi.api.exceptions import WrongVersion
|
||||
from jedi._compatibility import force_unicode
|
||||
from jedi.evaluate.sys_path import discover_buildout_paths
|
||||
from jedi.evaluate.cache import evaluator_as_method_param_cache
|
||||
from jedi.common.utils import traverse_parents
|
||||
from jedi.api.completion import search_in_module
|
||||
from jedi.api.helpers import split_search_string, get_module_names
|
||||
from jedi.inference.imports import load_module_from_path, \
|
||||
load_namespace_from_path, iter_module_names
|
||||
from jedi.inference.sys_path import discover_buildout_paths
|
||||
from jedi.inference.cache import inference_state_as_method_param_cache
|
||||
from jedi.inference.references import recurse_find_python_folders_and_files, search_in_file_ios
|
||||
from jedi.file_io import FolderIO
|
||||
|
||||
_CONFIG_FOLDER = '.jedi'
|
||||
_CONTAINS_POTENTIAL_PROJECT = 'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in'
|
||||
_CONTAINS_POTENTIAL_PROJECT = \
|
||||
'setup.py', '.git', '.hg', 'requirements.txt', 'MANIFEST.in', 'pyproject.toml'
|
||||
|
||||
_SERIALIZER_VERSION = 1
|
||||
|
||||
|
||||
def _try_to_skip_duplicates(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
found_tree_nodes = []
|
||||
found_modules = []
|
||||
for definition in func(*args, **kwargs):
|
||||
tree_node = definition._name.tree_name
|
||||
if tree_node is not None and tree_node in found_tree_nodes:
|
||||
continue
|
||||
if definition.type == 'module' and definition.module_path is not None:
|
||||
if definition.module_path in found_modules:
|
||||
continue
|
||||
found_modules.append(definition.module_path)
|
||||
yield definition
|
||||
found_tree_nodes.append(tree_node)
|
||||
return wrapper
|
||||
|
||||
|
||||
def _remove_duplicates_from_path(path):
|
||||
used = set()
|
||||
for p in path:
|
||||
@@ -25,161 +56,371 @@ def _remove_duplicates_from_path(path):
|
||||
yield p
|
||||
|
||||
|
||||
def _force_unicode_list(lst):
|
||||
return list(map(force_unicode, lst))
|
||||
|
||||
|
||||
class Project(object):
|
||||
# TODO serialize environment
|
||||
_serializer_ignore_attributes = ('_environment',)
|
||||
class Project:
|
||||
"""
|
||||
Projects are a simple way to manage Python folders and define how Jedi does
|
||||
import resolution. It is mostly used as a parameter to :class:`.Script`.
|
||||
Additionally there are functions to search a whole project.
|
||||
"""
|
||||
_environment = None
|
||||
|
||||
@staticmethod
|
||||
def _get_config_folder_path(base_path):
|
||||
return base_path.joinpath(_CONFIG_FOLDER)
|
||||
|
||||
@staticmethod
|
||||
def _get_json_path(base_path):
|
||||
return os.path.join(base_path, _CONFIG_FOLDER, 'project.json')
|
||||
return Project._get_config_folder_path(base_path).joinpath('project.json')
|
||||
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
"""
|
||||
Loads a project from a specific path. You should not provide the path
|
||||
to ``.jedi/project.json``, but rather the path to the project folder.
|
||||
|
||||
:param path: The path of the directory you want to use as a project.
|
||||
"""
|
||||
if isinstance(path, str):
|
||||
path = Path(path)
|
||||
with open(cls._get_json_path(path)) as f:
|
||||
version, data = json.load(f)
|
||||
|
||||
if version == 1:
|
||||
self = cls.__new__()
|
||||
self.__dict__.update(data)
|
||||
return self
|
||||
return cls(**data)
|
||||
else:
|
||||
raise WrongVersion(
|
||||
"The Jedi version of this project seems newer than what we can handle."
|
||||
)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Saves the project configuration in the project in ``.jedi/project.json``.
|
||||
"""
|
||||
data = dict(self.__dict__)
|
||||
data.pop('_environment', None)
|
||||
data.pop('_django', None) # TODO make django setting public?
|
||||
data = {k.lstrip('_'): v for k, v in data.items()}
|
||||
data['path'] = str(data['path'])
|
||||
|
||||
self._get_config_folder_path(self._path).mkdir(parents=True, exist_ok=True)
|
||||
with open(self._get_json_path(self._path), 'w') as f:
|
||||
return json.dump((_SERIALIZER_VERSION, data), f)
|
||||
|
||||
def __init__(self, path, **kwargs):
|
||||
"""
|
||||
:param path: The base path for this project.
|
||||
:param environment_path: The Python executable path, typically the path
|
||||
of a virtual environment.
|
||||
:param load_unsafe_extensions: Default False, Loads extensions that are not in the
|
||||
sys path and in the local directories. With this option enabled,
|
||||
this is potentially unsafe if you clone a git repository and
|
||||
analyze it's code, because those compiled extensions will be
|
||||
important and therefore have execution privileges.
|
||||
:param sys_path: list of str. You can override the sys path if you
|
||||
want. By default the ``sys.path.`` is generated from the
|
||||
want. By default the ``sys.path.`` is generated by the
|
||||
environment (virtualenvs, etc).
|
||||
:param added_sys_path: list of str. Adds these paths at the end of the
|
||||
sys path.
|
||||
:param smart_sys_path: If this is enabled (default), adds paths from
|
||||
local directories. Otherwise you will have to rely on your packages
|
||||
being properly configured on the ``sys.path``.
|
||||
"""
|
||||
def py2_comp(path, environment=None, sys_path=None,
|
||||
smart_sys_path=True, _django=False):
|
||||
def py2_comp(path, environment_path=None, load_unsafe_extensions=False,
|
||||
sys_path=None, added_sys_path=(), smart_sys_path=True):
|
||||
if isinstance(path, str):
|
||||
path = Path(path).absolute()
|
||||
self._path = path
|
||||
if isinstance(environment, SameEnvironment):
|
||||
self._environment = environment
|
||||
|
||||
self._environment_path = environment_path
|
||||
if sys_path is not None:
|
||||
# Remap potential pathlib.Path entries
|
||||
sys_path = list(map(str, sys_path))
|
||||
self._sys_path = sys_path
|
||||
self._smart_sys_path = smart_sys_path
|
||||
self._django = _django
|
||||
self._load_unsafe_extensions = load_unsafe_extensions
|
||||
self._django = False
|
||||
# Remap potential pathlib.Path entries
|
||||
self.added_sys_path = list(map(str, added_sys_path))
|
||||
"""The sys path that is going to be added at the end of the """
|
||||
|
||||
py2_comp(path, **kwargs)
|
||||
|
||||
def _get_base_sys_path(self, environment=None):
|
||||
if self._sys_path is not None:
|
||||
return self._sys_path
|
||||
@property
|
||||
def path(self):
|
||||
"""
|
||||
The base path for this project.
|
||||
"""
|
||||
return self._path
|
||||
|
||||
@property
|
||||
def sys_path(self):
|
||||
"""
|
||||
The sys path provided to this project. This can be None and in that
|
||||
case will be auto generated.
|
||||
"""
|
||||
return self._sys_path
|
||||
|
||||
@property
|
||||
def smart_sys_path(self):
|
||||
"""
|
||||
If the sys path is going to be calculated in a smart way, where
|
||||
additional paths are added.
|
||||
"""
|
||||
return self._smart_sys_path
|
||||
|
||||
@property
|
||||
def load_unsafe_extensions(self):
|
||||
"""
|
||||
Wheter the project loads unsafe extensions.
|
||||
"""
|
||||
return self._load_unsafe_extensions
|
||||
|
||||
@inference_state_as_method_param_cache()
|
||||
def _get_base_sys_path(self, inference_state):
|
||||
# The sys path has not been set explicitly.
|
||||
if environment is None:
|
||||
environment = self.get_environment()
|
||||
|
||||
sys_path = list(environment.get_sys_path())
|
||||
sys_path = list(inference_state.environment.get_sys_path())
|
||||
try:
|
||||
sys_path.remove('')
|
||||
except ValueError:
|
||||
pass
|
||||
return sys_path
|
||||
|
||||
@evaluator_as_method_param_cache()
|
||||
def _get_sys_path(self, evaluator, environment=None):
|
||||
@inference_state_as_method_param_cache()
|
||||
def _get_sys_path(self, inference_state, add_parent_paths=True, add_init_paths=False):
|
||||
"""
|
||||
Keep this method private for all users of jedi. However internally this
|
||||
one is used like a public method.
|
||||
"""
|
||||
suffixed = []
|
||||
suffixed = list(self.added_sys_path)
|
||||
prefixed = []
|
||||
|
||||
sys_path = list(self._get_base_sys_path(environment))
|
||||
if self._sys_path is None:
|
||||
sys_path = list(self._get_base_sys_path(inference_state))
|
||||
else:
|
||||
sys_path = list(self._sys_path)
|
||||
|
||||
if self._smart_sys_path:
|
||||
prefixed.append(self._path)
|
||||
prefixed.append(str(self._path))
|
||||
|
||||
if evaluator.script_path is not None:
|
||||
suffixed += discover_buildout_paths(evaluator, evaluator.script_path)
|
||||
if inference_state.script_path is not None:
|
||||
suffixed += map(str, discover_buildout_paths(
|
||||
inference_state,
|
||||
inference_state.script_path
|
||||
))
|
||||
|
||||
traversed = list(traverse_parents(evaluator.script_path))
|
||||
if add_parent_paths:
|
||||
# Collect directories in upward search by:
|
||||
# 1. Skipping directories with __init__.py
|
||||
# 2. Stopping immediately when above self._path
|
||||
traversed = []
|
||||
for parent_path in inference_state.script_path.parents:
|
||||
if parent_path == self._path \
|
||||
or self._path not in parent_path.parents:
|
||||
break
|
||||
if not add_init_paths \
|
||||
and parent_path.joinpath("__init__.py").is_file():
|
||||
continue
|
||||
traversed.append(str(parent_path))
|
||||
|
||||
# AFAIK some libraries have imports like `foo.foo.bar`, which
|
||||
# leads to the conclusion to by default prefer longer paths
|
||||
# rather than shorter ones by default.
|
||||
suffixed += reversed(traversed)
|
||||
# AFAIK some libraries have imports like `foo.foo.bar`, which
|
||||
# leads to the conclusion to by default prefer longer paths
|
||||
# rather than shorter ones by default.
|
||||
suffixed += reversed(traversed)
|
||||
|
||||
if self._django:
|
||||
prefixed.append(self._path)
|
||||
prefixed.append(str(self._path))
|
||||
|
||||
path = prefixed + sys_path + suffixed
|
||||
return list(_force_unicode_list(_remove_duplicates_from_path(path)))
|
||||
|
||||
def save(self):
|
||||
data = dict(self.__dict__)
|
||||
for attribute in self._serializer_ignore_attributes:
|
||||
data.pop(attribute, None)
|
||||
|
||||
with open(self._get_json_path(self._path), 'wb') as f:
|
||||
return json.dump((_SERIALIZER_VERSION, data), f)
|
||||
return list(_remove_duplicates_from_path(path))
|
||||
|
||||
def get_environment(self):
|
||||
if self._environment is None:
|
||||
return get_cached_default_environment()
|
||||
|
||||
if self._environment_path is not None:
|
||||
self._environment = create_environment(self._environment_path, safe=False)
|
||||
else:
|
||||
self._environment = get_cached_default_environment()
|
||||
return self._environment
|
||||
|
||||
def search(self, string, *, all_scopes=False):
|
||||
"""
|
||||
Searches a name in the whole project. If the project is very big,
|
||||
at some point Jedi will stop searching. However it's also very much
|
||||
recommended to not exhaust the generator. Just display the first ten
|
||||
results to the user.
|
||||
|
||||
There are currently three different search patterns:
|
||||
|
||||
- ``foo`` to search for a definition foo in any file or a file called
|
||||
``foo.py`` or ``foo.pyi``.
|
||||
- ``foo.bar`` to search for the ``foo`` and then an attribute ``bar``
|
||||
in it.
|
||||
- ``class foo.bar.Bar`` or ``def foo.bar.baz`` to search for a specific
|
||||
API type.
|
||||
|
||||
:param bool all_scopes: Default False; searches not only for
|
||||
definitions on the top level of a module level, but also in
|
||||
functions and classes.
|
||||
:yields: :class:`.Name`
|
||||
"""
|
||||
return self._search_func(string, all_scopes=all_scopes)
|
||||
|
||||
def complete_search(self, string, **kwargs):
|
||||
"""
|
||||
Like :meth:`.Script.search`, but completes that string. An empty string
|
||||
lists all definitions in a project, so be careful with that.
|
||||
|
||||
:param bool all_scopes: Default False; searches not only for
|
||||
definitions on the top level of a module level, but also in
|
||||
functions and classes.
|
||||
:yields: :class:`.Completion`
|
||||
"""
|
||||
return self._search_func(string, complete=True, **kwargs)
|
||||
|
||||
@_try_to_skip_duplicates
|
||||
def _search_func(self, string, complete=False, all_scopes=False):
|
||||
# Using a Script is they easiest way to get an empty module context.
|
||||
from jedi import Script
|
||||
s = Script('', project=self)
|
||||
inference_state = s._inference_state
|
||||
empty_module_context = s._get_module_context()
|
||||
|
||||
debug.dbg('Search for string %s, complete=%s', string, complete)
|
||||
wanted_type, wanted_names = split_search_string(string)
|
||||
name = wanted_names[0]
|
||||
stub_folder_name = name + '-stubs'
|
||||
|
||||
ios = recurse_find_python_folders_and_files(FolderIO(str(self._path)))
|
||||
file_ios = []
|
||||
|
||||
# 1. Search for modules in the current project
|
||||
for folder_io, file_io in ios:
|
||||
if file_io is None:
|
||||
file_name = folder_io.get_base_name()
|
||||
if file_name == name or file_name == stub_folder_name:
|
||||
f = folder_io.get_file_io('__init__.py')
|
||||
try:
|
||||
m = load_module_from_path(inference_state, f).as_context()
|
||||
except FileNotFoundError:
|
||||
f = folder_io.get_file_io('__init__.pyi')
|
||||
try:
|
||||
m = load_module_from_path(inference_state, f).as_context()
|
||||
except FileNotFoundError:
|
||||
m = load_namespace_from_path(inference_state, folder_io).as_context()
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
file_ios.append(file_io)
|
||||
if Path(file_io.path).name in (name + '.py', name + '.pyi'):
|
||||
m = load_module_from_path(inference_state, file_io).as_context()
|
||||
else:
|
||||
continue
|
||||
|
||||
debug.dbg('Search of a specific module %s', m)
|
||||
yield from search_in_module(
|
||||
inference_state,
|
||||
m,
|
||||
names=[m.name],
|
||||
wanted_type=wanted_type,
|
||||
wanted_names=wanted_names,
|
||||
complete=complete,
|
||||
convert=True,
|
||||
ignore_imports=True,
|
||||
)
|
||||
|
||||
# 2. Search for identifiers in the project.
|
||||
for module_context in search_in_file_ios(inference_state, file_ios, name):
|
||||
names = get_module_names(module_context.tree_node, all_scopes=all_scopes)
|
||||
names = [module_context.create_name(n) for n in names]
|
||||
names = _remove_imports(names)
|
||||
yield from search_in_module(
|
||||
inference_state,
|
||||
module_context,
|
||||
names=names,
|
||||
wanted_type=wanted_type,
|
||||
wanted_names=wanted_names,
|
||||
complete=complete,
|
||||
ignore_imports=True,
|
||||
)
|
||||
|
||||
# 3. Search for modules on sys.path
|
||||
sys_path = [
|
||||
p for p in self._get_sys_path(inference_state)
|
||||
# Exclude folders that are handled by recursing of the Python
|
||||
# folders.
|
||||
if not p.startswith(str(self._path))
|
||||
]
|
||||
names = list(iter_module_names(inference_state, empty_module_context, sys_path))
|
||||
yield from search_in_module(
|
||||
inference_state,
|
||||
empty_module_context,
|
||||
names=names,
|
||||
wanted_type=wanted_type,
|
||||
wanted_names=wanted_names,
|
||||
complete=complete,
|
||||
convert=True,
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._path)
|
||||
|
||||
|
||||
def _is_potential_project(path):
|
||||
for name in _CONTAINS_POTENTIAL_PROJECT:
|
||||
if os.path.exists(os.path.join(path, name)):
|
||||
return True
|
||||
try:
|
||||
if path.joinpath(name).exists():
|
||||
return True
|
||||
except OSError:
|
||||
continue
|
||||
return False
|
||||
|
||||
|
||||
def _is_django_path(directory):
|
||||
""" Detects the path of the very well known Django library (if used) """
|
||||
try:
|
||||
with open(os.path.join(directory, 'manage.py'), 'rb') as f:
|
||||
with open(directory.joinpath('manage.py'), 'rb') as f:
|
||||
return b"DJANGO_SETTINGS_MODULE" in f.read()
|
||||
except (FileNotFoundError, NotADirectoryError):
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||
return False
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_default_project(path=None):
|
||||
if path is None:
|
||||
path = os.getcwd()
|
||||
"""
|
||||
If a project is not defined by the user, Jedi tries to define a project by
|
||||
itself as well as possible. Jedi traverses folders until it finds one of
|
||||
the following:
|
||||
|
||||
check = os.path.realpath(path)
|
||||
1. A ``.jedi/config.json``
|
||||
2. One of the following files: ``setup.py``, ``.git``, ``.hg``,
|
||||
``requirements.txt`` and ``MANIFEST.in``.
|
||||
"""
|
||||
if path is None:
|
||||
path = Path.cwd()
|
||||
elif isinstance(path, str):
|
||||
path = Path(path)
|
||||
|
||||
check = path.absolute()
|
||||
probable_path = None
|
||||
first_no_init_file = None
|
||||
for dir in traverse_parents(check, include_current=True):
|
||||
for dir in chain([check], check.parents):
|
||||
try:
|
||||
return Project.load(dir)
|
||||
except (FileNotFoundError, NotADirectoryError):
|
||||
except (FileNotFoundError, IsADirectoryError, PermissionError):
|
||||
pass
|
||||
except NotADirectoryError:
|
||||
continue
|
||||
|
||||
if first_no_init_file is None:
|
||||
if os.path.exists(os.path.join(dir, '__init__.py')):
|
||||
if dir.joinpath('__init__.py').exists():
|
||||
# In the case that a __init__.py exists, it's in 99% just a
|
||||
# Python package and the project sits at least one level above.
|
||||
continue
|
||||
else:
|
||||
elif not dir.is_file():
|
||||
first_no_init_file = dir
|
||||
|
||||
if _is_django_path(dir):
|
||||
return Project(dir, _django=True)
|
||||
project = Project(dir)
|
||||
project._django = True
|
||||
return project
|
||||
|
||||
if probable_path is None and _is_potential_project(dir):
|
||||
probable_path = dir
|
||||
@@ -191,5 +432,12 @@ def get_default_project(path=None):
|
||||
if first_no_init_file is not None:
|
||||
return Project(first_no_init_file)
|
||||
|
||||
curdir = path if os.path.isdir(path) else os.path.dirname(path)
|
||||
curdir = path if path.is_dir() else path.parent
|
||||
return Project(curdir)
|
||||
|
||||
|
||||
def _remove_imports(names):
|
||||
return [
|
||||
n for n in names
|
||||
if n.tree_name is None or n.api_type != 'module'
|
||||
]
|
||||
|
||||
242
jedi/api/refactoring/__init__.py
Normal file
242
jedi/api/refactoring/__init__.py
Normal file
@@ -0,0 +1,242 @@
|
||||
import difflib
|
||||
from pathlib import Path
|
||||
from typing import Dict, Iterable, Tuple
|
||||
|
||||
from parso import split_lines
|
||||
|
||||
from jedi.api.exceptions import RefactoringError
|
||||
|
||||
EXPRESSION_PARTS = (
|
||||
'or_test and_test not_test comparison '
|
||||
'expr xor_expr and_expr shift_expr arith_expr term factor power atom_expr'
|
||||
).split()
|
||||
|
||||
|
||||
class ChangedFile:
|
||||
def __init__(self, inference_state, from_path, to_path,
|
||||
module_node, node_to_str_map):
|
||||
self._inference_state = inference_state
|
||||
self._from_path = from_path
|
||||
self._to_path = to_path
|
||||
self._module_node = module_node
|
||||
self._node_to_str_map = node_to_str_map
|
||||
|
||||
def get_diff(self):
|
||||
old_lines = split_lines(self._module_node.get_code(), keepends=True)
|
||||
new_lines = split_lines(self.get_new_code(), keepends=True)
|
||||
|
||||
# Add a newline at the end if it's missing. Otherwise the diff will be
|
||||
# very weird. A `diff -u file1 file2` would show the string:
|
||||
#
|
||||
# \ No newline at end of file
|
||||
#
|
||||
# This is not necessary IMO, because Jedi does not really play with
|
||||
# newlines and the ending newline does not really matter in Python
|
||||
# files. ~dave
|
||||
if old_lines[-1] != '':
|
||||
old_lines[-1] += '\n'
|
||||
if new_lines[-1] != '':
|
||||
new_lines[-1] += '\n'
|
||||
|
||||
project_path = self._inference_state.project.path
|
||||
if self._from_path is None:
|
||||
from_p = ''
|
||||
else:
|
||||
from_p = self._from_path.relative_to(project_path)
|
||||
if self._to_path is None:
|
||||
to_p = ''
|
||||
else:
|
||||
to_p = self._to_path.relative_to(project_path)
|
||||
diff = difflib.unified_diff(
|
||||
old_lines, new_lines,
|
||||
fromfile=str(from_p),
|
||||
tofile=str(to_p),
|
||||
)
|
||||
# Apparently there's a space at the end of the diff - for whatever
|
||||
# reason.
|
||||
return ''.join(diff).rstrip(' ')
|
||||
|
||||
def get_new_code(self):
|
||||
return self._inference_state.grammar.refactor(self._module_node, self._node_to_str_map)
|
||||
|
||||
def apply(self):
|
||||
if self._from_path is None:
|
||||
raise RefactoringError(
|
||||
'Cannot apply a refactoring on a Script with path=None'
|
||||
)
|
||||
|
||||
with open(self._from_path, 'w', newline='') as f:
|
||||
f.write(self.get_new_code())
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._from_path)
|
||||
|
||||
|
||||
class Refactoring:
|
||||
def __init__(self, inference_state, file_to_node_changes, renames=()):
|
||||
self._inference_state = inference_state
|
||||
self._renames = renames
|
||||
self._file_to_node_changes = file_to_node_changes
|
||||
|
||||
def get_changed_files(self) -> Dict[Path, ChangedFile]:
|
||||
def calculate_to_path(p):
|
||||
if p is None:
|
||||
return p
|
||||
p = str(p)
|
||||
for from_, to in renames:
|
||||
if p.startswith(str(from_)):
|
||||
p = str(to) + p[len(str(from_)):]
|
||||
return Path(p)
|
||||
|
||||
renames = self.get_renames()
|
||||
return {
|
||||
path: ChangedFile(
|
||||
self._inference_state,
|
||||
from_path=path,
|
||||
to_path=calculate_to_path(path),
|
||||
module_node=next(iter(map_)).get_root_node(),
|
||||
node_to_str_map=map_
|
||||
) for path, map_ in sorted(self._file_to_node_changes.items())
|
||||
}
|
||||
|
||||
def get_renames(self) -> Iterable[Tuple[Path, Path]]:
|
||||
"""
|
||||
Files can be renamed in a refactoring.
|
||||
"""
|
||||
return sorted(self._renames)
|
||||
|
||||
def get_diff(self):
|
||||
text = ''
|
||||
project_path = self._inference_state.project.path
|
||||
for from_, to in self.get_renames():
|
||||
text += 'rename from %s\nrename to %s\n' \
|
||||
% (from_.relative_to(project_path), to.relative_to(project_path))
|
||||
|
||||
return text + ''.join(f.get_diff() for f in self.get_changed_files().values())
|
||||
|
||||
def apply(self):
|
||||
"""
|
||||
Applies the whole refactoring to the files, which includes renames.
|
||||
"""
|
||||
for f in self.get_changed_files().values():
|
||||
f.apply()
|
||||
|
||||
for old, new in self.get_renames():
|
||||
old.rename(new)
|
||||
|
||||
|
||||
def _calculate_rename(path, new_name):
|
||||
dir_ = path.parent
|
||||
if path.name in ('__init__.py', '__init__.pyi'):
|
||||
return dir_, dir_.parent.joinpath(new_name)
|
||||
return path, dir_.joinpath(new_name + path.suffix)
|
||||
|
||||
|
||||
def rename(inference_state, definitions, new_name):
|
||||
file_renames = set()
|
||||
file_tree_name_map = {}
|
||||
|
||||
if not definitions:
|
||||
raise RefactoringError("There is no name under the cursor")
|
||||
|
||||
for d in definitions:
|
||||
tree_name = d._name.tree_name
|
||||
if d.type == 'module' and tree_name is None:
|
||||
p = None if d.module_path is None else Path(d.module_path)
|
||||
file_renames.add(_calculate_rename(p, new_name))
|
||||
else:
|
||||
# This private access is ok in a way. It's not public to
|
||||
# protect Jedi users from seeing it.
|
||||
if tree_name is not None:
|
||||
fmap = file_tree_name_map.setdefault(d.module_path, {})
|
||||
fmap[tree_name] = tree_name.prefix + new_name
|
||||
return Refactoring(inference_state, file_tree_name_map, file_renames)
|
||||
|
||||
|
||||
def inline(inference_state, names):
|
||||
if not names:
|
||||
raise RefactoringError("There is no name under the cursor")
|
||||
if any(n.api_type == 'module' for n in names):
|
||||
raise RefactoringError("Cannot inline imports or modules")
|
||||
if any(n.tree_name is None for n in names):
|
||||
raise RefactoringError("Cannot inline builtins/extensions")
|
||||
|
||||
definitions = [n for n in names if n.tree_name.is_definition()]
|
||||
if len(definitions) == 0:
|
||||
raise RefactoringError("No definition found to inline")
|
||||
if len(definitions) > 1:
|
||||
raise RefactoringError("Cannot inline a name with multiple definitions")
|
||||
if len(names) == 1:
|
||||
raise RefactoringError("There are no references to this name")
|
||||
|
||||
tree_name = definitions[0].tree_name
|
||||
|
||||
expr_stmt = tree_name.get_definition()
|
||||
if expr_stmt.type != 'expr_stmt':
|
||||
type_ = dict(
|
||||
funcdef='function',
|
||||
classdef='class',
|
||||
).get(expr_stmt.type, expr_stmt.type)
|
||||
raise RefactoringError("Cannot inline a %s" % type_)
|
||||
|
||||
if len(expr_stmt.get_defined_names(include_setitem=True)) > 1:
|
||||
raise RefactoringError("Cannot inline a statement with multiple definitions")
|
||||
first_child = expr_stmt.children[1]
|
||||
if first_child.type == 'annassign' and len(first_child.children) == 4:
|
||||
first_child = first_child.children[2]
|
||||
if first_child != '=':
|
||||
if first_child.type == 'annassign':
|
||||
raise RefactoringError(
|
||||
'Cannot inline a statement that is defined by an annotation'
|
||||
)
|
||||
else:
|
||||
raise RefactoringError(
|
||||
'Cannot inline a statement with "%s"'
|
||||
% first_child.get_code(include_prefix=False)
|
||||
)
|
||||
|
||||
rhs = expr_stmt.get_rhs()
|
||||
replace_code = rhs.get_code(include_prefix=False)
|
||||
|
||||
references = [n for n in names if not n.tree_name.is_definition()]
|
||||
file_to_node_changes = {}
|
||||
for name in references:
|
||||
tree_name = name.tree_name
|
||||
path = name.get_root_context().py__file__()
|
||||
s = replace_code
|
||||
if rhs.type == 'testlist_star_expr' \
|
||||
or tree_name.parent.type in EXPRESSION_PARTS \
|
||||
or tree_name.parent.type == 'trailer' \
|
||||
and tree_name.parent.get_next_sibling() is not None:
|
||||
s = '(' + replace_code + ')'
|
||||
|
||||
of_path = file_to_node_changes.setdefault(path, {})
|
||||
|
||||
n = tree_name
|
||||
prefix = n.prefix
|
||||
par = n.parent
|
||||
if par.type == 'trailer' and par.children[0] == '.':
|
||||
prefix = par.parent.children[0].prefix
|
||||
n = par
|
||||
for some_node in par.parent.children[:par.parent.children.index(par)]:
|
||||
of_path[some_node] = ''
|
||||
of_path[n] = prefix + s
|
||||
|
||||
path = definitions[0].get_root_context().py__file__()
|
||||
changes = file_to_node_changes.setdefault(path, {})
|
||||
changes[expr_stmt] = _remove_indent_of_prefix(expr_stmt.get_first_leaf().prefix)
|
||||
next_leaf = expr_stmt.get_next_leaf()
|
||||
|
||||
# Most of the time we have to remove the newline at the end of the
|
||||
# statement, but if there's a comment we might not need to.
|
||||
if next_leaf.prefix.strip(' \t') == '' \
|
||||
and (next_leaf.type == 'newline' or next_leaf == ';'):
|
||||
changes[next_leaf] = ''
|
||||
return Refactoring(inference_state, file_to_node_changes)
|
||||
|
||||
|
||||
def _remove_indent_of_prefix(prefix):
|
||||
r"""
|
||||
Removes the last indentation of a prefix, e.g. " \n \n " becomes " \n \n".
|
||||
"""
|
||||
return ''.join(split_lines(prefix, keepends=True)[:-1])
|
||||
386
jedi/api/refactoring/extract.py
Normal file
386
jedi/api/refactoring/extract.py
Normal file
@@ -0,0 +1,386 @@
|
||||
from textwrap import dedent
|
||||
|
||||
from parso import split_lines
|
||||
|
||||
from jedi import debug
|
||||
from jedi.api.exceptions import RefactoringError
|
||||
from jedi.api.refactoring import Refactoring, EXPRESSION_PARTS
|
||||
from jedi.common import indent_block
|
||||
from jedi.parser_utils import function_is_classmethod, function_is_staticmethod
|
||||
|
||||
|
||||
_DEFINITION_SCOPES = ('suite', 'file_input')
|
||||
_VARIABLE_EXCTRACTABLE = EXPRESSION_PARTS + \
|
||||
('atom testlist_star_expr testlist test lambdef lambdef_nocond '
|
||||
'keyword name number string fstring').split()
|
||||
|
||||
|
||||
def extract_variable(inference_state, path, module_node, name, pos, until_pos):
|
||||
nodes = _find_nodes(module_node, pos, until_pos)
|
||||
debug.dbg('Extracting nodes: %s', nodes)
|
||||
|
||||
is_expression, message = _is_expression_with_error(nodes)
|
||||
if not is_expression:
|
||||
raise RefactoringError(message)
|
||||
|
||||
generated_code = name + ' = ' + _expression_nodes_to_string(nodes)
|
||||
file_to_node_changes = {path: _replace(nodes, name, generated_code, pos)}
|
||||
return Refactoring(inference_state, file_to_node_changes)
|
||||
|
||||
|
||||
def _is_expression_with_error(nodes):
|
||||
"""
|
||||
Returns a tuple (is_expression, error_string).
|
||||
"""
|
||||
if any(node.type == 'name' and node.is_definition() for node in nodes):
|
||||
return False, 'Cannot extract a name that defines something'
|
||||
|
||||
if nodes[0].type not in _VARIABLE_EXCTRACTABLE:
|
||||
return False, 'Cannot extract a "%s"' % nodes[0].type
|
||||
return True, ''
|
||||
|
||||
|
||||
def _find_nodes(module_node, pos, until_pos):
|
||||
"""
|
||||
Looks up a module and tries to find the appropriate amount of nodes that
|
||||
are in there.
|
||||
"""
|
||||
start_node = module_node.get_leaf_for_position(pos, include_prefixes=True)
|
||||
|
||||
if until_pos is None:
|
||||
if start_node.type == 'operator':
|
||||
next_leaf = start_node.get_next_leaf()
|
||||
if next_leaf is not None and next_leaf.start_pos == pos:
|
||||
start_node = next_leaf
|
||||
|
||||
if _is_not_extractable_syntax(start_node):
|
||||
start_node = start_node.parent
|
||||
|
||||
if start_node.parent.type == 'trailer':
|
||||
start_node = start_node.parent.parent
|
||||
while start_node.parent.type in EXPRESSION_PARTS:
|
||||
start_node = start_node.parent
|
||||
|
||||
nodes = [start_node]
|
||||
else:
|
||||
# Get the next leaf if we are at the end of a leaf
|
||||
if start_node.end_pos == pos:
|
||||
next_leaf = start_node.get_next_leaf()
|
||||
if next_leaf is not None:
|
||||
start_node = next_leaf
|
||||
|
||||
# Some syntax is not exactable, just use its parent
|
||||
if _is_not_extractable_syntax(start_node):
|
||||
start_node = start_node.parent
|
||||
|
||||
# Find the end
|
||||
end_leaf = module_node.get_leaf_for_position(until_pos, include_prefixes=True)
|
||||
if end_leaf.start_pos > until_pos:
|
||||
end_leaf = end_leaf.get_previous_leaf()
|
||||
if end_leaf is None:
|
||||
raise RefactoringError('Cannot extract anything from that')
|
||||
|
||||
parent_node = start_node
|
||||
while parent_node.end_pos < end_leaf.end_pos:
|
||||
parent_node = parent_node.parent
|
||||
|
||||
nodes = _remove_unwanted_expression_nodes(parent_node, pos, until_pos)
|
||||
|
||||
# If the user marks just a return statement, we return the expression
|
||||
# instead of the whole statement, because the user obviously wants to
|
||||
# extract that part.
|
||||
if len(nodes) == 1 and start_node.type in ('return_stmt', 'yield_expr'):
|
||||
return [nodes[0].children[1]]
|
||||
return nodes
|
||||
|
||||
|
||||
def _replace(nodes, expression_replacement, extracted, pos,
|
||||
insert_before_leaf=None, remaining_prefix=None):
|
||||
# Now try to replace the nodes found with a variable and move the code
|
||||
# before the current statement.
|
||||
definition = _get_parent_definition(nodes[0])
|
||||
if insert_before_leaf is None:
|
||||
insert_before_leaf = definition.get_first_leaf()
|
||||
first_node_leaf = nodes[0].get_first_leaf()
|
||||
|
||||
lines = split_lines(insert_before_leaf.prefix, keepends=True)
|
||||
if first_node_leaf is insert_before_leaf:
|
||||
if remaining_prefix is not None:
|
||||
# The remaining prefix has already been calculated.
|
||||
lines[:-1] = remaining_prefix
|
||||
lines[-1:-1] = [indent_block(extracted, lines[-1]) + '\n']
|
||||
extracted_prefix = ''.join(lines)
|
||||
|
||||
replacement_dct = {}
|
||||
if first_node_leaf is insert_before_leaf:
|
||||
replacement_dct[nodes[0]] = extracted_prefix + expression_replacement
|
||||
else:
|
||||
if remaining_prefix is None:
|
||||
p = first_node_leaf.prefix
|
||||
else:
|
||||
p = remaining_prefix + _get_indentation(nodes[0])
|
||||
replacement_dct[nodes[0]] = p + expression_replacement
|
||||
replacement_dct[insert_before_leaf] = extracted_prefix + insert_before_leaf.value
|
||||
|
||||
for node in nodes[1:]:
|
||||
replacement_dct[node] = ''
|
||||
return replacement_dct
|
||||
|
||||
|
||||
def _expression_nodes_to_string(nodes):
|
||||
return ''.join(n.get_code(include_prefix=i != 0) for i, n in enumerate(nodes))
|
||||
|
||||
|
||||
def _suite_nodes_to_string(nodes, pos):
|
||||
n = nodes[0]
|
||||
prefix, part_of_code = _split_prefix_at(n.get_first_leaf(), pos[0] - 1)
|
||||
code = part_of_code + n.get_code(include_prefix=False) \
|
||||
+ ''.join(n.get_code() for n in nodes[1:])
|
||||
return prefix, code
|
||||
|
||||
|
||||
def _split_prefix_at(leaf, until_line):
|
||||
"""
|
||||
Returns a tuple of the leaf's prefix, split at the until_line
|
||||
position.
|
||||
"""
|
||||
# second means the second returned part
|
||||
second_line_count = leaf.start_pos[0] - until_line
|
||||
lines = split_lines(leaf.prefix, keepends=True)
|
||||
return ''.join(lines[:-second_line_count]), ''.join(lines[-second_line_count:])
|
||||
|
||||
|
||||
def _get_indentation(node):
|
||||
return split_lines(node.get_first_leaf().prefix)[-1]
|
||||
|
||||
|
||||
def _get_parent_definition(node):
|
||||
"""
|
||||
Returns the statement where a node is defined.
|
||||
"""
|
||||
while node is not None:
|
||||
if node.parent.type in _DEFINITION_SCOPES:
|
||||
return node
|
||||
node = node.parent
|
||||
raise NotImplementedError('We should never even get here')
|
||||
|
||||
|
||||
def _remove_unwanted_expression_nodes(parent_node, pos, until_pos):
|
||||
"""
|
||||
This function makes it so for `1 * 2 + 3` you can extract `2 + 3`, even
|
||||
though it is not part of the expression.
|
||||
"""
|
||||
typ = parent_node.type
|
||||
is_suite_part = typ in ('suite', 'file_input')
|
||||
if typ in EXPRESSION_PARTS or is_suite_part:
|
||||
nodes = parent_node.children
|
||||
for i, n in enumerate(nodes):
|
||||
if n.end_pos > pos:
|
||||
start_index = i
|
||||
if n.type == 'operator':
|
||||
start_index -= 1
|
||||
break
|
||||
for i, n in reversed(list(enumerate(nodes))):
|
||||
if n.start_pos < until_pos:
|
||||
end_index = i
|
||||
if n.type == 'operator':
|
||||
end_index += 1
|
||||
|
||||
# Something like `not foo or bar` should not be cut after not
|
||||
for n2 in nodes[i:]:
|
||||
if _is_not_extractable_syntax(n2):
|
||||
end_index += 1
|
||||
else:
|
||||
break
|
||||
break
|
||||
nodes = nodes[start_index:end_index + 1]
|
||||
if not is_suite_part:
|
||||
nodes[0:1] = _remove_unwanted_expression_nodes(nodes[0], pos, until_pos)
|
||||
nodes[-1:] = _remove_unwanted_expression_nodes(nodes[-1], pos, until_pos)
|
||||
return nodes
|
||||
return [parent_node]
|
||||
|
||||
|
||||
def _is_not_extractable_syntax(node):
|
||||
return node.type == 'operator' \
|
||||
or node.type == 'keyword' and node.value not in ('None', 'True', 'False')
|
||||
|
||||
|
||||
def extract_function(inference_state, path, module_context, name, pos, until_pos):
|
||||
nodes = _find_nodes(module_context.tree_node, pos, until_pos)
|
||||
assert len(nodes)
|
||||
|
||||
is_expression, _ = _is_expression_with_error(nodes)
|
||||
context = module_context.create_context(nodes[0])
|
||||
is_bound_method = context.is_bound_method()
|
||||
params, return_variables = list(_find_inputs_and_outputs(module_context, context, nodes))
|
||||
|
||||
# Find variables
|
||||
# Is a class method / method
|
||||
if context.is_module():
|
||||
insert_before_leaf = None # Leaf will be determined later
|
||||
else:
|
||||
node = _get_code_insertion_node(context.tree_node, is_bound_method)
|
||||
insert_before_leaf = node.get_first_leaf()
|
||||
if is_expression:
|
||||
code_block = 'return ' + _expression_nodes_to_string(nodes) + '\n'
|
||||
remaining_prefix = None
|
||||
has_ending_return_stmt = False
|
||||
else:
|
||||
has_ending_return_stmt = _is_node_ending_return_stmt(nodes[-1])
|
||||
if not has_ending_return_stmt:
|
||||
# Find the actually used variables (of the defined ones). If none are
|
||||
# used (e.g. if the range covers the whole function), return the last
|
||||
# defined variable.
|
||||
return_variables = list(_find_needed_output_variables(
|
||||
context,
|
||||
nodes[0].parent,
|
||||
nodes[-1].end_pos,
|
||||
return_variables
|
||||
)) or [return_variables[-1]] if return_variables else []
|
||||
|
||||
remaining_prefix, code_block = _suite_nodes_to_string(nodes, pos)
|
||||
after_leaf = nodes[-1].get_next_leaf()
|
||||
first, second = _split_prefix_at(after_leaf, until_pos[0])
|
||||
code_block += first
|
||||
|
||||
code_block = dedent(code_block)
|
||||
if not has_ending_return_stmt:
|
||||
output_var_str = ', '.join(return_variables)
|
||||
code_block += 'return ' + output_var_str + '\n'
|
||||
|
||||
# Check if we have to raise RefactoringError
|
||||
_check_for_non_extractables(nodes[:-1] if has_ending_return_stmt else nodes)
|
||||
|
||||
decorator = ''
|
||||
self_param = None
|
||||
if is_bound_method:
|
||||
if not function_is_staticmethod(context.tree_node):
|
||||
function_param_names = context.get_value().get_param_names()
|
||||
if len(function_param_names):
|
||||
self_param = function_param_names[0].string_name
|
||||
params = [p for p in params if p != self_param]
|
||||
|
||||
if function_is_classmethod(context.tree_node):
|
||||
decorator = '@classmethod\n'
|
||||
else:
|
||||
code_block += '\n'
|
||||
|
||||
function_code = '%sdef %s(%s):\n%s' % (
|
||||
decorator,
|
||||
name,
|
||||
', '.join(params if self_param is None else [self_param] + params),
|
||||
indent_block(code_block)
|
||||
)
|
||||
|
||||
function_call = '%s(%s)' % (
|
||||
('' if self_param is None else self_param + '.') + name,
|
||||
', '.join(params)
|
||||
)
|
||||
if is_expression:
|
||||
replacement = function_call
|
||||
else:
|
||||
if has_ending_return_stmt:
|
||||
replacement = 'return ' + function_call + '\n'
|
||||
else:
|
||||
replacement = output_var_str + ' = ' + function_call + '\n'
|
||||
|
||||
replacement_dct = _replace(nodes, replacement, function_code, pos,
|
||||
insert_before_leaf, remaining_prefix)
|
||||
if not is_expression:
|
||||
replacement_dct[after_leaf] = second + after_leaf.value
|
||||
file_to_node_changes = {path: replacement_dct}
|
||||
return Refactoring(inference_state, file_to_node_changes)
|
||||
|
||||
|
||||
def _check_for_non_extractables(nodes):
|
||||
for n in nodes:
|
||||
try:
|
||||
children = n.children
|
||||
except AttributeError:
|
||||
if n.value == 'return':
|
||||
raise RefactoringError(
|
||||
'Can only extract return statements if they are at the end.')
|
||||
if n.value == 'yield':
|
||||
raise RefactoringError('Cannot extract yield statements.')
|
||||
else:
|
||||
_check_for_non_extractables(children)
|
||||
|
||||
|
||||
def _is_name_input(module_context, names, first, last):
|
||||
for name in names:
|
||||
if name.api_type == 'param' or not name.parent_context.is_module():
|
||||
if name.get_root_context() is not module_context:
|
||||
return True
|
||||
if name.start_pos is None or not (first <= name.start_pos < last):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _find_inputs_and_outputs(module_context, context, nodes):
|
||||
first = nodes[0].start_pos
|
||||
last = nodes[-1].end_pos
|
||||
|
||||
inputs = []
|
||||
outputs = []
|
||||
for name in _find_non_global_names(nodes):
|
||||
if name.is_definition():
|
||||
if name not in outputs:
|
||||
outputs.append(name.value)
|
||||
else:
|
||||
if name.value not in inputs:
|
||||
name_definitions = context.goto(name, name.start_pos)
|
||||
if not name_definitions \
|
||||
or _is_name_input(module_context, name_definitions, first, last):
|
||||
inputs.append(name.value)
|
||||
|
||||
# Check if outputs are really needed:
|
||||
return inputs, outputs
|
||||
|
||||
|
||||
def _find_non_global_names(nodes):
|
||||
for node in nodes:
|
||||
try:
|
||||
children = node.children
|
||||
except AttributeError:
|
||||
if node.type == 'name':
|
||||
yield node
|
||||
else:
|
||||
# We only want to check foo in foo.bar
|
||||
if node.type == 'trailer' and node.children[0] == '.':
|
||||
continue
|
||||
|
||||
yield from _find_non_global_names(children)
|
||||
|
||||
|
||||
def _get_code_insertion_node(node, is_bound_method):
|
||||
if not is_bound_method or function_is_staticmethod(node):
|
||||
while node.parent.type != 'file_input':
|
||||
node = node.parent
|
||||
|
||||
while node.parent.type in ('async_funcdef', 'decorated', 'async_stmt'):
|
||||
node = node.parent
|
||||
return node
|
||||
|
||||
|
||||
def _find_needed_output_variables(context, search_node, at_least_pos, return_variables):
|
||||
"""
|
||||
Searches everything after at_least_pos in a node and checks if any of the
|
||||
return_variables are used in there and returns those.
|
||||
"""
|
||||
for node in search_node.children:
|
||||
if node.start_pos < at_least_pos:
|
||||
continue
|
||||
|
||||
return_variables = set(return_variables)
|
||||
for name in _find_non_global_names([node]):
|
||||
if not name.is_definition() and name.value in return_variables:
|
||||
return_variables.remove(name.value)
|
||||
yield name.value
|
||||
|
||||
|
||||
def _is_node_ending_return_stmt(node):
|
||||
t = node.type
|
||||
if t == 'simple_stmt':
|
||||
return _is_node_ending_return_stmt(node.children[0])
|
||||
return t == 'return_stmt'
|
||||
@@ -9,7 +9,7 @@ just use IPython instead::
|
||||
Then you will be able to use Jedi completer in your Python interpreter::
|
||||
|
||||
$ python
|
||||
Python 2.7.2+ (default, Jul 20 2012, 22:15:08)
|
||||
Python 3.9.2+ (default, Jul 20 2020, 22:15:08)
|
||||
[GCC 4.6.1] on linux2
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> import os
|
||||
@@ -21,7 +21,7 @@ import jedi.utils
|
||||
from jedi import __version__ as __jedi_version__
|
||||
|
||||
print('REPL completion using Jedi %s' % __jedi_version__)
|
||||
jedi.utils.setup_readline()
|
||||
jedi.utils.setup_readline(fuzzy=False)
|
||||
|
||||
del jedi
|
||||
|
||||
|
||||
108
jedi/api/strings.py
Normal file
108
jedi/api/strings.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
This module is here for string completions. This means mostly stuff where
|
||||
strings are returned, like `foo = dict(bar=3); foo["ba` would complete to
|
||||
`"bar"]`.
|
||||
|
||||
It however does the same for numbers. The difference between string completions
|
||||
and other completions is mostly that this module doesn't return defined
|
||||
names in a module, but pretty much an arbitrary string.
|
||||
"""
|
||||
import re
|
||||
|
||||
from jedi.inference.names import AbstractArbitraryName
|
||||
from jedi.inference.helpers import infer_call_of_leaf
|
||||
from jedi.api.classes import Completion
|
||||
from jedi.parser_utils import cut_value_at_position
|
||||
|
||||
_sentinel = object()
|
||||
|
||||
|
||||
class StringName(AbstractArbitraryName):
|
||||
api_type = 'string'
|
||||
is_value_name = False
|
||||
|
||||
|
||||
def complete_dict(module_context, code_lines, leaf, position, string, fuzzy):
|
||||
bracket_leaf = leaf
|
||||
if bracket_leaf != '[':
|
||||
bracket_leaf = leaf.get_previous_leaf()
|
||||
|
||||
cut_end_quote = ''
|
||||
if string:
|
||||
cut_end_quote = get_quote_ending(string, code_lines, position, invert_result=True)
|
||||
|
||||
if bracket_leaf == '[':
|
||||
if string is None and leaf is not bracket_leaf:
|
||||
string = cut_value_at_position(leaf, position)
|
||||
|
||||
context = module_context.create_context(bracket_leaf)
|
||||
before_bracket_leaf = bracket_leaf.get_previous_leaf()
|
||||
if before_bracket_leaf.type in ('atom', 'trailer', 'name'):
|
||||
values = infer_call_of_leaf(context, before_bracket_leaf)
|
||||
return list(_completions_for_dicts(
|
||||
module_context.inference_state,
|
||||
values,
|
||||
'' if string is None else string,
|
||||
cut_end_quote,
|
||||
fuzzy=fuzzy,
|
||||
))
|
||||
return []
|
||||
|
||||
|
||||
def _completions_for_dicts(inference_state, dicts, literal_string, cut_end_quote, fuzzy):
|
||||
for dict_key in sorted(_get_python_keys(dicts), key=lambda x: repr(x)):
|
||||
dict_key_str = _create_repr_string(literal_string, dict_key)
|
||||
if dict_key_str.startswith(literal_string):
|
||||
name = StringName(inference_state, dict_key_str[:-len(cut_end_quote) or None])
|
||||
yield Completion(
|
||||
inference_state,
|
||||
name,
|
||||
stack=None,
|
||||
like_name_length=len(literal_string),
|
||||
is_fuzzy=fuzzy
|
||||
)
|
||||
|
||||
|
||||
def _create_repr_string(literal_string, dict_key):
|
||||
if not isinstance(dict_key, (str, bytes)) or not literal_string:
|
||||
return repr(dict_key)
|
||||
|
||||
r = repr(dict_key)
|
||||
prefix, quote = _get_string_prefix_and_quote(literal_string)
|
||||
if quote is None:
|
||||
return r
|
||||
if quote == r[0]:
|
||||
return prefix + r
|
||||
return prefix + quote + r[1:-1] + quote
|
||||
|
||||
|
||||
def _get_python_keys(dicts):
|
||||
for dct in dicts:
|
||||
if dct.array_type == 'dict':
|
||||
for key in dct.get_key_values():
|
||||
dict_key = key.get_safe_value(default=_sentinel)
|
||||
if dict_key is not _sentinel:
|
||||
yield dict_key
|
||||
|
||||
|
||||
def _get_string_prefix_and_quote(string):
|
||||
match = re.match(r'(\w*)("""|\'{3}|"|\')', string)
|
||||
if match is None:
|
||||
return None, None
|
||||
return match.group(1), match.group(2)
|
||||
|
||||
|
||||
def _matches_quote_at_position(code_lines, quote, position):
|
||||
string = code_lines[position[0] - 1][position[1]:position[1] + len(quote)]
|
||||
return string == quote
|
||||
|
||||
|
||||
def get_quote_ending(string, code_lines, position, invert_result=False):
|
||||
_, quote = _get_string_prefix_and_quote(string)
|
||||
if quote is None:
|
||||
return ''
|
||||
|
||||
# Add a quote only if it's not already there.
|
||||
if _matches_quote_at_position(code_lines, quote, position) != invert_result:
|
||||
return ''
|
||||
return quote
|
||||
@@ -13,46 +13,15 @@ these variables are being cleaned after every API usage.
|
||||
"""
|
||||
import time
|
||||
from functools import wraps
|
||||
from typing import Any, Dict, Tuple
|
||||
|
||||
from jedi import settings
|
||||
from parso.cache import parser_cache
|
||||
|
||||
_time_caches = {}
|
||||
_time_caches: Dict[str, Dict[Any, Tuple[float, Any]]] = {}
|
||||
|
||||
|
||||
def underscore_memoization(func):
|
||||
"""
|
||||
Decorator for methods::
|
||||
|
||||
class A(object):
|
||||
def x(self):
|
||||
if self._x:
|
||||
self._x = 10
|
||||
return self._x
|
||||
|
||||
Becomes::
|
||||
|
||||
class A(object):
|
||||
@underscore_memoization
|
||||
def x(self):
|
||||
return 10
|
||||
|
||||
A now has an attribute ``_x`` written by this decorator.
|
||||
"""
|
||||
name = '_' + func.__name__
|
||||
|
||||
def wrapper(self):
|
||||
try:
|
||||
return getattr(self, name)
|
||||
except AttributeError:
|
||||
result = func(self)
|
||||
setattr(self, name, result)
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def clear_time_caches(delete_all=False):
|
||||
def clear_time_caches(delete_all: bool = False) -> None:
|
||||
""" Jedi caches many things, that should be completed after each completion
|
||||
finishes.
|
||||
|
||||
@@ -75,7 +44,7 @@ def clear_time_caches(delete_all=False):
|
||||
del tc[key]
|
||||
|
||||
|
||||
def call_signature_time_cache(time_add_setting):
|
||||
def signature_time_cache(time_add_setting):
|
||||
"""
|
||||
This decorator works as follows: Call it with a setting and after that
|
||||
use the function with a callable that returns the key.
|
||||
|
||||
24
jedi/common.py
Normal file
24
jedi/common.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
@contextmanager
|
||||
def monkeypatch(obj, attribute_name, new_value):
|
||||
"""
|
||||
Like pytest's monkeypatch, but as a value manager.
|
||||
"""
|
||||
old_value = getattr(obj, attribute_name)
|
||||
try:
|
||||
setattr(obj, attribute_name, new_value)
|
||||
yield
|
||||
finally:
|
||||
setattr(obj, attribute_name, old_value)
|
||||
|
||||
|
||||
def indent_block(text, indention=' '):
|
||||
"""This function indents a text block with a default of four spaces."""
|
||||
temp = ''
|
||||
while text and text[-1] == '\n':
|
||||
temp += text[-1]
|
||||
text = text[:-1]
|
||||
lines = text.split('\n')
|
||||
return '\n'.join(map(lambda s: indention + s, lines)) + temp
|
||||
@@ -1 +0,0 @@
|
||||
from jedi.common.context import BaseContextSet, BaseContext
|
||||
@@ -1,67 +0,0 @@
|
||||
class BaseContext(object):
|
||||
def __init__(self, evaluator, parent_context=None):
|
||||
self.evaluator = evaluator
|
||||
self.parent_context = parent_context
|
||||
|
||||
def get_root_context(self):
|
||||
context = self
|
||||
while True:
|
||||
if context.parent_context is None:
|
||||
return context
|
||||
context = context.parent_context
|
||||
|
||||
|
||||
class BaseContextSet(object):
|
||||
def __init__(self, *args):
|
||||
self._set = set(args)
|
||||
|
||||
@classmethod
|
||||
def from_iterable(cls, iterable):
|
||||
return cls.from_set(set(iterable))
|
||||
|
||||
@classmethod
|
||||
def from_set(cls, set_):
|
||||
self = cls()
|
||||
self._set = set_
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_sets(cls, sets):
|
||||
"""
|
||||
Used to work with an iterable of set.
|
||||
"""
|
||||
aggregated = set()
|
||||
sets = list(sets)
|
||||
for set_ in sets:
|
||||
if isinstance(set_, BaseContextSet):
|
||||
aggregated |= set_._set
|
||||
else:
|
||||
aggregated |= set_
|
||||
return cls.from_set(aggregated)
|
||||
|
||||
def __or__(self, other):
|
||||
return type(self).from_set(self._set | other._set)
|
||||
|
||||
def __iter__(self):
|
||||
for element in self._set:
|
||||
yield element
|
||||
|
||||
def __bool__(self):
|
||||
return bool(self._set)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._set)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, ', '.join(str(s) for s in self._set))
|
||||
|
||||
def filter(self, filter_func):
|
||||
return type(self).from_iterable(filter(filter_func, self._set))
|
||||
|
||||
def __getattr__(self, name):
|
||||
def mapper(*args, **kwargs):
|
||||
return type(self).from_sets(
|
||||
getattr(context, name)(*args, **kwargs)
|
||||
for context in self._set
|
||||
)
|
||||
return mapper
|
||||
@@ -1,26 +0,0 @@
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
def traverse_parents(path, include_current=False):
|
||||
if not include_current:
|
||||
path = os.path.dirname(path)
|
||||
|
||||
previous = None
|
||||
while previous != path:
|
||||
yield path
|
||||
previous = path
|
||||
path = os.path.dirname(path)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def monkeypatch(obj, attribute_name, new_value):
|
||||
"""
|
||||
Like pytest's monkeypatch, but as a context manager.
|
||||
"""
|
||||
old_value = getattr(obj, attribute_name)
|
||||
try:
|
||||
setattr(obj, attribute_name, new_value)
|
||||
yield
|
||||
finally:
|
||||
setattr(obj, attribute_name, old_value)
|
||||
@@ -1,6 +1,7 @@
|
||||
from jedi._compatibility import encoding, is_py3, u
|
||||
import os
|
||||
import time
|
||||
from contextlib import contextmanager
|
||||
from typing import Callable, Optional
|
||||
|
||||
_inited = False
|
||||
|
||||
@@ -20,7 +21,7 @@ try:
|
||||
raise ImportError
|
||||
else:
|
||||
# Use colorama for nicer console output.
|
||||
from colorama import Fore, init
|
||||
from colorama import Fore, init # type: ignore[import]
|
||||
from colorama import initialise
|
||||
|
||||
def _lazy_colorama_init(): # noqa: F811
|
||||
@@ -45,12 +46,13 @@ try:
|
||||
_inited = True
|
||||
|
||||
except ImportError:
|
||||
class Fore(object):
|
||||
class Fore: # type: ignore[no-redef]
|
||||
RED = ''
|
||||
GREEN = ''
|
||||
YELLOW = ''
|
||||
MAGENTA = ''
|
||||
RESET = ''
|
||||
BLUE = ''
|
||||
|
||||
NOTICE = object()
|
||||
WARNING = object()
|
||||
@@ -61,7 +63,7 @@ enable_warning = False
|
||||
enable_notice = False
|
||||
|
||||
# callback, interface: level, str
|
||||
debug_function = None
|
||||
debug_function: Optional[Callable[[str, str], None]] = None
|
||||
_debug_indent = 0
|
||||
_start_time = time.time()
|
||||
|
||||
@@ -75,25 +77,33 @@ def reset_time():
|
||||
def increase_indent(func):
|
||||
"""Decorator for makin """
|
||||
def wrapper(*args, **kwargs):
|
||||
global _debug_indent
|
||||
_debug_indent += 1
|
||||
try:
|
||||
with increase_indent_cm():
|
||||
return func(*args, **kwargs)
|
||||
finally:
|
||||
_debug_indent -= 1
|
||||
return wrapper
|
||||
|
||||
|
||||
def dbg(message, *args, **kwargs):
|
||||
@contextmanager
|
||||
def increase_indent_cm(title=None, color='MAGENTA'):
|
||||
global _debug_indent
|
||||
if title:
|
||||
dbg('Start: ' + title, color=color)
|
||||
_debug_indent += 1
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
_debug_indent -= 1
|
||||
if title:
|
||||
dbg('End: ' + title, color=color)
|
||||
|
||||
|
||||
def dbg(message, *args, color='GREEN'):
|
||||
""" Looks at the stack, to see if a debug message should be printed. """
|
||||
# Python 2 compatibility, because it doesn't understand default args
|
||||
color = kwargs.pop('color', 'GREEN')
|
||||
assert color
|
||||
|
||||
if debug_function and enable_notice:
|
||||
i = ' ' * _debug_indent
|
||||
_lazy_colorama_init()
|
||||
debug_function(color, i + 'dbg: ' + message % tuple(u(repr(a)) for a in args))
|
||||
debug_function(color, i + 'dbg: ' + message % tuple(repr(a) for a in args))
|
||||
|
||||
|
||||
def warning(message, *args, **kwargs):
|
||||
@@ -103,7 +113,7 @@ def warning(message, *args, **kwargs):
|
||||
if debug_function and enable_warning:
|
||||
i = ' ' * _debug_indent
|
||||
if format:
|
||||
message = message % tuple(u(repr(a)) for a in args)
|
||||
message = message % tuple(repr(a) for a in args)
|
||||
debug_function('RED', i + 'warning: ' + message)
|
||||
|
||||
|
||||
@@ -122,9 +132,4 @@ def print_to_stdout(color, str_out):
|
||||
"""
|
||||
col = getattr(Fore, color)
|
||||
_lazy_colorama_init()
|
||||
if not is_py3:
|
||||
str_out = str_out.encode(encoding, 'replace')
|
||||
print(col + str_out + Fore.RESET)
|
||||
|
||||
|
||||
# debug_function = print_to_stdout
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
"""
|
||||
Evaluation of Python code in |jedi| is based on three assumptions:
|
||||
|
||||
* The code uses as least side effects as possible. Jedi understands certain
|
||||
list/tuple/set modifications, but there's no guarantee that Jedi detects
|
||||
everything (list.append in different modules for example).
|
||||
* No magic is being used:
|
||||
|
||||
- metaclasses
|
||||
- ``setattr()`` / ``__import__()``
|
||||
- writing to ``globals()``, ``locals()``, ``object.__dict__``
|
||||
* The programmer is not a total dick, e.g. like `this
|
||||
<https://github.com/davidhalter/jedi/issues/24>`_ :-)
|
||||
|
||||
The actual algorithm is based on a principle called lazy evaluation. That
|
||||
said, the typical entry point for static analysis is calling
|
||||
``eval_expr_stmt``. There's separate logic for autocompletion in the API, the
|
||||
evaluator is all about evaluating an expression.
|
||||
|
||||
TODO this paragraph is not what jedi does anymore, it's similar, but not the
|
||||
same.
|
||||
|
||||
Now you need to understand what follows after ``eval_expr_stmt``. Let's
|
||||
make an example::
|
||||
|
||||
import datetime
|
||||
datetime.date.toda# <-- cursor here
|
||||
|
||||
First of all, this module doesn't care about completion. It really just cares
|
||||
about ``datetime.date``. At the end of the procedure ``eval_expr_stmt`` will
|
||||
return the ``date`` class.
|
||||
|
||||
To *visualize* this (simplified):
|
||||
|
||||
- ``Evaluator.eval_expr_stmt`` doesn't do much, because there's no assignment.
|
||||
- ``Context.eval_node`` cares for resolving the dotted path
|
||||
- ``Evaluator.find_types`` searches for global definitions of datetime, which
|
||||
it finds in the definition of an import, by scanning the syntax tree.
|
||||
- Using the import logic, the datetime module is found.
|
||||
- Now ``find_types`` is called again by ``eval_node`` to find ``date``
|
||||
inside the datetime module.
|
||||
|
||||
Now what would happen if we wanted ``datetime.date.foo.bar``? Two more
|
||||
calls to ``find_types``. However the second call would be ignored, because the
|
||||
first one would return nothing (there's no foo attribute in ``date``).
|
||||
|
||||
What if the import would contain another ``ExprStmt`` like this::
|
||||
|
||||
from foo import bar
|
||||
Date = bar.baz
|
||||
|
||||
Well... You get it. Just another ``eval_expr_stmt`` recursion. It's really
|
||||
easy. Python can obviously get way more complicated then this. To understand
|
||||
tuple assignments, list comprehensions and everything else, a lot more code had
|
||||
to be written.
|
||||
|
||||
Jedi has been tested very well, so you can just start modifying code. It's best
|
||||
to write your own test first for your "new" feature. Don't be scared of
|
||||
breaking stuff. As long as the tests pass, you're most likely to be fine.
|
||||
|
||||
I need to mention now that lazy evaluation is really good because it
|
||||
only *evaluates* what needs to be *evaluated*. All the statements and modules
|
||||
that are not used are just being ignored.
|
||||
"""
|
||||
|
||||
from parso.python import tree
|
||||
import parso
|
||||
from parso import python_bytes_to_unicode
|
||||
|
||||
from jedi import debug
|
||||
from jedi import parser_utils
|
||||
from jedi.evaluate.utils import unite
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import recursion
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.filters import TreeNameDefinition, ParamName
|
||||
from jedi.evaluate.base_context import ContextualizedName, ContextualizedNode, \
|
||||
ContextSet, NO_CONTEXTS, iterate_contexts
|
||||
from jedi.evaluate.context import ClassContext, FunctionContext, \
|
||||
AnonymousInstance, BoundMethod
|
||||
from jedi.evaluate.context.iterable import CompForContext
|
||||
from jedi.evaluate.syntax_tree import eval_trailer, eval_expr_stmt, \
|
||||
eval_node, check_tuple_assignments
|
||||
|
||||
|
||||
class Evaluator(object):
|
||||
def __init__(self, project, environment=None, script_path=None):
|
||||
if environment is None:
|
||||
environment = project.get_environment()
|
||||
self.environment = environment
|
||||
self.script_path = script_path
|
||||
self.compiled_subprocess = environment.get_evaluator_subprocess(self)
|
||||
self.grammar = environment.get_grammar()
|
||||
|
||||
self.latest_grammar = parso.load_grammar(version='3.6')
|
||||
self.memoize_cache = {} # for memoize decorators
|
||||
self.module_cache = imports.ModuleCache() # does the job of `sys.modules`.
|
||||
self.compiled_cache = {} # see `evaluate.compiled.create()`
|
||||
self.inferred_element_counts = {}
|
||||
self.mixed_cache = {} # see `evaluate.compiled.mixed._create()`
|
||||
self.analysis = []
|
||||
self.dynamic_params_depth = 0
|
||||
self.is_analysis = False
|
||||
self.project = project
|
||||
self.access_cache = {}
|
||||
# This setting is only temporary to limit the work we have to do with
|
||||
# tensorflow and others.
|
||||
self.infer_enabled = True
|
||||
|
||||
self.reset_recursion_limitations()
|
||||
self.allow_different_encoding = True
|
||||
|
||||
@property
|
||||
@evaluator_function_cache()
|
||||
def builtins_module(self):
|
||||
return compiled.get_special_object(self, u'BUILTINS')
|
||||
|
||||
def reset_recursion_limitations(self):
|
||||
self.recursion_detector = recursion.RecursionDetector()
|
||||
self.execution_recursion_detector = recursion.ExecutionRecursionDetector(self)
|
||||
|
||||
def get_sys_path(self):
|
||||
"""Convenience function"""
|
||||
return self.project._get_sys_path(self, environment=self.environment)
|
||||
|
||||
def eval_element(self, context, element):
|
||||
if not self.infer_enabled:
|
||||
return NO_CONTEXTS
|
||||
|
||||
if isinstance(context, CompForContext):
|
||||
return eval_node(context, element)
|
||||
|
||||
if_stmt = element
|
||||
while if_stmt is not None:
|
||||
if_stmt = if_stmt.parent
|
||||
if if_stmt.type in ('if_stmt', 'for_stmt'):
|
||||
break
|
||||
if parser_utils.is_scope(if_stmt):
|
||||
if_stmt = None
|
||||
break
|
||||
predefined_if_name_dict = context.predefined_names.get(if_stmt)
|
||||
# TODO there's a lot of issues with this one. We actually should do
|
||||
# this in a different way. Caching should only be active in certain
|
||||
# cases and this all sucks.
|
||||
if predefined_if_name_dict is None and if_stmt \
|
||||
and if_stmt.type == 'if_stmt' and self.is_analysis:
|
||||
if_stmt_test = if_stmt.children[1]
|
||||
name_dicts = [{}]
|
||||
# If we already did a check, we don't want to do it again -> If
|
||||
# context.predefined_names is filled, we stop.
|
||||
# We don't want to check the if stmt itself, it's just about
|
||||
# the content.
|
||||
if element.start_pos > if_stmt_test.end_pos:
|
||||
# Now we need to check if the names in the if_stmt match the
|
||||
# names in the suite.
|
||||
if_names = helpers.get_names_of_node(if_stmt_test)
|
||||
element_names = helpers.get_names_of_node(element)
|
||||
str_element_names = [e.value for e in element_names]
|
||||
if any(i.value in str_element_names for i in if_names):
|
||||
for if_name in if_names:
|
||||
definitions = self.goto_definitions(context, if_name)
|
||||
# Every name that has multiple different definitions
|
||||
# causes the complexity to rise. The complexity should
|
||||
# never fall below 1.
|
||||
if len(definitions) > 1:
|
||||
if len(name_dicts) * len(definitions) > 16:
|
||||
debug.dbg('Too many options for if branch evaluation %s.', if_stmt)
|
||||
# There's only a certain amount of branches
|
||||
# Jedi can evaluate, otherwise it will take to
|
||||
# long.
|
||||
name_dicts = [{}]
|
||||
break
|
||||
|
||||
original_name_dicts = list(name_dicts)
|
||||
name_dicts = []
|
||||
for definition in definitions:
|
||||
new_name_dicts = list(original_name_dicts)
|
||||
for i, name_dict in enumerate(new_name_dicts):
|
||||
new_name_dicts[i] = name_dict.copy()
|
||||
new_name_dicts[i][if_name.value] = ContextSet(definition)
|
||||
|
||||
name_dicts += new_name_dicts
|
||||
else:
|
||||
for name_dict in name_dicts:
|
||||
name_dict[if_name.value] = definitions
|
||||
if len(name_dicts) > 1:
|
||||
result = ContextSet()
|
||||
for name_dict in name_dicts:
|
||||
with helpers.predefine_names(context, if_stmt, name_dict):
|
||||
result |= eval_node(context, element)
|
||||
return result
|
||||
else:
|
||||
return self._eval_element_if_evaluated(context, element)
|
||||
else:
|
||||
if predefined_if_name_dict:
|
||||
return eval_node(context, element)
|
||||
else:
|
||||
return self._eval_element_if_evaluated(context, element)
|
||||
|
||||
def _eval_element_if_evaluated(self, context, element):
|
||||
"""
|
||||
TODO This function is temporary: Merge with eval_element.
|
||||
"""
|
||||
parent = element
|
||||
while parent is not None:
|
||||
parent = parent.parent
|
||||
predefined_if_name_dict = context.predefined_names.get(parent)
|
||||
if predefined_if_name_dict is not None:
|
||||
return eval_node(context, element)
|
||||
return self._eval_element_cached(context, element)
|
||||
|
||||
@evaluator_function_cache(default=NO_CONTEXTS)
|
||||
def _eval_element_cached(self, context, element):
|
||||
return eval_node(context, element)
|
||||
|
||||
def goto_definitions(self, context, name):
|
||||
def_ = name.get_definition(import_name_always=True)
|
||||
if def_ is not None:
|
||||
type_ = def_.type
|
||||
if type_ == 'classdef':
|
||||
return [ClassContext(self, context, name.parent)]
|
||||
elif type_ == 'funcdef':
|
||||
return [FunctionContext.from_context(context, name.parent)]
|
||||
|
||||
if type_ == 'expr_stmt':
|
||||
is_simple_name = name.parent.type not in ('power', 'trailer')
|
||||
if is_simple_name:
|
||||
return eval_expr_stmt(context, def_, name)
|
||||
if type_ == 'for_stmt':
|
||||
container_types = context.eval_node(def_.children[3])
|
||||
cn = ContextualizedNode(context, def_.children[3])
|
||||
for_types = iterate_contexts(container_types, cn)
|
||||
c_node = ContextualizedName(context, name)
|
||||
return check_tuple_assignments(self, c_node, for_types)
|
||||
if type_ in ('import_from', 'import_name'):
|
||||
return imports.infer_import(context, name)
|
||||
|
||||
return helpers.evaluate_call_of_leaf(context, name)
|
||||
|
||||
def goto(self, context, name):
|
||||
definition = name.get_definition(import_name_always=True)
|
||||
if definition is not None:
|
||||
type_ = definition.type
|
||||
if type_ == 'expr_stmt':
|
||||
# Only take the parent, because if it's more complicated than just
|
||||
# a name it's something you can "goto" again.
|
||||
is_simple_name = name.parent.type not in ('power', 'trailer')
|
||||
if is_simple_name:
|
||||
return [TreeNameDefinition(context, name)]
|
||||
elif type_ == 'param':
|
||||
return [ParamName(context, name)]
|
||||
elif type_ in ('funcdef', 'classdef'):
|
||||
return [TreeNameDefinition(context, name)]
|
||||
elif type_ in ('import_from', 'import_name'):
|
||||
module_names = imports.infer_import(context, name, is_goto=True)
|
||||
return module_names
|
||||
|
||||
par = name.parent
|
||||
node_type = par.type
|
||||
if node_type == 'argument' and par.children[1] == '=' and par.children[0] == name:
|
||||
# Named param goto.
|
||||
trailer = par.parent
|
||||
if trailer.type == 'arglist':
|
||||
trailer = trailer.parent
|
||||
if trailer.type != 'classdef':
|
||||
if trailer.type == 'decorator':
|
||||
context_set = context.eval_node(trailer.children[1])
|
||||
else:
|
||||
i = trailer.parent.children.index(trailer)
|
||||
to_evaluate = trailer.parent.children[:i]
|
||||
if to_evaluate[0] == 'await':
|
||||
to_evaluate.pop(0)
|
||||
context_set = context.eval_node(to_evaluate[0])
|
||||
for trailer in to_evaluate[1:]:
|
||||
context_set = eval_trailer(context, context_set, trailer)
|
||||
param_names = []
|
||||
for context in context_set:
|
||||
try:
|
||||
get_param_names = context.get_param_names
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for param_name in get_param_names():
|
||||
if param_name.string_name == name.value:
|
||||
param_names.append(param_name)
|
||||
return param_names
|
||||
elif node_type == 'dotted_name': # Is a decorator.
|
||||
index = par.children.index(name)
|
||||
if index > 0:
|
||||
new_dotted = helpers.deep_ast_copy(par)
|
||||
new_dotted.children[index - 1:] = []
|
||||
values = context.eval_node(new_dotted)
|
||||
return unite(
|
||||
value.py__getattribute__(name, name_context=context, is_goto=True)
|
||||
for value in values
|
||||
)
|
||||
|
||||
if node_type == 'trailer' and par.children[0] == '.':
|
||||
values = helpers.evaluate_call_of_leaf(context, name, cut_own_trailer=True)
|
||||
return unite(
|
||||
value.py__getattribute__(name, name_context=context, is_goto=True)
|
||||
for value in values
|
||||
)
|
||||
else:
|
||||
stmt = tree.search_ancestor(
|
||||
name, 'expr_stmt', 'lambdef'
|
||||
) or name
|
||||
if stmt.type == 'lambdef':
|
||||
stmt = name
|
||||
return context.py__getattribute__(
|
||||
name,
|
||||
position=stmt.start_pos,
|
||||
search_global=True, is_goto=True
|
||||
)
|
||||
|
||||
def create_context(self, base_context, node, node_is_context=False, node_is_object=False):
|
||||
def parent_scope(node):
|
||||
while True:
|
||||
node = node.parent
|
||||
|
||||
if parser_utils.is_scope(node):
|
||||
return node
|
||||
elif node.type in ('argument', 'testlist_comp'):
|
||||
if node.children[1].type == 'comp_for':
|
||||
return node.children[1]
|
||||
elif node.type == 'dictorsetmaker':
|
||||
for n in node.children[1:4]:
|
||||
# In dictionaries it can be pretty much anything.
|
||||
if n.type == 'comp_for':
|
||||
return n
|
||||
|
||||
def from_scope_node(scope_node, child_is_funcdef=None, is_nested=True, node_is_object=False):
|
||||
if scope_node == base_node:
|
||||
return base_context
|
||||
|
||||
is_funcdef = scope_node.type in ('funcdef', 'lambdef')
|
||||
parent_scope = parser_utils.get_parent_scope(scope_node)
|
||||
parent_context = from_scope_node(parent_scope, child_is_funcdef=is_funcdef)
|
||||
|
||||
if is_funcdef:
|
||||
func = FunctionContext.from_context(
|
||||
parent_context,
|
||||
scope_node
|
||||
)
|
||||
if isinstance(parent_context, AnonymousInstance):
|
||||
func = BoundMethod(
|
||||
instance=parent_context,
|
||||
klass=parent_context.class_context,
|
||||
function=func
|
||||
)
|
||||
if is_nested and not node_is_object:
|
||||
return func.get_function_execution()
|
||||
return func
|
||||
elif scope_node.type == 'classdef':
|
||||
class_context = ClassContext(self, parent_context, scope_node)
|
||||
if child_is_funcdef:
|
||||
# anonymous instance
|
||||
return AnonymousInstance(self, parent_context, class_context)
|
||||
else:
|
||||
return class_context
|
||||
elif scope_node.type == 'comp_for':
|
||||
if node.start_pos >= scope_node.children[-1].start_pos:
|
||||
return parent_context
|
||||
return CompForContext.from_comp_for(parent_context, scope_node)
|
||||
raise Exception("There's a scope that was not managed.")
|
||||
|
||||
base_node = base_context.tree_node
|
||||
|
||||
if node_is_context and parser_utils.is_scope(node):
|
||||
scope_node = node
|
||||
else:
|
||||
if node.parent.type in ('funcdef', 'classdef') and node.parent.name == node:
|
||||
# When we're on class/function names/leafs that define the
|
||||
# object itself and not its contents.
|
||||
node = node.parent
|
||||
scope_node = parent_scope(node)
|
||||
return from_scope_node(scope_node, is_nested=True, node_is_object=node_is_object)
|
||||
|
||||
def parse_and_get_code(self, code=None, path=None, encoding='utf-8', **kwargs):
|
||||
if self.allow_different_encoding:
|
||||
if code is None:
|
||||
with open(path, 'rb') as f:
|
||||
code = f.read()
|
||||
code = python_bytes_to_unicode(code, encoding=encoding, errors='replace')
|
||||
|
||||
return self.grammar.parse(code=code, path=path, **kwargs), code
|
||||
|
||||
def parse(self, *args, **kwargs):
|
||||
return self.parse_and_get_code(*args, **kwargs)[0]
|
||||
@@ -1,305 +0,0 @@
|
||||
import re
|
||||
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import zip_longest
|
||||
from jedi import debug
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
|
||||
LazyTreeContext, get_merged_lazy_context
|
||||
from jedi.evaluate.filters import ParamName
|
||||
from jedi.evaluate.base_context import NO_CONTEXTS
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.param import get_executed_params, ExecutedParam
|
||||
|
||||
|
||||
def try_iter_content(types, depth=0):
|
||||
"""Helper method for static analysis."""
|
||||
if depth > 10:
|
||||
# It's possible that a loop has references on itself (especially with
|
||||
# CompiledObject). Therefore don't loop infinitely.
|
||||
return
|
||||
|
||||
for typ in types:
|
||||
try:
|
||||
f = typ.py__iter__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for lazy_context in f():
|
||||
try_iter_content(lazy_context.infer(), depth + 1)
|
||||
|
||||
|
||||
def repack_with_argument_clinic(string, keep_arguments_param=False):
|
||||
"""
|
||||
Transforms a function or method with arguments to the signature that is
|
||||
given as an argument clinic notation.
|
||||
|
||||
Argument clinic is part of CPython and used for all the functions that are
|
||||
implemented in C (Python 3.7):
|
||||
|
||||
str.split.__text_signature__
|
||||
# Results in: '($self, /, sep=None, maxsplit=-1)'
|
||||
"""
|
||||
clinic_args = list(_parse_argument_clinic(string))
|
||||
|
||||
def decorator(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
if keep_arguments_param:
|
||||
arguments = kwargs['arguments']
|
||||
else:
|
||||
arguments = kwargs.pop('arguments')
|
||||
try:
|
||||
args += tuple(_iterate_argument_clinic(arguments, clinic_args))
|
||||
except ValueError:
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
|
||||
def _iterate_argument_clinic(arguments, parameters):
|
||||
"""Uses a list with argument clinic information (see PEP 436)."""
|
||||
iterator = arguments.unpack()
|
||||
for i, (name, optional, allow_kwargs) in enumerate(parameters):
|
||||
key, argument = next(iterator, (None, None))
|
||||
if key is not None:
|
||||
debug.warning('Keyword arguments in argument clinic are currently not supported.')
|
||||
raise ValueError
|
||||
if argument is None and not optional:
|
||||
debug.warning('TypeError: %s expected at least %s arguments, got %s',
|
||||
name, len(parameters), i)
|
||||
raise ValueError
|
||||
|
||||
context_set = NO_CONTEXTS if argument is None else argument.infer()
|
||||
|
||||
if not context_set and not optional:
|
||||
# For the stdlib we always want values. If we don't get them,
|
||||
# that's ok, maybe something is too hard to resolve, however,
|
||||
# we will not proceed with the evaluation of that function.
|
||||
debug.warning('argument_clinic "%s" not resolvable.', name)
|
||||
raise ValueError
|
||||
yield context_set
|
||||
|
||||
|
||||
def _parse_argument_clinic(string):
|
||||
allow_kwargs = False
|
||||
optional = False
|
||||
while string:
|
||||
# Optional arguments have to begin with a bracket. And should always be
|
||||
# at the end of the arguments. This is therefore not a proper argument
|
||||
# clinic implementation. `range()` for exmple allows an optional start
|
||||
# value at the beginning.
|
||||
match = re.match('(?:(?:(\[),? ?|, ?|)(\w+)|, ?/)\]*', string)
|
||||
string = string[len(match.group(0)):]
|
||||
if not match.group(2): # A slash -> allow named arguments
|
||||
allow_kwargs = True
|
||||
continue
|
||||
optional = optional or bool(match.group(1))
|
||||
word = match.group(2)
|
||||
yield (word, optional, allow_kwargs)
|
||||
|
||||
|
||||
class AbstractArguments(object):
|
||||
context = None
|
||||
argument_node = None
|
||||
trailer = None
|
||||
|
||||
def eval_all(self, funcdef=None):
|
||||
"""
|
||||
Evaluates all arguments as a support for static analysis
|
||||
(normally Jedi).
|
||||
"""
|
||||
for key, lazy_context in self.unpack():
|
||||
types = lazy_context.infer()
|
||||
try_iter_content(types)
|
||||
|
||||
def get_calling_nodes(self):
|
||||
return []
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_executed_params(self, execution_context):
|
||||
return get_executed_params(execution_context, self)
|
||||
|
||||
|
||||
class AnonymousArguments(AbstractArguments):
|
||||
def get_executed_params(self, execution_context):
|
||||
from jedi.evaluate.dynamic import search_params
|
||||
return search_params(
|
||||
execution_context.evaluator,
|
||||
execution_context,
|
||||
execution_context.tree_node
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s()' % self.__class__.__name__
|
||||
|
||||
|
||||
class TreeArguments(AbstractArguments):
|
||||
def __init__(self, evaluator, context, argument_node, trailer=None):
|
||||
"""
|
||||
The argument_node is either a parser node or a list of evaluated
|
||||
objects. Those evaluated objects may be lists of evaluated objects
|
||||
themselves (one list for the first argument, one for the second, etc).
|
||||
|
||||
:param argument_node: May be an argument_node or a list of nodes.
|
||||
"""
|
||||
self.argument_node = argument_node
|
||||
self.context = context
|
||||
self._evaluator = evaluator
|
||||
self.trailer = trailer # Can be None, e.g. in a class definition.
|
||||
|
||||
def _split(self):
|
||||
if self.argument_node is None:
|
||||
return
|
||||
|
||||
# Allow testlist here as well for Python2's class inheritance
|
||||
# definitions.
|
||||
if not (self.argument_node.type in ('arglist', 'testlist') or (
|
||||
# in python 3.5 **arg is an argument, not arglist
|
||||
(self.argument_node.type == 'argument') and
|
||||
self.argument_node.children[0] in ('*', '**'))):
|
||||
yield 0, self.argument_node
|
||||
return
|
||||
|
||||
iterator = iter(self.argument_node.children)
|
||||
for child in iterator:
|
||||
if child == ',':
|
||||
continue
|
||||
elif child in ('*', '**'):
|
||||
yield len(child.value), next(iterator)
|
||||
elif child.type == 'argument' and \
|
||||
child.children[0] in ('*', '**'):
|
||||
assert len(child.children) == 2
|
||||
yield len(child.children[0].value), child.children[1]
|
||||
else:
|
||||
yield 0, child
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
named_args = []
|
||||
for star_count, el in self._split():
|
||||
if star_count == 1:
|
||||
arrays = self.context.eval_node(el)
|
||||
iterators = [_iterate_star_args(self.context, a, el, funcdef)
|
||||
for a in arrays]
|
||||
for values in list(zip_longest(*iterators)):
|
||||
# TODO zip_longest yields None, that means this would raise
|
||||
# an exception?
|
||||
yield None, get_merged_lazy_context(
|
||||
[v for v in values if v is not None]
|
||||
)
|
||||
elif star_count == 2:
|
||||
arrays = self.context.eval_node(el)
|
||||
for dct in arrays:
|
||||
for key, values in _star_star_dict(self.context, dct, el, funcdef):
|
||||
yield key, values
|
||||
else:
|
||||
if el.type == 'argument':
|
||||
c = el.children
|
||||
if len(c) == 3: # Keyword argument.
|
||||
named_args.append((c[0].value, LazyTreeContext(self.context, c[2]),))
|
||||
else: # Generator comprehension.
|
||||
# Include the brackets with the parent.
|
||||
comp = iterable.GeneratorComprehension(
|
||||
self._evaluator, self.context, self.argument_node.parent)
|
||||
yield None, LazyKnownContext(comp)
|
||||
else:
|
||||
yield None, LazyTreeContext(self.context, el)
|
||||
|
||||
# Reordering var_args is necessary, because star args sometimes appear
|
||||
# after named argument, but in the actual order it's prepended.
|
||||
for named_arg in named_args:
|
||||
yield named_arg
|
||||
|
||||
def as_tree_tuple_objects(self):
|
||||
for star_count, argument in self._split():
|
||||
if argument.type == 'argument':
|
||||
argument, default = argument.children[::2]
|
||||
else:
|
||||
default = None
|
||||
yield argument, default, star_count
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.argument_node)
|
||||
|
||||
def get_calling_nodes(self):
|
||||
from jedi.evaluate.dynamic import DynamicExecutedParams
|
||||
old_arguments_list = []
|
||||
arguments = self
|
||||
|
||||
while arguments not in old_arguments_list:
|
||||
if not isinstance(arguments, TreeArguments):
|
||||
break
|
||||
|
||||
old_arguments_list.append(arguments)
|
||||
for name, default, star_count in reversed(list(arguments.as_tree_tuple_objects())):
|
||||
if not star_count or not isinstance(name, tree.Name):
|
||||
continue
|
||||
|
||||
names = self._evaluator.goto(arguments.context, name)
|
||||
if len(names) != 1:
|
||||
break
|
||||
if not isinstance(names[0], ParamName):
|
||||
break
|
||||
param = names[0].get_param()
|
||||
if isinstance(param, DynamicExecutedParams):
|
||||
# For dynamic searches we don't even want to see errors.
|
||||
return []
|
||||
if not isinstance(param, ExecutedParam):
|
||||
break
|
||||
if param.var_args is None:
|
||||
break
|
||||
arguments = param.var_args
|
||||
break
|
||||
|
||||
if arguments.argument_node is not None:
|
||||
return [arguments.argument_node]
|
||||
if arguments.trailer is not None:
|
||||
return [arguments.trailer]
|
||||
return []
|
||||
|
||||
|
||||
class ValuesArguments(AbstractArguments):
|
||||
def __init__(self, values_list):
|
||||
self._values_list = values_list
|
||||
|
||||
def unpack(self, funcdef=None):
|
||||
for values in self._values_list:
|
||||
yield None, LazyKnownContexts(values)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._values_list)
|
||||
|
||||
|
||||
def _iterate_star_args(context, array, input_node, funcdef=None):
|
||||
try:
|
||||
iter_ = array.py__iter__
|
||||
except AttributeError:
|
||||
if funcdef is not None:
|
||||
# TODO this funcdef should not be needed.
|
||||
m = "TypeError: %s() argument after * must be a sequence, not %s" \
|
||||
% (funcdef.name.value, array)
|
||||
analysis.add(context, 'type-error-star', input_node, message=m)
|
||||
else:
|
||||
for lazy_context in iter_():
|
||||
yield lazy_context
|
||||
|
||||
|
||||
def _star_star_dict(context, array, input_node, funcdef):
|
||||
from jedi.evaluate.context.instance import CompiledInstance
|
||||
if isinstance(array, CompiledInstance) and array.name.string_name == 'dict':
|
||||
# For now ignore this case. In the future add proper iterators and just
|
||||
# make one call without crazy isinstance checks.
|
||||
return {}
|
||||
elif isinstance(array, iterable.Sequence) and array.array_type == 'dict':
|
||||
return array.exact_key_items()
|
||||
else:
|
||||
if funcdef is not None:
|
||||
m = "TypeError: %s argument after ** must be a mapping, not %s" \
|
||||
% (funcdef.name.value, array)
|
||||
analysis.add(context, 'type-error-star-star', input_node, message=m)
|
||||
return {}
|
||||
@@ -1,279 +0,0 @@
|
||||
"""
|
||||
Contexts are the "values" that Python would return. However Contexts are at the
|
||||
same time also the "contexts" that a user is currently sitting in.
|
||||
|
||||
A ContextSet is typically used to specify the return of a function or any other
|
||||
static analysis operation. In jedi there are always multiple returns and not
|
||||
just one.
|
||||
"""
|
||||
from parso.python.tree import ExprStmt, CompFor
|
||||
|
||||
from jedi import debug
|
||||
from jedi._compatibility import Python3Method, zip_longest, unicode
|
||||
from jedi.parser_utils import clean_scope_docstring, get_doc_with_call_signature
|
||||
from jedi.common import BaseContextSet, BaseContext
|
||||
from jedi.evaluate.helpers import EvaluatorIndexError, EvaluatorTypeError, \
|
||||
EvaluatorKeyError
|
||||
|
||||
|
||||
class Context(BaseContext):
|
||||
"""
|
||||
Should be defined, otherwise the API returns empty types.
|
||||
"""
|
||||
|
||||
predefined_names = {}
|
||||
tree_node = None
|
||||
"""
|
||||
To be defined by subclasses.
|
||||
"""
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
# By default just lower name of the class. Can and should be
|
||||
# overwritten.
|
||||
return self.__class__.__name__.lower()
|
||||
|
||||
@debug.increase_indent
|
||||
def execute(self, arguments):
|
||||
"""
|
||||
In contrast to py__call__ this function is always available.
|
||||
|
||||
`hasattr(x, py__call__)` can also be checked to see if a context is
|
||||
executable.
|
||||
"""
|
||||
if self.evaluator.is_analysis:
|
||||
arguments.eval_all()
|
||||
|
||||
debug.dbg('execute: %s %s', self, arguments)
|
||||
from jedi.evaluate import stdlib
|
||||
try:
|
||||
# Some stdlib functions like super(), namedtuple(), etc. have been
|
||||
# hard-coded in Jedi to support them.
|
||||
return stdlib.execute(self.evaluator, self, arguments)
|
||||
except stdlib.NotInStdLib:
|
||||
pass
|
||||
|
||||
try:
|
||||
func = self.py__call__
|
||||
except AttributeError:
|
||||
debug.warning("no execution possible %s", self)
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
context_set = func(arguments)
|
||||
debug.dbg('execute result: %s in %s', context_set, self)
|
||||
return context_set
|
||||
|
||||
return self.evaluator.execute(self, arguments)
|
||||
|
||||
def execute_evaluated(self, *value_list):
|
||||
"""
|
||||
Execute a function with already executed arguments.
|
||||
"""
|
||||
from jedi.evaluate.arguments import ValuesArguments
|
||||
arguments = ValuesArguments([ContextSet(value) for value in value_list])
|
||||
return self.execute(arguments)
|
||||
|
||||
def iterate(self, contextualized_node=None, is_async=False):
|
||||
debug.dbg('iterate %s', self)
|
||||
try:
|
||||
if is_async:
|
||||
iter_method = self.py__aiter__
|
||||
else:
|
||||
iter_method = self.py__iter__
|
||||
except AttributeError:
|
||||
if contextualized_node is not None:
|
||||
from jedi.evaluate import analysis
|
||||
analysis.add(
|
||||
contextualized_node.context,
|
||||
'type-error-not-iterable',
|
||||
contextualized_node.node,
|
||||
message="TypeError: '%s' object is not iterable" % self)
|
||||
return iter([])
|
||||
else:
|
||||
return iter_method()
|
||||
|
||||
def get_item(self, index_contexts, contextualized_node):
|
||||
from jedi.evaluate.compiled import CompiledObject
|
||||
from jedi.evaluate.context.iterable import Slice, Sequence
|
||||
result = ContextSet()
|
||||
|
||||
for index in index_contexts:
|
||||
if isinstance(index, Slice):
|
||||
index = index.obj
|
||||
if isinstance(index, CompiledObject):
|
||||
try:
|
||||
index = index.get_safe_value()
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
if type(index) not in (float, int, str, unicode, slice, bytes):
|
||||
# If the index is not clearly defined, we have to get all the
|
||||
# possiblities.
|
||||
if isinstance(self, Sequence) and self.array_type == 'dict':
|
||||
result |= self.dict_values()
|
||||
else:
|
||||
result |= iterate_contexts(ContextSet(self))
|
||||
continue
|
||||
|
||||
# The actual getitem call.
|
||||
try:
|
||||
getitem = self.py__getitem__
|
||||
except AttributeError:
|
||||
from jedi.evaluate import analysis
|
||||
# TODO this context is probably not right.
|
||||
analysis.add(
|
||||
contextualized_node.context,
|
||||
'type-error-not-subscriptable',
|
||||
contextualized_node.node,
|
||||
message="TypeError: '%s' object is not subscriptable" % self
|
||||
)
|
||||
else:
|
||||
try:
|
||||
result |= getitem(index)
|
||||
except EvaluatorIndexError:
|
||||
result |= iterate_contexts(ContextSet(self))
|
||||
except EvaluatorKeyError:
|
||||
# Must be a dict. Lists don't raise KeyErrors.
|
||||
result |= self.dict_values()
|
||||
except EvaluatorTypeError:
|
||||
# The type is wrong and therefore it makes no sense to do
|
||||
# anything anymore.
|
||||
result = NO_CONTEXTS
|
||||
return result
|
||||
|
||||
def eval_node(self, node):
|
||||
return self.evaluator.eval_element(self, node)
|
||||
|
||||
@Python3Method
|
||||
def py__getattribute__(self, name_or_str, name_context=None, position=None,
|
||||
search_global=False, is_goto=False,
|
||||
analysis_errors=True):
|
||||
"""
|
||||
:param position: Position of the last statement -> tuple of line, column
|
||||
"""
|
||||
if name_context is None:
|
||||
name_context = self
|
||||
from jedi.evaluate import finder
|
||||
f = finder.NameFinder(self.evaluator, self, name_context, name_or_str,
|
||||
position, analysis_errors=analysis_errors)
|
||||
filters = f.get_filters(search_global)
|
||||
if is_goto:
|
||||
return f.filter_name(filters)
|
||||
return f.find(filters, attribute_lookup=not search_global)
|
||||
|
||||
def create_context(self, node, node_is_context=False, node_is_object=False):
|
||||
return self.evaluator.create_context(self, node, node_is_context, node_is_object)
|
||||
|
||||
def is_class(self):
|
||||
return False
|
||||
|
||||
def py__bool__(self):
|
||||
"""
|
||||
Since Wrapper is a super class for classes, functions and modules,
|
||||
the return value will always be true.
|
||||
"""
|
||||
return True
|
||||
|
||||
def py__doc__(self, include_call_signature=False):
|
||||
try:
|
||||
self.tree_node.get_doc_node
|
||||
except AttributeError:
|
||||
return ''
|
||||
else:
|
||||
if include_call_signature:
|
||||
return get_doc_with_call_signature(self.tree_node)
|
||||
else:
|
||||
return clean_scope_docstring(self.tree_node)
|
||||
return None
|
||||
|
||||
|
||||
def iterate_contexts(contexts, contextualized_node=None, is_async=False):
|
||||
"""
|
||||
Calls `iterate`, on all contexts but ignores the ordering and just returns
|
||||
all contexts that the iterate functions yield.
|
||||
"""
|
||||
return ContextSet.from_sets(
|
||||
lazy_context.infer()
|
||||
for lazy_context in contexts.iterate(contextualized_node, is_async=is_async)
|
||||
)
|
||||
|
||||
|
||||
class TreeContext(Context):
|
||||
def __init__(self, evaluator, parent_context, tree_node):
|
||||
super(TreeContext, self).__init__(evaluator, parent_context)
|
||||
self.predefined_names = {}
|
||||
self.tree_node = tree_node
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.tree_node)
|
||||
|
||||
|
||||
class ContextualizedNode(object):
|
||||
def __init__(self, context, node):
|
||||
self.context = context
|
||||
self.node = node
|
||||
|
||||
def get_root_context(self):
|
||||
return self.context.get_root_context()
|
||||
|
||||
def infer(self):
|
||||
return self.context.eval_node(self.node)
|
||||
|
||||
|
||||
class ContextualizedName(ContextualizedNode):
|
||||
# TODO merge with TreeNameDefinition?!
|
||||
@property
|
||||
def name(self):
|
||||
return self.node
|
||||
|
||||
def assignment_indexes(self):
|
||||
"""
|
||||
Returns an array of tuple(int, node) of the indexes that are used in
|
||||
tuple assignments.
|
||||
|
||||
For example if the name is ``y`` in the following code::
|
||||
|
||||
x, (y, z) = 2, ''
|
||||
|
||||
would result in ``[(1, xyz_node), (0, yz_node)]``.
|
||||
"""
|
||||
indexes = []
|
||||
node = self.node.parent
|
||||
compare = self.node
|
||||
while node is not None:
|
||||
if node.type in ('testlist', 'testlist_comp', 'testlist_star_expr', 'exprlist'):
|
||||
for i, child in enumerate(node.children):
|
||||
if child == compare:
|
||||
indexes.insert(0, (int(i / 2), node))
|
||||
break
|
||||
else:
|
||||
raise LookupError("Couldn't find the assignment.")
|
||||
elif isinstance(node, (ExprStmt, CompFor)):
|
||||
break
|
||||
|
||||
compare = node
|
||||
node = node.parent
|
||||
return indexes
|
||||
|
||||
|
||||
class ContextSet(BaseContextSet):
|
||||
def py__class__(self):
|
||||
return ContextSet.from_iterable(c.py__class__() for c in self._set)
|
||||
|
||||
def iterate(self, contextualized_node=None, is_async=False):
|
||||
from jedi.evaluate.lazy_context import get_merged_lazy_context
|
||||
type_iters = [c.iterate(contextualized_node, is_async=is_async) for c in self._set]
|
||||
for lazy_contexts in zip_longest(*type_iters):
|
||||
yield get_merged_lazy_context(
|
||||
[l for l in lazy_contexts if l is not None]
|
||||
)
|
||||
|
||||
|
||||
NO_CONTEXTS = ContextSet()
|
||||
|
||||
|
||||
def iterator_to_context_set(func):
|
||||
def wrapper(*args, **kwargs):
|
||||
return ContextSet.from_iterable(func(*args, **kwargs))
|
||||
|
||||
return wrapper
|
||||
@@ -1,77 +0,0 @@
|
||||
"""
|
||||
- the popular ``_memoize_default`` works like a typical memoize and returns the
|
||||
default otherwise.
|
||||
- ``CachedMetaClass`` uses ``_memoize_default`` to do the same with classes.
|
||||
"""
|
||||
|
||||
_NO_DEFAULT = object()
|
||||
|
||||
|
||||
def _memoize_default(default=_NO_DEFAULT, evaluator_is_first_arg=False, second_arg_is_evaluator=False):
|
||||
""" This is a typical memoization decorator, BUT there is one difference:
|
||||
To prevent recursion it sets defaults.
|
||||
|
||||
Preventing recursion is in this case the much bigger use than speed. I
|
||||
don't think, that there is a big speed difference, but there are many cases
|
||||
where recursion could happen (think about a = b; b = a).
|
||||
"""
|
||||
def func(function):
|
||||
def wrapper(obj, *args, **kwargs):
|
||||
# TODO These checks are kind of ugly and slow.
|
||||
if evaluator_is_first_arg:
|
||||
cache = obj.memoize_cache
|
||||
elif second_arg_is_evaluator:
|
||||
cache = args[0].memoize_cache # needed for meta classes
|
||||
else:
|
||||
cache = obj.evaluator.memoize_cache
|
||||
|
||||
try:
|
||||
memo = cache[function]
|
||||
except KeyError:
|
||||
memo = {}
|
||||
cache[function] = memo
|
||||
|
||||
key = (obj, args, frozenset(kwargs.items()))
|
||||
if key in memo:
|
||||
return memo[key]
|
||||
else:
|
||||
if default is not _NO_DEFAULT:
|
||||
memo[key] = default
|
||||
rv = function(obj, *args, **kwargs)
|
||||
memo[key] = rv
|
||||
return rv
|
||||
return wrapper
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def evaluator_function_cache(default=_NO_DEFAULT):
|
||||
def decorator(func):
|
||||
return _memoize_default(default=default, evaluator_is_first_arg=True)(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def evaluator_method_cache(default=_NO_DEFAULT):
|
||||
def decorator(func):
|
||||
return _memoize_default(default=default)(func)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def evaluator_as_method_param_cache():
|
||||
def decorator(call):
|
||||
return _memoize_default(second_arg_is_evaluator=True)(call)
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class CachedMetaClass(type):
|
||||
"""
|
||||
This is basically almost the same than the decorator above, it just caches
|
||||
class initializations. Either you do it this way or with decorators, but
|
||||
with decorators you lose class access (isinstance, etc).
|
||||
"""
|
||||
@evaluator_as_method_param_cache()
|
||||
def __call__(self, *args, **kwargs):
|
||||
return super(CachedMetaClass, self).__call__(*args, **kwargs)
|
||||
@@ -1,43 +0,0 @@
|
||||
from jedi._compatibility import unicode
|
||||
from jedi.evaluate.compiled.context import CompiledObject, CompiledName, \
|
||||
CompiledObjectFilter, CompiledContextName, create_from_access_path, \
|
||||
create_from_name
|
||||
|
||||
|
||||
def builtin_from_name(evaluator, string):
|
||||
builtins = evaluator.builtins_module
|
||||
return create_from_name(evaluator, builtins, string)
|
||||
|
||||
|
||||
def create_simple_object(evaluator, obj):
|
||||
"""
|
||||
Only allows creations of objects that are easily picklable across Python
|
||||
versions.
|
||||
"""
|
||||
assert isinstance(obj, (int, float, str, bytes, unicode, slice, complex))
|
||||
return create_from_access_path(
|
||||
evaluator,
|
||||
evaluator.compiled_subprocess.create_simple_object(obj)
|
||||
)
|
||||
|
||||
|
||||
def get_special_object(evaluator, identifier):
|
||||
return create_from_access_path(
|
||||
evaluator,
|
||||
evaluator.compiled_subprocess.get_special_object(identifier)
|
||||
)
|
||||
|
||||
|
||||
def get_string_context_set(evaluator):
|
||||
return builtin_from_name(evaluator, u'str').execute_evaluated()
|
||||
|
||||
|
||||
def load_module(evaluator, dotted_name, **kwargs):
|
||||
# Temporary, some tensorflow builtins cannot be loaded, so it's tried again
|
||||
# and again and it's really slow.
|
||||
if dotted_name.startswith('tensorflow.'):
|
||||
return None
|
||||
access_path = evaluator.compiled_subprocess.load_module(dotted_name=dotted_name, **kwargs)
|
||||
if access_path is None:
|
||||
return None
|
||||
return create_from_access_path(evaluator, access_path)
|
||||
@@ -1,483 +0,0 @@
|
||||
import inspect
|
||||
import types
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
import operator as op
|
||||
from collections import namedtuple
|
||||
|
||||
from jedi._compatibility import unicode, is_py3, builtins, \
|
||||
py_version, force_unicode, print_to_stderr
|
||||
from jedi.evaluate.compiled.getattr_static import getattr_static
|
||||
|
||||
|
||||
MethodDescriptorType = type(str.replace)
|
||||
# These are not considered classes and access is granted even though they have
|
||||
# a __class__ attribute.
|
||||
NOT_CLASS_TYPES = (
|
||||
types.BuiltinFunctionType,
|
||||
types.CodeType,
|
||||
types.FrameType,
|
||||
types.FunctionType,
|
||||
types.GeneratorType,
|
||||
types.GetSetDescriptorType,
|
||||
types.LambdaType,
|
||||
types.MemberDescriptorType,
|
||||
types.MethodType,
|
||||
types.ModuleType,
|
||||
types.TracebackType,
|
||||
MethodDescriptorType
|
||||
)
|
||||
|
||||
if is_py3:
|
||||
NOT_CLASS_TYPES += (
|
||||
types.MappingProxyType,
|
||||
types.SimpleNamespace,
|
||||
types.DynamicClassAttribute,
|
||||
)
|
||||
|
||||
|
||||
# Those types don't exist in typing.
|
||||
MethodDescriptorType = type(str.replace)
|
||||
WrapperDescriptorType = type(set.__iter__)
|
||||
# `object.__subclasshook__` is an already executed descriptor.
|
||||
object_class_dict = type.__dict__["__dict__"].__get__(object)
|
||||
ClassMethodDescriptorType = type(object_class_dict['__subclasshook__'])
|
||||
|
||||
def _a_generator(foo):
|
||||
"""Used to have an object to return for generators."""
|
||||
yield 42
|
||||
yield foo
|
||||
|
||||
|
||||
_sentinel = object()
|
||||
|
||||
# Maps Python syntax to the operator module.
|
||||
COMPARISON_OPERATORS = {
|
||||
'==': op.eq,
|
||||
'!=': op.ne,
|
||||
'is': op.is_,
|
||||
'is not': op.is_not,
|
||||
'<': op.lt,
|
||||
'<=': op.le,
|
||||
'>': op.gt,
|
||||
'>=': op.ge,
|
||||
}
|
||||
|
||||
_OPERATORS = {
|
||||
'+': op.add,
|
||||
'-': op.sub,
|
||||
}
|
||||
_OPERATORS.update(COMPARISON_OPERATORS)
|
||||
|
||||
ALLOWED_DESCRIPTOR_ACCESS = (
|
||||
types.FunctionType,
|
||||
types.GetSetDescriptorType,
|
||||
types.MemberDescriptorType,
|
||||
MethodDescriptorType,
|
||||
WrapperDescriptorType,
|
||||
ClassMethodDescriptorType,
|
||||
staticmethod,
|
||||
classmethod,
|
||||
)
|
||||
|
||||
|
||||
def safe_getattr(obj, name, default=_sentinel):
|
||||
try:
|
||||
attr, is_get_descriptor = getattr_static(obj, name)
|
||||
except AttributeError:
|
||||
if default is _sentinel:
|
||||
raise
|
||||
return default
|
||||
else:
|
||||
if type(attr) in ALLOWED_DESCRIPTOR_ACCESS:
|
||||
# In case of descriptors that have get methods we cannot return
|
||||
# it's value, because that would mean code execution.
|
||||
return getattr(obj, name)
|
||||
return attr
|
||||
|
||||
|
||||
SignatureParam = namedtuple(
|
||||
'SignatureParam',
|
||||
'name has_default default has_annotation annotation kind_name'
|
||||
)
|
||||
|
||||
|
||||
def compiled_objects_cache(attribute_name):
|
||||
def decorator(func):
|
||||
"""
|
||||
This decorator caches just the ids, oopposed to caching the object itself.
|
||||
Caching the id has the advantage that an object doesn't need to be
|
||||
hashable.
|
||||
"""
|
||||
def wrapper(evaluator, obj, parent_context=None):
|
||||
cache = getattr(evaluator, attribute_name)
|
||||
# Do a very cheap form of caching here.
|
||||
key = id(obj)
|
||||
try:
|
||||
cache[key]
|
||||
return cache[key][0]
|
||||
except KeyError:
|
||||
# TODO wuaaaarrghhhhhhhh
|
||||
if attribute_name == 'mixed_cache':
|
||||
result = func(evaluator, obj, parent_context)
|
||||
else:
|
||||
result = func(evaluator, obj)
|
||||
# Need to cache all of them, otherwise the id could be overwritten.
|
||||
cache[key] = result, obj, parent_context
|
||||
return result
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def create_access(evaluator, obj):
|
||||
return evaluator.compiled_subprocess.get_or_create_access_handle(obj)
|
||||
|
||||
|
||||
def load_module(evaluator, dotted_name, sys_path):
|
||||
temp, sys.path = sys.path, sys_path
|
||||
try:
|
||||
__import__(dotted_name)
|
||||
except ImportError:
|
||||
# If a module is "corrupt" or not really a Python module or whatever.
|
||||
print_to_stderr('Module %s not importable in path %s.' % (dotted_name, sys_path))
|
||||
return None
|
||||
except Exception:
|
||||
# Since __import__ pretty much makes code execution possible, just
|
||||
# catch any error here and print it.
|
||||
import traceback
|
||||
print_to_stderr("Cannot import:\n%s" % traceback.format_exc())
|
||||
return None
|
||||
finally:
|
||||
sys.path = temp
|
||||
|
||||
# Just access the cache after import, because of #59 as well as the very
|
||||
# complicated import structure of Python.
|
||||
module = sys.modules[dotted_name]
|
||||
return create_access_path(evaluator, module)
|
||||
|
||||
|
||||
class AccessPath(object):
|
||||
def __init__(self, accesses):
|
||||
self.accesses = accesses
|
||||
|
||||
# Writing both of these methods here looks a bit ridiculous. However with
|
||||
# the differences of Python 2/3 it's actually necessary, because we will
|
||||
# otherwise have a accesses attribute that is bytes instead of unicode.
|
||||
def __getstate__(self):
|
||||
return self.accesses
|
||||
|
||||
def __setstate__(self, value):
|
||||
self.accesses = value
|
||||
|
||||
|
||||
def create_access_path(evaluator, obj):
|
||||
access = create_access(evaluator, obj)
|
||||
return AccessPath(access.get_access_path_tuples())
|
||||
|
||||
|
||||
def _force_unicode_decorator(func):
|
||||
return lambda *args, **kwargs: force_unicode(func(*args, **kwargs))
|
||||
|
||||
|
||||
class DirectObjectAccess(object):
|
||||
def __init__(self, evaluator, obj):
|
||||
self._evaluator = evaluator
|
||||
self._obj = obj
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, self.get_repr())
|
||||
|
||||
def _create_access(self, obj):
|
||||
return create_access(self._evaluator, obj)
|
||||
|
||||
def _create_access_path(self, obj):
|
||||
return create_access_path(self._evaluator, obj)
|
||||
|
||||
def py__bool__(self):
|
||||
return bool(self._obj)
|
||||
|
||||
def py__file__(self):
|
||||
try:
|
||||
return self._obj.__file__
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def py__doc__(self, include_call_signature=False):
|
||||
return force_unicode(inspect.getdoc(self._obj)) or u''
|
||||
|
||||
def py__name__(self):
|
||||
if not _is_class_instance(self._obj) or \
|
||||
inspect.ismethoddescriptor(self._obj): # slots
|
||||
cls = self._obj
|
||||
else:
|
||||
try:
|
||||
cls = self._obj.__class__
|
||||
except AttributeError:
|
||||
# happens with numpy.core.umath._UFUNC_API (you get it
|
||||
# automatically by doing `import numpy`.
|
||||
return None
|
||||
|
||||
try:
|
||||
return force_unicode(cls.__name__)
|
||||
except AttributeError:
|
||||
return None
|
||||
|
||||
def py__mro__accesses(self):
|
||||
return tuple(self._create_access_path(cls) for cls in self._obj.__mro__[1:])
|
||||
|
||||
def py__getitem__(self, index):
|
||||
if type(self._obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
|
||||
# Get rid of side effects, we won't call custom `__getitem__`s.
|
||||
return None
|
||||
|
||||
return self._create_access_path(self._obj[index])
|
||||
|
||||
def py__iter__list(self):
|
||||
if type(self._obj) not in (str, list, tuple, unicode, bytes, bytearray, dict):
|
||||
# Get rid of side effects, we won't call custom `__getitem__`s.
|
||||
return []
|
||||
|
||||
lst = []
|
||||
for i, part in enumerate(self._obj):
|
||||
if i > 20:
|
||||
# Should not go crazy with large iterators
|
||||
break
|
||||
lst.append(self._create_access_path(part))
|
||||
return lst
|
||||
|
||||
def py__class__(self):
|
||||
return self._create_access_path(self._obj.__class__)
|
||||
|
||||
def py__bases__(self):
|
||||
return [self._create_access_path(base) for base in self._obj.__bases__]
|
||||
|
||||
def py__path__(self):
|
||||
return self._obj.__path__
|
||||
|
||||
@_force_unicode_decorator
|
||||
def get_repr(self):
|
||||
builtins = 'builtins', '__builtin__'
|
||||
|
||||
if inspect.ismodule(self._obj):
|
||||
return repr(self._obj)
|
||||
# Try to avoid execution of the property.
|
||||
if safe_getattr(self._obj, '__module__', default='') in builtins:
|
||||
return repr(self._obj)
|
||||
|
||||
type_ = type(self._obj)
|
||||
if type_ == type:
|
||||
return type.__repr__(self._obj)
|
||||
|
||||
if safe_getattr(type_, '__module__', default='') in builtins:
|
||||
# Allow direct execution of repr for builtins.
|
||||
return repr(self._obj)
|
||||
return object.__repr__(self._obj)
|
||||
|
||||
def is_class(self):
|
||||
return inspect.isclass(self._obj)
|
||||
|
||||
def ismethoddescriptor(self):
|
||||
return inspect.ismethoddescriptor(self._obj)
|
||||
|
||||
def dir(self):
|
||||
return list(map(force_unicode, dir(self._obj)))
|
||||
|
||||
def has_iter(self):
|
||||
try:
|
||||
iter(self._obj)
|
||||
return True
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
def is_allowed_getattr(self, name):
|
||||
# TODO this API is ugly.
|
||||
try:
|
||||
attr, is_get_descriptor = getattr_static(self._obj, name)
|
||||
except AttributeError:
|
||||
return False, False
|
||||
else:
|
||||
if is_get_descriptor and type(attr) not in ALLOWED_DESCRIPTOR_ACCESS:
|
||||
# In case of descriptors that have get methods we cannot return
|
||||
# it's value, because that would mean code execution.
|
||||
return True, True
|
||||
return True, False
|
||||
|
||||
def getattr(self, name, default=_sentinel):
|
||||
try:
|
||||
return self._create_access(getattr(self._obj, name))
|
||||
except AttributeError:
|
||||
# Happens e.g. in properties of
|
||||
# PyQt4.QtGui.QStyleOptionComboBox.currentText
|
||||
# -> just set it to None
|
||||
if default is _sentinel:
|
||||
raise
|
||||
return self._create_access(default)
|
||||
|
||||
def get_safe_value(self):
|
||||
if type(self._obj) in (bool, bytes, float, int, str, unicode, slice):
|
||||
return self._obj
|
||||
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'
|
||||
|
||||
def get_access_path_tuples(self):
|
||||
accesses = [create_access(self._evaluator, o) for o in self._get_objects_path()]
|
||||
return [(access.py__name__(), access) for access in accesses]
|
||||
|
||||
def _get_objects_path(self):
|
||||
def get():
|
||||
obj = self._obj
|
||||
yield obj
|
||||
try:
|
||||
obj = obj.__objclass__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
yield obj
|
||||
|
||||
try:
|
||||
# Returns a dotted string path.
|
||||
imp_plz = obj.__module__
|
||||
except AttributeError:
|
||||
# Unfortunately in some cases like `int` there's no __module__
|
||||
if not inspect.ismodule(obj):
|
||||
yield builtins
|
||||
else:
|
||||
if imp_plz is None:
|
||||
# Happens for example in `(_ for _ in []).send.__module__`.
|
||||
yield builtins
|
||||
else:
|
||||
try:
|
||||
# TODO use sys.modules, __module__ can be faked.
|
||||
yield sys.modules[imp_plz]
|
||||
except KeyError:
|
||||
# __module__ can be something arbitrary that doesn't exist.
|
||||
yield builtins
|
||||
|
||||
return list(reversed(list(get())))
|
||||
|
||||
def execute_operation(self, other_access_handle, operator):
|
||||
other_access = other_access_handle.access
|
||||
op = _OPERATORS[operator]
|
||||
return self._create_access_path(op(self._obj, other_access._obj))
|
||||
|
||||
def needs_type_completions(self):
|
||||
return inspect.isclass(self._obj) and self._obj != type
|
||||
|
||||
def get_signature_params(self):
|
||||
obj = self._obj
|
||||
if py_version < 33:
|
||||
raise ValueError("inspect.signature was introduced in 3.3")
|
||||
if py_version == 34:
|
||||
# In 3.4 inspect.signature are wrong for str and int. This has
|
||||
# been fixed in 3.5. The signature of object is returned,
|
||||
# because no signature was found for str. Here we imitate 3.5
|
||||
# logic and just ignore the signature if the magic methods
|
||||
# don't match object.
|
||||
# 3.3 doesn't even have the logic and returns nothing for str
|
||||
# and classes that inherit from object.
|
||||
user_def = inspect._signature_get_user_defined_method
|
||||
if (inspect.isclass(obj)
|
||||
and not user_def(type(obj), '__init__')
|
||||
and not user_def(type(obj), '__new__')
|
||||
and (obj.__init__ != object.__init__
|
||||
or obj.__new__ != object.__new__)):
|
||||
raise ValueError
|
||||
|
||||
try:
|
||||
signature = inspect.signature(obj)
|
||||
except (RuntimeError, TypeError):
|
||||
# Reading the code of the function in Python 3.6 implies there are
|
||||
# at least these errors that might occur if something is wrong with
|
||||
# the signature. In that case we just want a simple escape for now.
|
||||
raise ValueError
|
||||
return [
|
||||
SignatureParam(
|
||||
name=p.name,
|
||||
has_default=p.default is not p.empty,
|
||||
default=self._create_access_path(p.default),
|
||||
has_annotation=p.annotation is not p.empty,
|
||||
annotation=self._create_access_path(p.annotation),
|
||||
kind_name=str(p.kind)
|
||||
) for p in signature.parameters.values()
|
||||
]
|
||||
|
||||
def negate(self):
|
||||
return self._create_access_path(-self._obj)
|
||||
|
||||
def dict_values(self):
|
||||
return [self._create_access_path(v) for v in self._obj.values()]
|
||||
|
||||
def is_super_class(self, exception):
|
||||
return issubclass(exception, self._obj)
|
||||
|
||||
def get_dir_infos(self):
|
||||
"""
|
||||
Used to return a couple of infos that are needed when accessing the sub
|
||||
objects of an objects
|
||||
"""
|
||||
# TODO is_allowed_getattr might raise an AttributeError
|
||||
tuples = dict(
|
||||
(force_unicode(name), self.is_allowed_getattr(name))
|
||||
for name in self.dir()
|
||||
)
|
||||
return self.needs_type_completions(), tuples
|
||||
|
||||
|
||||
def _is_class_instance(obj):
|
||||
"""Like inspect.* methods."""
|
||||
try:
|
||||
cls = obj.__class__
|
||||
except AttributeError:
|
||||
return False
|
||||
else:
|
||||
return cls != type and not issubclass(cls, NOT_CLASS_TYPES)
|
||||
|
||||
|
||||
if py_version >= 35:
|
||||
exec(compile(dedent("""
|
||||
async def _coroutine(): pass
|
||||
_coroutine = _coroutine()
|
||||
CoroutineType = type(_coroutine)
|
||||
_coroutine.close() # Prevent ResourceWarning
|
||||
"""), 'blub', 'exec'))
|
||||
_coroutine_wrapper = _coroutine.__await__()
|
||||
else:
|
||||
_coroutine = None
|
||||
_coroutine_wrapper = None
|
||||
|
||||
if py_version >= 36:
|
||||
exec(compile(dedent("""
|
||||
async def _async_generator():
|
||||
yield
|
||||
_async_generator = _async_generator()
|
||||
AsyncGeneratorType = type(_async_generator)
|
||||
"""), 'blub', 'exec'))
|
||||
else:
|
||||
_async_generator = None
|
||||
|
||||
class _SPECIAL_OBJECTS(object):
|
||||
FUNCTION_CLASS = types.FunctionType
|
||||
BOUND_METHOD_CLASS = type(DirectObjectAccess(None, None).py__bool__)
|
||||
MODULE_CLASS = types.ModuleType
|
||||
GENERATOR_OBJECT = _a_generator(1.0)
|
||||
BUILTINS = builtins
|
||||
COROUTINE = _coroutine
|
||||
COROUTINE_WRAPPER = _coroutine_wrapper
|
||||
ASYNC_GENERATOR = _async_generator
|
||||
|
||||
|
||||
def get_special_object(evaluator, identifier):
|
||||
obj = getattr(_SPECIAL_OBJECTS, identifier)
|
||||
return create_access_path(evaluator, obj)
|
||||
@@ -1,483 +0,0 @@
|
||||
"""
|
||||
Imitate the parser representation.
|
||||
"""
|
||||
import re
|
||||
from functools import partial
|
||||
|
||||
from jedi import debug
|
||||
from jedi._compatibility import force_unicode, Parameter
|
||||
from jedi.cache import underscore_memoization, memoize_method
|
||||
from jedi.evaluate.filters import AbstractFilter, AbstractNameDefinition, \
|
||||
ContextNameMixin
|
||||
from jedi.evaluate.base_context import Context, ContextSet
|
||||
from jedi.evaluate.lazy_context import LazyKnownContext
|
||||
from jedi.evaluate.compiled.access import _sentinel
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate.helpers import reraise_as_evaluator
|
||||
from . import fake
|
||||
|
||||
|
||||
class CheckAttribute(object):
|
||||
"""Raises an AttributeError if the attribute X isn't available."""
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
# Remove the py in front of e.g. py__call__.
|
||||
self.check_name = force_unicode(func.__name__[2:])
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
|
||||
# This might raise an AttributeError. That's wanted.
|
||||
if self.check_name == '__iter__':
|
||||
# Python iterators are a bit strange, because there's no need for
|
||||
# the __iter__ function as long as __getitem__ is defined (it will
|
||||
# just start with __getitem__(0). This is especially true for
|
||||
# Python 2 strings, where `str.__iter__` is not even defined.
|
||||
if not instance.access_handle.has_iter():
|
||||
raise AttributeError
|
||||
else:
|
||||
instance.access_handle.getattr(self.check_name)
|
||||
return partial(self.func, instance)
|
||||
|
||||
|
||||
class CompiledObject(Context):
|
||||
def __init__(self, evaluator, access_handle, parent_context=None, faked_class=None):
|
||||
super(CompiledObject, self).__init__(evaluator, parent_context)
|
||||
self.access_handle = access_handle
|
||||
# This attribute will not be set for most classes, except for fakes.
|
||||
self.tree_node = faked_class
|
||||
|
||||
@CheckAttribute
|
||||
def py__call__(self, params):
|
||||
if self.tree_node is not None and self.tree_node.type == 'funcdef':
|
||||
from jedi.evaluate.context.function import FunctionContext
|
||||
return FunctionContext(
|
||||
self.evaluator,
|
||||
parent_context=self.parent_context,
|
||||
tree_node=self.tree_node
|
||||
).py__call__(params)
|
||||
if self.access_handle.is_class():
|
||||
from jedi.evaluate.context import CompiledInstance
|
||||
return ContextSet(CompiledInstance(self.evaluator, self.parent_context, self, params))
|
||||
else:
|
||||
return ContextSet.from_iterable(self._execute_function(params))
|
||||
|
||||
@CheckAttribute
|
||||
def py__class__(self):
|
||||
return create_from_access_path(self.evaluator, self.access_handle.py__class__())
|
||||
|
||||
@CheckAttribute
|
||||
def py__mro__(self):
|
||||
return (self,) + tuple(
|
||||
create_from_access_path(self.evaluator, access)
|
||||
for access in self.access_handle.py__mro__accesses()
|
||||
)
|
||||
|
||||
@CheckAttribute
|
||||
def py__bases__(self):
|
||||
return tuple(
|
||||
create_from_access_path(self.evaluator, access)
|
||||
for access in self.access_handle.py__bases__()
|
||||
)
|
||||
|
||||
@CheckAttribute
|
||||
def py__path__(self):
|
||||
return self.access_handle.py__path__()
|
||||
|
||||
def py__bool__(self):
|
||||
return self.access_handle.py__bool__()
|
||||
|
||||
def py__file__(self):
|
||||
return self.access_handle.py__file__()
|
||||
|
||||
def is_class(self):
|
||||
return self.access_handle.is_class()
|
||||
|
||||
def py__doc__(self, include_call_signature=False):
|
||||
return self.access_handle.py__doc__()
|
||||
|
||||
def get_param_names(self):
|
||||
try:
|
||||
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 self.access_handle.ismethoddescriptor():
|
||||
tokens.insert(0, 'self')
|
||||
for p in tokens:
|
||||
parts = p.strip().split('=')
|
||||
yield UnresolvableParamName(self, parts[0])
|
||||
else:
|
||||
for signature_param in signature_params:
|
||||
yield SignatureParamName(self, signature_param)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.access_handle.get_repr())
|
||||
|
||||
@underscore_memoization
|
||||
def _parse_function_doc(self):
|
||||
doc = self.py__doc__()
|
||||
if doc is None:
|
||||
return '', ''
|
||||
|
||||
return _parse_function_doc(doc)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return self.access_handle.get_api_type()
|
||||
|
||||
@underscore_memoization
|
||||
def _cls(self):
|
||||
"""
|
||||
We used to limit the lookups for instantiated objects like list(), but
|
||||
this is not the case anymore. Python itself
|
||||
"""
|
||||
# Ensures that a CompiledObject is returned that is not an instance (like list)
|
||||
return self
|
||||
|
||||
def get_filters(self, search_global=False, is_instance=False,
|
||||
until_position=None, origin_scope=None):
|
||||
yield self._ensure_one_filter(is_instance)
|
||||
|
||||
@memoize_method
|
||||
def _ensure_one_filter(self, is_instance):
|
||||
"""
|
||||
search_global shouldn't change the fact that there's one dict, this way
|
||||
there's only one `object`.
|
||||
"""
|
||||
return CompiledObjectFilter(self.evaluator, self, is_instance)
|
||||
|
||||
@CheckAttribute
|
||||
def py__getitem__(self, index):
|
||||
with reraise_as_evaluator(IndexError, KeyError, TypeError):
|
||||
access = self.access_handle.py__getitem__(index)
|
||||
if access is None:
|
||||
return ContextSet()
|
||||
|
||||
return ContextSet(create_from_access_path(self.evaluator, access))
|
||||
|
||||
@CheckAttribute
|
||||
def py__iter__(self):
|
||||
for access in self.access_handle.py__iter__list():
|
||||
yield LazyKnownContext(create_from_access_path(self.evaluator, access))
|
||||
|
||||
def py__name__(self):
|
||||
return self.access_handle.py__name__()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
name = self.py__name__()
|
||||
if name is None:
|
||||
name = self.access_handle.get_repr()
|
||||
return CompiledContextName(self, name)
|
||||
|
||||
def _execute_function(self, params):
|
||||
from jedi.evaluate import docstrings
|
||||
from jedi.evaluate.compiled import builtin_from_name
|
||||
if self.api_type != 'function':
|
||||
return
|
||||
|
||||
for name in self._parse_function_doc()[1].split():
|
||||
try:
|
||||
# TODO wtf is this? this is exactly the same as the thing
|
||||
# below. It uses getattr as well.
|
||||
self.evaluator.builtins_module.access_handle.getattr(name)
|
||||
except AttributeError:
|
||||
continue
|
||||
else:
|
||||
bltn_obj = builtin_from_name(self.evaluator, name)
|
||||
for result in bltn_obj.execute(params):
|
||||
yield result
|
||||
for type_ in docstrings.infer_return_types(self):
|
||||
yield type_
|
||||
|
||||
def dict_values(self):
|
||||
return ContextSet.from_iterable(
|
||||
create_from_access_path(self.evaluator, access)
|
||||
for access in self.access_handle.dict_values()
|
||||
)
|
||||
|
||||
def get_safe_value(self, default=_sentinel):
|
||||
try:
|
||||
return self.access_handle.get_safe_value()
|
||||
except ValueError:
|
||||
if default == _sentinel:
|
||||
raise
|
||||
return default
|
||||
|
||||
def execute_operation(self, other, operator):
|
||||
return create_from_access_path(
|
||||
self.evaluator,
|
||||
self.access_handle.execute_operation(other.access_handle, operator)
|
||||
)
|
||||
|
||||
def negate(self):
|
||||
return create_from_access_path(self.evaluator, self.access_handle.negate())
|
||||
|
||||
def is_super_class(self, exception):
|
||||
return self.access_handle.is_super_class(exception)
|
||||
|
||||
|
||||
class CompiledName(AbstractNameDefinition):
|
||||
def __init__(self, evaluator, parent_context, name):
|
||||
self._evaluator = evaluator
|
||||
self.parent_context = parent_context
|
||||
self.string_name = name
|
||||
|
||||
def __repr__(self):
|
||||
try:
|
||||
name = self.parent_context.name # __name__ is not defined all the time
|
||||
except AttributeError:
|
||||
name = None
|
||||
return '<%s: (%s).%s>' % (self.__class__.__name__, name, self.string_name)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return next(iter(self.infer())).api_type
|
||||
|
||||
@underscore_memoization
|
||||
def infer(self):
|
||||
return ContextSet(create_from_name(
|
||||
self._evaluator, self.parent_context, self.string_name
|
||||
))
|
||||
|
||||
|
||||
class SignatureParamName(AbstractNameDefinition):
|
||||
api_type = u'param'
|
||||
|
||||
def __init__(self, compiled_obj, signature_param):
|
||||
self.parent_context = compiled_obj.parent_context
|
||||
self._signature_param = signature_param
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
return self._signature_param.name
|
||||
|
||||
def 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
|
||||
contexts = ContextSet()
|
||||
if p.has_default:
|
||||
contexts = ContextSet(create_from_access_path(evaluator, p.default))
|
||||
if p.has_annotation:
|
||||
annotation = create_from_access_path(evaluator, p.annotation)
|
||||
contexts |= annotation.execute_evaluated()
|
||||
return contexts
|
||||
|
||||
|
||||
class UnresolvableParamName(AbstractNameDefinition):
|
||||
api_type = u'param'
|
||||
|
||||
def __init__(self, compiled_obj, name):
|
||||
self.parent_context = compiled_obj.parent_context
|
||||
self.string_name = name
|
||||
|
||||
def get_kind(self):
|
||||
return Parameter.POSITIONAL_ONLY
|
||||
|
||||
def infer(self):
|
||||
return ContextSet()
|
||||
|
||||
|
||||
class CompiledContextName(ContextNameMixin, AbstractNameDefinition):
|
||||
def __init__(self, context, name):
|
||||
self.string_name = name
|
||||
self._context = context
|
||||
self.parent_context = context.parent_context
|
||||
|
||||
|
||||
class EmptyCompiledName(AbstractNameDefinition):
|
||||
"""
|
||||
Accessing some names will raise an exception. To avoid not having any
|
||||
completions, just give Jedi the option to return this object. It infers to
|
||||
nothing.
|
||||
"""
|
||||
def __init__(self, evaluator, name):
|
||||
self.parent_context = evaluator.builtins_module
|
||||
self.string_name = name
|
||||
|
||||
def infer(self):
|
||||
return ContextSet()
|
||||
|
||||
|
||||
class CompiledObjectFilter(AbstractFilter):
|
||||
name_class = CompiledName
|
||||
|
||||
def __init__(self, evaluator, compiled_object, is_instance=False):
|
||||
self._evaluator = evaluator
|
||||
self._compiled_object = compiled_object
|
||||
self._is_instance = is_instance
|
||||
|
||||
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(),
|
||||
check_has_attribute=True
|
||||
)
|
||||
|
||||
def _get(self, name, allowed_getattr_callback, dir_callback, check_has_attribute=False):
|
||||
"""
|
||||
To remove quite a few access calls we introduced the callback here.
|
||||
"""
|
||||
has_attribute, is_descriptor = allowed_getattr_callback()
|
||||
if check_has_attribute and not has_attribute:
|
||||
return []
|
||||
|
||||
# Always use unicode objects in Python 2 from here.
|
||||
name = force_unicode(name)
|
||||
|
||||
if is_descriptor or not has_attribute:
|
||||
return [self._get_cached_name(name, is_empty=True)]
|
||||
|
||||
if self._is_instance and name not in dir_callback():
|
||||
return []
|
||||
return [self._get_cached_name(name)]
|
||||
|
||||
@memoize_method
|
||||
def _get_cached_name(self, name, is_empty=False):
|
||||
if is_empty:
|
||||
return EmptyCompiledName(self._evaluator, name)
|
||||
else:
|
||||
return self._create_name(name)
|
||||
|
||||
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()
|
||||
for name in dir_infos:
|
||||
names += self._get(
|
||||
name,
|
||||
lambda: dir_infos[name],
|
||||
lambda: dir_infos.keys(),
|
||||
)
|
||||
|
||||
# ``dir`` doesn't include the type names.
|
||||
if not self._is_instance and needs_type_completions:
|
||||
for filter in builtin_from_name(self._evaluator, u'type').get_filters():
|
||||
names += filter.values()
|
||||
return names
|
||||
|
||||
def _create_name(self, name):
|
||||
return self.name_class(self._evaluator, self._compiled_object, name)
|
||||
|
||||
|
||||
docstr_defaults = {
|
||||
'floating point number': u'float',
|
||||
'character': u'str',
|
||||
'integer': u'int',
|
||||
'dictionary': u'dict',
|
||||
'string': u'str',
|
||||
}
|
||||
|
||||
|
||||
def _parse_function_doc(doc):
|
||||
"""
|
||||
Takes a function and returns the params and return value as a tuple.
|
||||
This is nothing more than a docstring parser.
|
||||
|
||||
TODO docstrings like utime(path, (atime, mtime)) and a(b [, b]) -> None
|
||||
TODO docstrings like 'tuple of integers'
|
||||
"""
|
||||
doc = force_unicode(doc)
|
||||
# parse round parentheses: def func(a, (b,c))
|
||||
try:
|
||||
count = 0
|
||||
start = doc.index('(')
|
||||
for i, s in enumerate(doc[start:]):
|
||||
if s == '(':
|
||||
count += 1
|
||||
elif s == ')':
|
||||
count -= 1
|
||||
if count == 0:
|
||||
end = start + i
|
||||
break
|
||||
param_str = doc[start + 1:end]
|
||||
except (ValueError, UnboundLocalError):
|
||||
# ValueError for doc.index
|
||||
# UnboundLocalError for undefined end in last line
|
||||
debug.dbg('no brackets found - no param')
|
||||
end = 0
|
||||
param_str = u''
|
||||
else:
|
||||
# remove square brackets, that show an optional param ( = None)
|
||||
def change_options(m):
|
||||
args = m.group(1).split(',')
|
||||
for i, a in enumerate(args):
|
||||
if a and '=' not in a:
|
||||
args[i] += '=None'
|
||||
return ','.join(args)
|
||||
|
||||
while True:
|
||||
param_str, changes = re.subn(r' ?\[([^\[\]]+)\]',
|
||||
change_options, param_str)
|
||||
if changes == 0:
|
||||
break
|
||||
param_str = param_str.replace('-', '_') # see: isinstance.__doc__
|
||||
|
||||
# parse return value
|
||||
r = re.search(u'-[>-]* ', doc[end:end + 7])
|
||||
if r is None:
|
||||
ret = u''
|
||||
else:
|
||||
index = end + r.end()
|
||||
# get result type, which can contain newlines
|
||||
pattern = re.compile(r'(,\n|[^\n-])+')
|
||||
ret_str = pattern.match(doc, index).group(0).strip()
|
||||
# New object -> object()
|
||||
ret_str = re.sub(r'[nN]ew (.*)', r'\1()', ret_str)
|
||||
|
||||
ret = docstr_defaults.get(ret_str, ret_str)
|
||||
|
||||
return param_str, ret
|
||||
|
||||
|
||||
def create_from_name(evaluator, compiled_object, name):
|
||||
faked = None
|
||||
try:
|
||||
faked = fake.get_faked_with_parent_context(compiled_object, name)
|
||||
except fake.FakeDoesNotExist:
|
||||
pass
|
||||
|
||||
access = compiled_object.access_handle.getattr(name, default=None)
|
||||
parent_context = compiled_object
|
||||
if parent_context.is_class():
|
||||
parent_context = parent_context.parent_context
|
||||
return create_cached_compiled_object(
|
||||
evaluator, access, parent_context=parent_context, faked=faked
|
||||
)
|
||||
|
||||
|
||||
def _normalize_create_args(func):
|
||||
"""The cache doesn't care about keyword vs. normal args."""
|
||||
def wrapper(evaluator, obj, parent_context=None, faked=None):
|
||||
return func(evaluator, obj, parent_context, faked)
|
||||
return wrapper
|
||||
|
||||
|
||||
def create_from_access_path(evaluator, access_path):
|
||||
parent_context = None
|
||||
for name, access in access_path.accesses:
|
||||
try:
|
||||
if parent_context is None:
|
||||
faked = fake.get_faked_module(evaluator, access_path.accesses[0][0])
|
||||
else:
|
||||
faked = fake.get_faked_with_parent_context(parent_context, name)
|
||||
except fake.FakeDoesNotExist:
|
||||
faked = None
|
||||
|
||||
parent_context = create_cached_compiled_object(evaluator, access, parent_context, faked)
|
||||
return parent_context
|
||||
|
||||
|
||||
@_normalize_create_args
|
||||
@evaluator_function_cache()
|
||||
def create_cached_compiled_object(evaluator, access_handle, parent_context, faked):
|
||||
return CompiledObject(evaluator, access_handle, parent_context, faked)
|
||||
@@ -1,82 +0,0 @@
|
||||
"""
|
||||
Loads functions that are mixed in to the standard library. E.g. builtins are
|
||||
written in C (binaries), but my autocompletion only understands Python code. By
|
||||
mixing in Python code, the autocompletion should work much better for builtins.
|
||||
"""
|
||||
|
||||
import os
|
||||
from itertools import chain
|
||||
|
||||
from jedi._compatibility import unicode
|
||||
|
||||
fake_modules = {}
|
||||
|
||||
|
||||
def _get_path_dict():
|
||||
path = os.path.dirname(os.path.abspath(__file__))
|
||||
base_path = os.path.join(path, 'fake')
|
||||
dct = {}
|
||||
for file_name in os.listdir(base_path):
|
||||
if file_name.endswith('.pym'):
|
||||
dct[file_name[:-4]] = os.path.join(base_path, file_name)
|
||||
return dct
|
||||
|
||||
|
||||
_path_dict = _get_path_dict()
|
||||
|
||||
|
||||
class FakeDoesNotExist(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def _load_faked_module(evaluator, module_name):
|
||||
try:
|
||||
return fake_modules[module_name]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
check_module_name = module_name
|
||||
if module_name == '__builtin__' and evaluator.environment.version_info.major == 2:
|
||||
check_module_name = 'builtins'
|
||||
|
||||
try:
|
||||
path = _path_dict[check_module_name]
|
||||
except KeyError:
|
||||
fake_modules[module_name] = None
|
||||
return
|
||||
|
||||
with open(path) as f:
|
||||
source = f.read()
|
||||
|
||||
fake_modules[module_name] = m = evaluator.latest_grammar.parse(unicode(source))
|
||||
|
||||
if check_module_name != module_name:
|
||||
# There are two implementations of `open` for either python 2/3.
|
||||
# -> Rename the python2 version (`look at fake/builtins.pym`).
|
||||
open_func = _search_scope(m, 'open')
|
||||
open_func.children[1].value = 'open_python3'
|
||||
open_func = _search_scope(m, 'open_python2')
|
||||
open_func.children[1].value = 'open'
|
||||
return m
|
||||
|
||||
|
||||
def _search_scope(scope, obj_name):
|
||||
for s in chain(scope.iter_classdefs(), scope.iter_funcdefs()):
|
||||
if s.name.value == obj_name:
|
||||
return s
|
||||
|
||||
|
||||
def get_faked_with_parent_context(parent_context, name):
|
||||
if parent_context.tree_node is not None:
|
||||
# Try to search in already clearly defined stuff.
|
||||
found = _search_scope(parent_context.tree_node, name)
|
||||
if found is not None:
|
||||
return found
|
||||
raise FakeDoesNotExist
|
||||
|
||||
|
||||
def get_faked_module(evaluator, string_name):
|
||||
module = _load_faked_module(evaluator, string_name)
|
||||
if module is None:
|
||||
raise FakeDoesNotExist
|
||||
return module
|
||||
@@ -1,9 +0,0 @@
|
||||
class partial():
|
||||
def __init__(self, func, *args, **keywords):
|
||||
self.__func = func
|
||||
self.__args = args
|
||||
self.__keywords = keywords
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
# TODO should be **dict(self.__keywords, **kwargs)
|
||||
return self.__func(*(self.__args + args), **self.__keywords)
|
||||
@@ -1,26 +0,0 @@
|
||||
def connect(database, timeout=None, isolation_level=None, detect_types=None, factory=None):
|
||||
return Connection()
|
||||
|
||||
|
||||
class Connection():
|
||||
def cursor(self):
|
||||
return Cursor()
|
||||
|
||||
|
||||
class Cursor():
|
||||
def cursor(self):
|
||||
return Cursor()
|
||||
|
||||
def fetchone(self):
|
||||
return Row()
|
||||
|
||||
def fetchmany(self, size=cursor.arraysize):
|
||||
return [self.fetchone()]
|
||||
|
||||
def fetchall(self):
|
||||
return [self.fetchone()]
|
||||
|
||||
|
||||
class Row():
|
||||
def keys(self):
|
||||
return ['']
|
||||
@@ -1,99 +0,0 @@
|
||||
def compile():
|
||||
class SRE_Match():
|
||||
endpos = int()
|
||||
lastgroup = int()
|
||||
lastindex = int()
|
||||
pos = int()
|
||||
string = str()
|
||||
regs = ((int(), int()),)
|
||||
|
||||
def __init__(self, pattern):
|
||||
self.re = pattern
|
||||
|
||||
def start(self):
|
||||
return int()
|
||||
|
||||
def end(self):
|
||||
return int()
|
||||
|
||||
def span(self):
|
||||
return int(), int()
|
||||
|
||||
def expand(self):
|
||||
return str()
|
||||
|
||||
def group(self, nr):
|
||||
return str()
|
||||
|
||||
def groupdict(self):
|
||||
return {str(): str()}
|
||||
|
||||
def groups(self):
|
||||
return (str(),)
|
||||
|
||||
class SRE_Pattern():
|
||||
flags = int()
|
||||
groupindex = {}
|
||||
groups = int()
|
||||
pattern = str()
|
||||
|
||||
def findall(self, string, pos=None, endpos=None):
|
||||
"""
|
||||
findall(string[, pos[, endpos]]) --> list.
|
||||
Return a list of all non-overlapping matches of pattern in string.
|
||||
"""
|
||||
return [str()]
|
||||
|
||||
def finditer(self, string, pos=None, endpos=None):
|
||||
"""
|
||||
finditer(string[, pos[, endpos]]) --> iterator.
|
||||
Return an iterator over all non-overlapping matches for the
|
||||
RE pattern in string. For each match, the iterator returns a
|
||||
match object.
|
||||
"""
|
||||
yield SRE_Match(self)
|
||||
|
||||
def match(self, string, pos=None, endpos=None):
|
||||
"""
|
||||
match(string[, pos[, endpos]]) --> match object or None.
|
||||
Matches zero or more characters at the beginning of the string
|
||||
pattern
|
||||
"""
|
||||
return SRE_Match(self)
|
||||
|
||||
def scanner(self, string, pos=None, endpos=None):
|
||||
pass
|
||||
|
||||
def search(self, string, pos=None, endpos=None):
|
||||
"""
|
||||
search(string[, pos[, endpos]]) --> match object or None.
|
||||
Scan through string looking for a match, and return a corresponding
|
||||
MatchObject instance. Return None if no position in the string matches.
|
||||
"""
|
||||
return SRE_Match(self)
|
||||
|
||||
def split(self, string, maxsplit=0]):
|
||||
"""
|
||||
split(string[, maxsplit = 0]) --> list.
|
||||
Split string by the occurrences of pattern.
|
||||
"""
|
||||
return [str()]
|
||||
|
||||
def sub(self, repl, string, count=0):
|
||||
"""
|
||||
sub(repl, string[, count = 0]) --> newstring
|
||||
Return the string obtained by replacing the leftmost non-overlapping
|
||||
occurrences of pattern in string by the replacement repl.
|
||||
"""
|
||||
return str()
|
||||
|
||||
def subn(self, repl, string, count=0):
|
||||
"""
|
||||
subn(repl, string[, count = 0]) --> (newstring, number of subs)
|
||||
Return the tuple (new_string, number_of_subs_made) found by replacing
|
||||
the leftmost non-overlapping occurrences of pattern with the
|
||||
replacement repl.
|
||||
"""
|
||||
return (str(), int())
|
||||
|
||||
return SRE_Pattern()
|
||||
@@ -1,9 +0,0 @@
|
||||
def proxy(object, callback=None):
|
||||
return object
|
||||
|
||||
class ref():
|
||||
def __init__(self, object, callback=None):
|
||||
self.__object = object
|
||||
|
||||
def __call__(self):
|
||||
return self.__object
|
||||
@@ -1,277 +0,0 @@
|
||||
"""
|
||||
Pure Python implementation of some builtins.
|
||||
This code is not going to be executed anywhere.
|
||||
These implementations are not always correct, but should work as good as
|
||||
possible for the auto completion.
|
||||
"""
|
||||
|
||||
|
||||
def next(iterator, default=None):
|
||||
if random.choice([0, 1]):
|
||||
if hasattr("next"):
|
||||
return iterator.next()
|
||||
else:
|
||||
return iterator.__next__()
|
||||
else:
|
||||
if default is not None:
|
||||
return default
|
||||
|
||||
|
||||
def iter(collection, sentinel=None):
|
||||
if sentinel:
|
||||
yield collection()
|
||||
else:
|
||||
for c in collection:
|
||||
yield c
|
||||
|
||||
|
||||
def range(start, stop=None, step=1):
|
||||
return [0]
|
||||
|
||||
|
||||
class file():
|
||||
def __iter__(self):
|
||||
yield ''
|
||||
|
||||
def next(self):
|
||||
return ''
|
||||
|
||||
def readlines(self):
|
||||
return ['']
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
|
||||
class xrange():
|
||||
# Attention: this function doesn't exist in Py3k (there it is range).
|
||||
def __iter__(self):
|
||||
yield 1
|
||||
|
||||
def count(self):
|
||||
return 1
|
||||
|
||||
def index(self):
|
||||
return 1
|
||||
|
||||
|
||||
def open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True):
|
||||
import io
|
||||
return io.TextIOWrapper(file, mode, buffering, encoding, errors, newline, closefd)
|
||||
|
||||
|
||||
def open_python2(name, mode=None, buffering=None):
|
||||
return file(name, mode, buffering)
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# descriptors
|
||||
#--------------------------------------------------------
|
||||
class property():
|
||||
def __init__(self, fget, fset=None, fdel=None, doc=None):
|
||||
self.fget = fget
|
||||
self.fset = fset
|
||||
self.fdel = fdel
|
||||
self.__doc__ = doc
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.fget(obj)
|
||||
|
||||
def __set__(self, obj, value):
|
||||
self.fset(obj, value)
|
||||
|
||||
def __delete__(self, obj):
|
||||
self.fdel(obj)
|
||||
|
||||
def setter(self, func):
|
||||
self.fset = func
|
||||
return self
|
||||
|
||||
def getter(self, func):
|
||||
self.fget = func
|
||||
return self
|
||||
|
||||
def deleter(self, func):
|
||||
self.fdel = func
|
||||
return self
|
||||
|
||||
|
||||
class staticmethod():
|
||||
def __init__(self, func):
|
||||
self.__func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
return self.__func
|
||||
|
||||
|
||||
class classmethod():
|
||||
def __init__(self, func):
|
||||
self.__func = func
|
||||
|
||||
def __get__(self, obj, cls):
|
||||
def _method(*args, **kwargs):
|
||||
return self.__func(cls, *args, **kwargs)
|
||||
return _method
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# array stuff
|
||||
#--------------------------------------------------------
|
||||
class list():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = []
|
||||
for i in iterable:
|
||||
self.__iterable += [i]
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def __getitem__(self, y):
|
||||
return self.__iterable[y]
|
||||
|
||||
def pop(self):
|
||||
return self.__iterable[int()]
|
||||
|
||||
|
||||
class tuple():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = []
|
||||
for i in iterable:
|
||||
self.__iterable += [i]
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def __getitem__(self, y):
|
||||
return self.__iterable[y]
|
||||
|
||||
def index(self):
|
||||
return 1
|
||||
|
||||
def count(self):
|
||||
return 1
|
||||
|
||||
|
||||
class set():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = iterable
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def pop(self):
|
||||
return list(self.__iterable)[-1]
|
||||
|
||||
def copy(self):
|
||||
return self
|
||||
|
||||
def difference(self, other):
|
||||
return self - other
|
||||
|
||||
def intersection(self, other):
|
||||
return self & other
|
||||
|
||||
def symmetric_difference(self, other):
|
||||
return self ^ other
|
||||
|
||||
def union(self, other):
|
||||
return self | other
|
||||
|
||||
|
||||
class frozenset():
|
||||
def __init__(self, iterable=[]):
|
||||
self.__iterable = iterable
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def copy(self):
|
||||
return self
|
||||
|
||||
|
||||
class dict():
|
||||
def __init__(self, **elements):
|
||||
self.__elements = elements
|
||||
|
||||
def clear(self):
|
||||
# has a strange docstr
|
||||
pass
|
||||
|
||||
def __getitem__(self, obj):
|
||||
return self.__elements[obj]
|
||||
|
||||
def get(self, k, d=None):
|
||||
# TODO implement
|
||||
try:
|
||||
return self.__elements[k]
|
||||
pass
|
||||
except KeyError:
|
||||
return d
|
||||
|
||||
def values(self):
|
||||
return self.__elements.values()
|
||||
|
||||
def setdefault(self, k, d):
|
||||
# TODO maybe also return the content
|
||||
return d
|
||||
|
||||
|
||||
class enumerate():
|
||||
def __init__(self, sequence, start=0):
|
||||
self.__sequence = sequence
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__sequence:
|
||||
yield 1, i
|
||||
|
||||
def __next__(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
def next(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
|
||||
class reversed():
|
||||
def __init__(self, sequence):
|
||||
self.__sequence = sequence
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__sequence:
|
||||
yield i
|
||||
|
||||
def __next__(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
def next(self):
|
||||
return next(self.__iter__())
|
||||
|
||||
|
||||
def sorted(iterable, cmp=None, key=None, reverse=False):
|
||||
return iterable
|
||||
|
||||
|
||||
#--------------------------------------------------------
|
||||
# basic types
|
||||
#--------------------------------------------------------
|
||||
class int():
|
||||
def __init__(self, x, base=None):
|
||||
pass
|
||||
|
||||
|
||||
class str():
|
||||
def __init__(self, obj):
|
||||
pass
|
||||
|
||||
def strip(self):
|
||||
return str()
|
||||
|
||||
def split(self):
|
||||
return [str()]
|
||||
|
||||
class type():
|
||||
def mro():
|
||||
return [object]
|
||||
@@ -1,4 +0,0 @@
|
||||
class datetime():
|
||||
@staticmethod
|
||||
def now():
|
||||
return datetime()
|
||||
@@ -1,12 +0,0 @@
|
||||
class TextIOWrapper():
|
||||
def __next__(self):
|
||||
return str()
|
||||
|
||||
def __iter__(self):
|
||||
yield str()
|
||||
|
||||
def readlines(self):
|
||||
return ['']
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
@@ -1,33 +0,0 @@
|
||||
# Just copied this code from Python 3.6.
|
||||
|
||||
class itemgetter:
|
||||
"""
|
||||
Return a callable object that fetches the given item(s) from its operand.
|
||||
After f = itemgetter(2), the call f(r) returns r[2].
|
||||
After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3])
|
||||
"""
|
||||
__slots__ = ('_items', '_call')
|
||||
|
||||
def __init__(self, item, *items):
|
||||
if not items:
|
||||
self._items = (item,)
|
||||
def func(obj):
|
||||
return obj[item]
|
||||
self._call = func
|
||||
else:
|
||||
self._items = items = (item,) + items
|
||||
def func(obj):
|
||||
return tuple(obj[i] for i in items)
|
||||
self._call = func
|
||||
|
||||
def __call__(self, obj):
|
||||
return self._call(obj)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s.%s(%s)' % (self.__class__.__module__,
|
||||
self.__class__.__name__,
|
||||
', '.join(map(repr, self._items)))
|
||||
|
||||
def __reduce__(self):
|
||||
return self.__class__, self._items
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
def getcwd():
|
||||
return ''
|
||||
|
||||
def getcwdu():
|
||||
return ''
|
||||
@@ -1,238 +0,0 @@
|
||||
"""
|
||||
Used only for REPL Completion.
|
||||
"""
|
||||
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from jedi.parser_utils import get_cached_code_lines
|
||||
|
||||
from jedi import settings
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.cache import underscore_memoization
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.base_context import Context, ContextSet
|
||||
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
|
||||
from jedi.evaluate.compiled.context import create_cached_compiled_object
|
||||
|
||||
|
||||
class MixedObject(object):
|
||||
"""
|
||||
A ``MixedObject`` is used in two ways:
|
||||
|
||||
1. It uses the default logic of ``parser.python.tree`` objects,
|
||||
2. except for getattr calls. The names dicts are generated in a fashion
|
||||
like ``CompiledObject``.
|
||||
|
||||
This combined logic makes it possible to provide more powerful REPL
|
||||
completion. It allows side effects that are not noticable with the default
|
||||
parser structure to still be completeable.
|
||||
|
||||
The biggest difference from CompiledObject to MixedObject is that we are
|
||||
generally dealing with Python code and not with C code. This will generate
|
||||
fewer special cases, because we in Python you don't have the same freedoms
|
||||
to modify the runtime.
|
||||
"""
|
||||
def __init__(self, evaluator, parent_context, compiled_object, tree_context):
|
||||
self.evaluator = evaluator
|
||||
self.parent_context = parent_context
|
||||
self.compiled_object = compiled_object
|
||||
self._context = tree_context
|
||||
self.access_handle = compiled_object.access_handle
|
||||
|
||||
# We have to overwrite everything that has to do with trailers, name
|
||||
# lookups and filters to make it possible to route name lookups towards
|
||||
# compiled objects and the rest towards tree node contexts.
|
||||
def py__getattribute__(*args, **kwargs):
|
||||
return Context.py__getattribute__(*args, **kwargs)
|
||||
|
||||
def get_filters(self, *args, **kwargs):
|
||||
yield MixedObjectFilter(self.evaluator, self)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (type(self).__name__, self.access_handle.get_repr())
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._context, name)
|
||||
|
||||
|
||||
class MixedName(compiled.CompiledName):
|
||||
"""
|
||||
The ``CompiledName._compiled_object`` is our MixedObject.
|
||||
"""
|
||||
@property
|
||||
def start_pos(self):
|
||||
contexts = list(self.infer())
|
||||
if not contexts:
|
||||
# This means a start_pos that doesn't exist (compiled objects).
|
||||
return 0, 0
|
||||
return contexts[0].name.start_pos
|
||||
|
||||
@start_pos.setter
|
||||
def start_pos(self, value):
|
||||
# Ignore the __init__'s start_pos setter call.
|
||||
pass
|
||||
|
||||
@underscore_memoization
|
||||
def infer(self):
|
||||
access_handle = self.parent_context.access_handle
|
||||
# TODO use logic from compiled.CompiledObjectFilter
|
||||
access_handle = access_handle.getattr(self.string_name, default=None)
|
||||
return ContextSet(
|
||||
_create(self._evaluator, access_handle, parent_context=self.parent_context)
|
||||
)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return next(iter(self.infer())).api_type
|
||||
|
||||
|
||||
class MixedObjectFilter(compiled.CompiledObjectFilter):
|
||||
name_class = MixedName
|
||||
|
||||
def __init__(self, evaluator, mixed_object, is_instance=False):
|
||||
super(MixedObjectFilter, self).__init__(
|
||||
evaluator, mixed_object, is_instance)
|
||||
self._mixed_object = mixed_object
|
||||
|
||||
#def _create(self, name):
|
||||
#return MixedName(self._evaluator, self._compiled_object, name)
|
||||
|
||||
|
||||
@evaluator_function_cache()
|
||||
def _load_module(evaluator, path):
|
||||
module_node = evaluator.grammar.parse(
|
||||
path=path,
|
||||
cache=True,
|
||||
diff_cache=settings.fast_parser,
|
||||
cache_path=settings.cache_directory
|
||||
).get_root_node()
|
||||
# python_module = inspect.getmodule(python_object)
|
||||
# TODO we should actually make something like this possible.
|
||||
#evaluator.modules[python_module.__name__] = module_node
|
||||
return module_node
|
||||
|
||||
|
||||
def _get_object_to_check(python_object):
|
||||
"""Check if inspect.getfile has a chance to find the source."""
|
||||
if (inspect.ismodule(python_object) or
|
||||
inspect.isclass(python_object) or
|
||||
inspect.ismethod(python_object) or
|
||||
inspect.isfunction(python_object) or
|
||||
inspect.istraceback(python_object) or
|
||||
inspect.isframe(python_object) or
|
||||
inspect.iscode(python_object)):
|
||||
return python_object
|
||||
|
||||
try:
|
||||
return python_object.__class__
|
||||
except AttributeError:
|
||||
raise TypeError # Prevents computation of `repr` within inspect.
|
||||
|
||||
|
||||
def _find_syntax_node_name(evaluator, 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
|
||||
try:
|
||||
python_object = _get_object_to_check(python_object)
|
||||
path = inspect.getsourcefile(python_object)
|
||||
except TypeError:
|
||||
# The type might not be known (e.g. class_with_dict.__weakref__)
|
||||
return None
|
||||
if path is None or not os.path.exists(path):
|
||||
# The path might not exist or be e.g. <stdin>.
|
||||
return None
|
||||
|
||||
module_node = _load_module(evaluator, path)
|
||||
|
||||
if inspect.ismodule(python_object):
|
||||
# We don't need to check names for modules, because there's not really
|
||||
# a way to write a module in a module in Python (and also __name__ can
|
||||
# be something like ``email.utils``).
|
||||
code_lines = get_cached_code_lines(evaluator.grammar, path)
|
||||
return module_node, module_node, path, code_lines
|
||||
|
||||
try:
|
||||
name_str = python_object.__name__
|
||||
except AttributeError:
|
||||
# Stuff like python_function.__code__.
|
||||
return None
|
||||
|
||||
if name_str == '<lambda>':
|
||||
return None # It's too hard to find lambdas.
|
||||
|
||||
# Doesn't always work (e.g. os.stat_result)
|
||||
names = module_node.get_used_names().get(name_str, [])
|
||||
names = [n for n in names if n.is_definition()]
|
||||
if not names:
|
||||
return None
|
||||
|
||||
try:
|
||||
code = python_object.__code__
|
||||
# By using the line number of a code object we make the lookup in a
|
||||
# file pretty easy. There's still a possibility of people defining
|
||||
# stuff like ``a = 3; foo(a); a = 4`` on the same line, but if people
|
||||
# do so we just don't care.
|
||||
line_nr = code.co_firstlineno
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
line_names = [name for name in names if name.start_pos[0] == line_nr]
|
||||
# There's a chance that the object is not available anymore, because
|
||||
# the code has changed in the background.
|
||||
if line_names:
|
||||
names = line_names
|
||||
|
||||
code_lines = get_cached_code_lines(evaluator.grammar, path)
|
||||
# It's really hard to actually get the right definition, here as a last
|
||||
# resort we just return the last one. This chance might lead to odd
|
||||
# completions at some points but will lead to mostly correct type
|
||||
# inference, because people tend to define a public name in a module only
|
||||
# once.
|
||||
return module_node, names[-1].parent, path, code_lines
|
||||
|
||||
|
||||
@compiled_objects_cache('mixed_cache')
|
||||
def _create(evaluator, access_handle, parent_context, *args):
|
||||
compiled_object = create_cached_compiled_object(
|
||||
evaluator, access_handle, parent_context=parent_context.compiled_object)
|
||||
|
||||
result = _find_syntax_node_name(evaluator, access_handle)
|
||||
if result is None:
|
||||
return compiled_object
|
||||
|
||||
module_node, tree_node, path, code_lines = result
|
||||
|
||||
if parent_context.tree_node.get_root_node() == module_node:
|
||||
module_context = parent_context.get_root_context()
|
||||
else:
|
||||
module_context = ModuleContext(
|
||||
evaluator, module_node,
|
||||
path=path,
|
||||
code_lines=code_lines,
|
||||
)
|
||||
# TODO this __name__ is probably wrong.
|
||||
name = compiled_object.get_root_context().py__name__()
|
||||
if name is not None:
|
||||
imports.add_module_to_cache(evaluator, name, module_context)
|
||||
|
||||
tree_context = module_context.create_context(
|
||||
tree_node,
|
||||
node_is_context=True,
|
||||
node_is_object=True
|
||||
)
|
||||
if tree_node.type == 'classdef':
|
||||
if not access_handle.is_class():
|
||||
# Is an instance, not a class.
|
||||
tree_context, = tree_context.execute_evaluated()
|
||||
|
||||
return MixedObject(
|
||||
evaluator,
|
||||
parent_context,
|
||||
compiled_object,
|
||||
tree_context=tree_context
|
||||
)
|
||||
@@ -1,55 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def _get_paths():
|
||||
# Get the path to jedi.
|
||||
_d = os.path.dirname
|
||||
_jedi_path = _d(_d(_d(_d(_d(__file__)))))
|
||||
_parso_path = sys.argv[1]
|
||||
# The paths are the directory that jedi and parso lie in.
|
||||
return {'jedi': _jedi_path, 'parso': _parso_path}
|
||||
|
||||
|
||||
# Remove the first entry, because it's simply a directory entry that equals
|
||||
# this directory.
|
||||
del sys.path[0]
|
||||
|
||||
if sys.version_info > (3, 4):
|
||||
from importlib.machinery import PathFinder
|
||||
|
||||
class _ExactImporter(object):
|
||||
def __init__(self, path_dct):
|
||||
self._path_dct = path_dct
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
if path is None and fullname in self._path_dct:
|
||||
p = self._path_dct[fullname]
|
||||
loader = PathFinder.find_module(fullname, path=[p])
|
||||
return loader
|
||||
return None
|
||||
|
||||
# Try to import jedi/parso.
|
||||
sys.meta_path.insert(0, _ExactImporter(_get_paths()))
|
||||
from jedi.evaluate.compiled import subprocess # NOQA
|
||||
sys.meta_path.pop(0)
|
||||
else:
|
||||
import imp
|
||||
|
||||
def load(name):
|
||||
paths = list(_get_paths().values())
|
||||
fp, pathname, description = imp.find_module(name, paths)
|
||||
return imp.load_module(name, fp, pathname, description)
|
||||
|
||||
load('parso')
|
||||
load('jedi')
|
||||
from jedi.evaluate.compiled import subprocess # NOQA
|
||||
|
||||
from jedi._compatibility import highest_pickle_protocol # noqa: E402
|
||||
|
||||
|
||||
# Retrieve the pickle protocol.
|
||||
host_sys_version = [int(x) for x in sys.argv[2].split('.')]
|
||||
pickle_protocol = highest_pickle_protocol([sys.version_info, host_sys_version])
|
||||
# And finally start the client.
|
||||
subprocess.Listener(pickle_protocol=pickle_protocol).listen()
|
||||
@@ -1,113 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
from jedi._compatibility import find_module, cast_path, force_unicode, \
|
||||
iter_modules, all_suffixes, print_to_stderr
|
||||
from jedi.evaluate.compiled import access
|
||||
from jedi import parser_utils
|
||||
|
||||
|
||||
def get_sys_path():
|
||||
return list(map(cast_path, sys.path))
|
||||
|
||||
|
||||
def load_module(evaluator, **kwargs):
|
||||
return access.load_module(evaluator, **kwargs)
|
||||
|
||||
|
||||
def get_compiled_method_return(evaluator, id, attribute, *args, **kwargs):
|
||||
handle = evaluator.compiled_subprocess.get_access_handle(id)
|
||||
return getattr(handle.access, attribute)(*args, **kwargs)
|
||||
|
||||
|
||||
def get_special_object(evaluator, identifier):
|
||||
return access.get_special_object(evaluator, identifier)
|
||||
|
||||
|
||||
def create_simple_object(evaluator, obj):
|
||||
return access.create_access_path(evaluator, obj)
|
||||
|
||||
|
||||
def get_module_info(evaluator, sys_path=None, full_name=None, **kwargs):
|
||||
if sys_path is not None:
|
||||
sys.path, temp = sys_path, sys.path
|
||||
try:
|
||||
module_file, module_path, is_pkg = find_module(full_name=full_name, **kwargs)
|
||||
except ImportError:
|
||||
return None, None, None
|
||||
finally:
|
||||
if sys_path is not None:
|
||||
sys.path = temp
|
||||
|
||||
code = None
|
||||
if is_pkg:
|
||||
# In this case, we don't have a file yet. Search for the
|
||||
# __init__ file.
|
||||
if module_path.endswith(('.zip', '.egg')):
|
||||
code = module_file.loader.get_source(full_name)
|
||||
else:
|
||||
module_path = _get_init_path(module_path)
|
||||
elif module_file:
|
||||
if module_path.endswith(('.zip', '.egg')):
|
||||
# Unfortunately we are reading unicode here already, not byes.
|
||||
# It seems however hard to get bytes, because the zip importer
|
||||
# logic just unpacks the zip file and returns a file descriptor
|
||||
# that we cannot as easily access. Therefore we just read it as
|
||||
# a string.
|
||||
code = module_file.read()
|
||||
else:
|
||||
# Read the code with a binary file, because the binary file
|
||||
# might not be proper unicode. This is handled by the parser
|
||||
# wrapper.
|
||||
with open(module_path, 'rb') as f:
|
||||
code = f.read()
|
||||
|
||||
module_file.close()
|
||||
|
||||
return code, cast_path(module_path), is_pkg
|
||||
|
||||
|
||||
def list_module_names(evaluator, search_path):
|
||||
return [
|
||||
force_unicode(name)
|
||||
for module_loader, name, is_pkg in iter_modules(search_path)
|
||||
]
|
||||
|
||||
|
||||
def get_builtin_module_names(evaluator):
|
||||
return list(map(force_unicode, sys.builtin_module_names))
|
||||
|
||||
|
||||
def _test_raise_error(evaluator, exception_type):
|
||||
"""
|
||||
Raise an error to simulate certain problems for unit tests.
|
||||
"""
|
||||
raise exception_type
|
||||
|
||||
|
||||
def _test_print(evaluator, stderr=None, stdout=None):
|
||||
"""
|
||||
Force some prints in the subprocesses. This exists for unit tests.
|
||||
"""
|
||||
if stderr is not None:
|
||||
print_to_stderr(stderr)
|
||||
sys.stderr.flush()
|
||||
if stdout is not None:
|
||||
print(stdout)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def _get_init_path(directory_path):
|
||||
"""
|
||||
The __init__ file can be searched in a directory. If found return it, else
|
||||
None.
|
||||
"""
|
||||
for suffix in all_suffixes():
|
||||
path = os.path.join(directory_path, '__init__' + suffix)
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
return None
|
||||
|
||||
|
||||
def safe_literal_eval(evaluator, value):
|
||||
return parser_utils.safe_literal_eval(value)
|
||||
@@ -1,5 +0,0 @@
|
||||
from jedi.evaluate.context.module import ModuleContext
|
||||
from jedi.evaluate.context.klass import ClassContext
|
||||
from jedi.evaluate.context.function import FunctionContext, FunctionExecutionContext
|
||||
from jedi.evaluate.context.instance import AnonymousInstance, BoundMethod, \
|
||||
CompiledInstance, AbstractInstanceContext, TreeInstance
|
||||
@@ -1,38 +0,0 @@
|
||||
from jedi.evaluate.filters import publish_method, BuiltinOverwrite
|
||||
from jedi.evaluate.base_context import ContextSet
|
||||
|
||||
|
||||
class AsyncBase(BuiltinOverwrite):
|
||||
def __init__(self, evaluator, func_execution_context):
|
||||
super(AsyncBase, self).__init__(evaluator)
|
||||
self.func_execution_context = func_execution_context
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.get_object().name
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (type(self).__name__, self.func_execution_context)
|
||||
|
||||
|
||||
class Coroutine(AsyncBase):
|
||||
special_object_identifier = u'COROUTINE'
|
||||
|
||||
@publish_method('__await__')
|
||||
def _await(self):
|
||||
return ContextSet(CoroutineWrapper(self.evaluator, self.func_execution_context))
|
||||
|
||||
|
||||
class CoroutineWrapper(AsyncBase):
|
||||
special_object_identifier = u'COROUTINE_WRAPPER'
|
||||
|
||||
def py__stop_iteration_returns(self):
|
||||
return self.func_execution_context.get_return_values()
|
||||
|
||||
|
||||
class AsyncGenerator(AsyncBase):
|
||||
"""Handling of `yield` functions."""
|
||||
special_object_identifier = u'ASYNC_GENERATOR'
|
||||
|
||||
def py__aiter__(self):
|
||||
return self.func_execution_context.get_yield_lazy_contexts(is_async=True)
|
||||
@@ -1,253 +0,0 @@
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import use_metaclass
|
||||
from jedi import debug
|
||||
from jedi.evaluate.cache import evaluator_method_cache, CachedMetaClass
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import recursion
|
||||
from jedi.evaluate import docstrings
|
||||
from jedi.evaluate import pep0484
|
||||
from jedi.evaluate import flow_analysis
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.arguments import AnonymousArguments
|
||||
from jedi.evaluate.filters import ParserTreeFilter, FunctionExecutionFilter, \
|
||||
ContextName, AbstractNameDefinition, ParamName
|
||||
from jedi.evaluate.base_context import ContextualizedNode, NO_CONTEXTS, \
|
||||
ContextSet, TreeContext
|
||||
from jedi.evaluate.lazy_context import LazyKnownContexts, LazyKnownContext, \
|
||||
LazyTreeContext
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.context import asynchronous
|
||||
from jedi import parser_utils
|
||||
from jedi.evaluate.parser_cache import get_yield_exprs
|
||||
|
||||
|
||||
class LambdaName(AbstractNameDefinition):
|
||||
string_name = '<lambda>'
|
||||
api_type = u'function'
|
||||
|
||||
def __init__(self, lambda_context):
|
||||
self._lambda_context = lambda_context
|
||||
self.parent_context = lambda_context.parent_context
|
||||
|
||||
@property
|
||||
def start_pos(self):
|
||||
return self._lambda_context.tree_node.start_pos
|
||||
|
||||
def infer(self):
|
||||
return ContextSet(self._lambda_context)
|
||||
|
||||
|
||||
class AbstractFunction(TreeContext):
|
||||
api_type = u'function'
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
if search_global:
|
||||
yield ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
else:
|
||||
scope = self.py__class__()
|
||||
for filter in scope.get_filters(search_global=False, origin_scope=origin_scope):
|
||||
yield filter
|
||||
|
||||
def get_param_names(self):
|
||||
function_execution = self.get_function_execution()
|
||||
return [ParamName(function_execution, param.name)
|
||||
for param in self.tree_node.get_params()]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
if self.tree_node.type == 'lambdef':
|
||||
return LambdaName(self)
|
||||
return ContextName(self, self.tree_node.name)
|
||||
|
||||
def get_function_execution(self, arguments=None):
|
||||
raise NotImplementedError
|
||||
|
||||
def py__call__(self, arguments):
|
||||
function_execution = self.get_function_execution(arguments)
|
||||
return self.infer_function_execution(function_execution)
|
||||
|
||||
def infer_function_execution(self, function_execution):
|
||||
"""
|
||||
Created to be used by inheritance.
|
||||
"""
|
||||
is_coroutine = self.tree_node.parent.type == 'async_stmt'
|
||||
is_generator = bool(get_yield_exprs(self.evaluator, self.tree_node))
|
||||
|
||||
if is_coroutine:
|
||||
if is_generator:
|
||||
if self.evaluator.environment.version_info < (3, 6):
|
||||
return NO_CONTEXTS
|
||||
return ContextSet(asynchronous.AsyncGenerator(self.evaluator, function_execution))
|
||||
else:
|
||||
if self.evaluator.environment.version_info < (3, 5):
|
||||
return NO_CONTEXTS
|
||||
return ContextSet(asynchronous.Coroutine(self.evaluator, function_execution))
|
||||
else:
|
||||
if is_generator:
|
||||
return ContextSet(iterable.Generator(self.evaluator, function_execution))
|
||||
else:
|
||||
return function_execution.get_return_values()
|
||||
|
||||
def py__name__(self):
|
||||
return self.name.string_name
|
||||
|
||||
|
||||
class FunctionContext(use_metaclass(CachedMetaClass, AbstractFunction)):
|
||||
"""
|
||||
Needed because of decorators. Decorators are evaluated here.
|
||||
"""
|
||||
@classmethod
|
||||
def from_context(cls, context, tree_node):
|
||||
from jedi.evaluate.context import AbstractInstanceContext
|
||||
|
||||
while context.is_class() or isinstance(context, AbstractInstanceContext):
|
||||
context = context.parent_context
|
||||
|
||||
return cls(context.evaluator, parent_context=context, tree_node=tree_node)
|
||||
|
||||
def get_function_execution(self, arguments=None):
|
||||
if arguments is None:
|
||||
arguments = AnonymousArguments()
|
||||
|
||||
return FunctionExecutionContext(self.evaluator, self.parent_context, self, arguments)
|
||||
|
||||
def py__class__(self):
|
||||
return compiled.get_special_object(self.evaluator, u'FUNCTION_CLASS')
|
||||
|
||||
|
||||
class FunctionExecutionContext(TreeContext):
|
||||
"""
|
||||
This class is used to evaluate functions and their returns.
|
||||
|
||||
This is the most complicated class, because it contains the logic to
|
||||
transfer parameters. It is even more complicated, because there may be
|
||||
multiple calls to functions and recursion has to be avoided. But this is
|
||||
responsibility of the decorators.
|
||||
"""
|
||||
function_execution_filter = FunctionExecutionFilter
|
||||
|
||||
def __init__(self, evaluator, parent_context, function_context, var_args):
|
||||
super(FunctionExecutionContext, self).__init__(
|
||||
evaluator,
|
||||
parent_context,
|
||||
function_context.tree_node,
|
||||
)
|
||||
self.function_context = function_context
|
||||
self.var_args = var_args
|
||||
|
||||
@evaluator_method_cache(default=NO_CONTEXTS)
|
||||
@recursion.execution_recursion_decorator()
|
||||
def get_return_values(self, check_yields=False):
|
||||
funcdef = self.tree_node
|
||||
if funcdef.type == 'lambdef':
|
||||
return self.eval_node(funcdef.children[-1])
|
||||
|
||||
if check_yields:
|
||||
context_set = NO_CONTEXTS
|
||||
returns = get_yield_exprs(self.evaluator, funcdef)
|
||||
else:
|
||||
returns = funcdef.iter_return_stmts()
|
||||
context_set = docstrings.infer_return_types(self.function_context)
|
||||
context_set |= pep0484.infer_return_types(self.function_context)
|
||||
|
||||
for r in returns:
|
||||
check = flow_analysis.reachability_check(self, funcdef, r)
|
||||
if check is flow_analysis.UNREACHABLE:
|
||||
debug.dbg('Return unreachable: %s', r)
|
||||
else:
|
||||
if check_yields:
|
||||
context_set |= ContextSet.from_sets(
|
||||
lazy_context.infer()
|
||||
for lazy_context in self._get_yield_lazy_context(r)
|
||||
)
|
||||
else:
|
||||
try:
|
||||
children = r.children
|
||||
except AttributeError:
|
||||
ctx = compiled.builtin_from_name(self.evaluator, u'None')
|
||||
context_set |= ContextSet(ctx)
|
||||
else:
|
||||
context_set |= self.eval_node(children[1])
|
||||
if check is flow_analysis.REACHABLE:
|
||||
debug.dbg('Return reachable: %s', r)
|
||||
break
|
||||
return context_set
|
||||
|
||||
def _get_yield_lazy_context(self, yield_expr):
|
||||
if yield_expr.type == 'keyword':
|
||||
# `yield` just yields None.
|
||||
ctx = compiled.builtin_from_name(self.evaluator, u'None')
|
||||
yield LazyKnownContext(ctx)
|
||||
return
|
||||
|
||||
node = yield_expr.children[1]
|
||||
if node.type == 'yield_arg': # It must be a yield from.
|
||||
cn = ContextualizedNode(self, node.children[1])
|
||||
for lazy_context in cn.infer().iterate(cn):
|
||||
yield lazy_context
|
||||
else:
|
||||
yield LazyTreeContext(self, node)
|
||||
|
||||
@recursion.execution_recursion_decorator(default=iter([]))
|
||||
def get_yield_lazy_contexts(self, is_async=False):
|
||||
# TODO: if is_async, wrap yield statements in Awaitable/async_generator_asend
|
||||
for_parents = [(y, tree.search_ancestor(y, 'for_stmt', 'funcdef',
|
||||
'while_stmt', 'if_stmt'))
|
||||
for y in get_yield_exprs(self.evaluator, self.tree_node)]
|
||||
|
||||
# Calculate if the yields are placed within the same for loop.
|
||||
yields_order = []
|
||||
last_for_stmt = None
|
||||
for yield_, for_stmt in for_parents:
|
||||
# For really simple for loops we can predict the order. Otherwise
|
||||
# we just ignore it.
|
||||
parent = for_stmt.parent
|
||||
if parent.type == 'suite':
|
||||
parent = parent.parent
|
||||
if for_stmt.type == 'for_stmt' and parent == self.tree_node \
|
||||
and parser_utils.for_stmt_defines_one_name(for_stmt): # Simplicity for now.
|
||||
if for_stmt == last_for_stmt:
|
||||
yields_order[-1][1].append(yield_)
|
||||
else:
|
||||
yields_order.append((for_stmt, [yield_]))
|
||||
elif for_stmt == self.tree_node:
|
||||
yields_order.append((None, [yield_]))
|
||||
else:
|
||||
types = self.get_return_values(check_yields=True)
|
||||
if types:
|
||||
yield LazyKnownContexts(types)
|
||||
return
|
||||
last_for_stmt = for_stmt
|
||||
|
||||
for for_stmt, yields in yields_order:
|
||||
if for_stmt is None:
|
||||
# No for_stmt, just normal yields.
|
||||
for yield_ in yields:
|
||||
for result in self._get_yield_lazy_context(yield_):
|
||||
yield result
|
||||
else:
|
||||
input_node = for_stmt.get_testlist()
|
||||
cn = ContextualizedNode(self, input_node)
|
||||
ordered = cn.infer().iterate(cn)
|
||||
ordered = list(ordered)
|
||||
for lazy_context in ordered:
|
||||
dct = {str(for_stmt.children[1].value): lazy_context.infer()}
|
||||
with helpers.predefine_names(self, for_stmt, dct):
|
||||
for yield_in_same_for_stmt in yields:
|
||||
for result in self._get_yield_lazy_context(yield_in_same_for_stmt):
|
||||
yield result
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield self.function_execution_filter(self.evaluator, self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope)
|
||||
|
||||
@evaluator_method_cache()
|
||||
def get_executed_params(self):
|
||||
return self.var_args.get_executed_params(self)
|
||||
@@ -1,483 +0,0 @@
|
||||
from abc import abstractproperty
|
||||
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import filters
|
||||
from jedi.evaluate.base_context import Context, NO_CONTEXTS, ContextSet, \
|
||||
iterator_to_context_set
|
||||
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.arguments import AbstractArguments, AnonymousArguments
|
||||
from jedi.evaluate.context.function import FunctionExecutionContext, \
|
||||
FunctionContext, AbstractFunction
|
||||
from jedi.evaluate.context.klass import ClassContext, apply_py__get__, ClassFilter
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
class InstanceExecutedParam(object):
|
||||
def __init__(self, instance):
|
||||
self._instance = instance
|
||||
|
||||
def infer(self):
|
||||
return ContextSet(self._instance)
|
||||
|
||||
|
||||
class AnonymousInstanceArguments(AnonymousArguments):
|
||||
def __init__(self, instance):
|
||||
self._instance = instance
|
||||
|
||||
def get_executed_params(self, execution_context):
|
||||
from jedi.evaluate.dynamic import search_params
|
||||
self_param = InstanceExecutedParam(self._instance)
|
||||
tree_params = execution_context.tree_node.get_params()
|
||||
if len(tree_params) == 1:
|
||||
# If the only param is self, we don't need to try to find
|
||||
# executions of this function, we have all the params already.
|
||||
return [self_param]
|
||||
executed_params = list(search_params(
|
||||
execution_context.evaluator,
|
||||
execution_context,
|
||||
execution_context.tree_node
|
||||
))
|
||||
executed_params[0] = self_param
|
||||
return executed_params
|
||||
|
||||
|
||||
class AbstractInstanceContext(Context):
|
||||
"""
|
||||
This class is used to evaluate instances.
|
||||
"""
|
||||
api_type = u'instance'
|
||||
|
||||
def __init__(self, evaluator, parent_context, class_context, var_args):
|
||||
super(AbstractInstanceContext, self).__init__(evaluator, parent_context)
|
||||
# Generated instances are classes that are just generated by self
|
||||
# (No var_args) used.
|
||||
self.class_context = class_context
|
||||
self.var_args = var_args
|
||||
|
||||
def is_class(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def py__call__(self):
|
||||
names = self.get_function_slot_names(u'__call__')
|
||||
if not names:
|
||||
# Means the Instance is not callable.
|
||||
raise AttributeError
|
||||
|
||||
def execute(arguments):
|
||||
return ContextSet.from_sets(name.infer().execute(arguments) for name in names)
|
||||
|
||||
return execute
|
||||
|
||||
def py__class__(self):
|
||||
return self.class_context
|
||||
|
||||
def py__bool__(self):
|
||||
# Signalize that we don't know about the bool type.
|
||||
return None
|
||||
|
||||
def get_function_slot_names(self, name):
|
||||
# Python classes don't look at the dictionary of the instance when
|
||||
# looking up `__call__`. This is something that has to do with Python's
|
||||
# internal slot system (note: not __slots__, but C slots).
|
||||
for filter in self.get_filters(include_self_names=False):
|
||||
names = filter.get(name)
|
||||
if names:
|
||||
return names
|
||||
return []
|
||||
|
||||
def execute_function_slots(self, names, *evaluated_args):
|
||||
return ContextSet.from_sets(
|
||||
name.infer().execute_evaluated(*evaluated_args)
|
||||
for name in names
|
||||
)
|
||||
|
||||
def py__get__(self, obj):
|
||||
# Arguments in __get__ descriptors are obj, class.
|
||||
# `method` is the new parent of the array, don't know if that's good.
|
||||
names = self.get_function_slot_names(u'__get__')
|
||||
if names:
|
||||
if isinstance(obj, AbstractInstanceContext):
|
||||
return self.execute_function_slots(names, obj, obj.class_context)
|
||||
else:
|
||||
none_obj = compiled.builtin_from_name(self.evaluator, u'None')
|
||||
return self.execute_function_slots(names, none_obj, obj)
|
||||
else:
|
||||
return ContextSet(self)
|
||||
|
||||
def get_filters(self, search_global=None, until_position=None,
|
||||
origin_scope=None, include_self_names=True):
|
||||
if include_self_names:
|
||||
for cls in self.class_context.py__mro__():
|
||||
if not isinstance(cls, compiled.CompiledObject) \
|
||||
or cls.tree_node is not None:
|
||||
# In this case we're excluding compiled objects that are
|
||||
# not fake objects. It doesn't make sense for normal
|
||||
# compiled objects to search for self variables.
|
||||
yield SelfAttributeFilter(self.evaluator, self, cls, origin_scope)
|
||||
|
||||
for cls in self.class_context.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
yield CompiledInstanceClassFilter(self.evaluator, self, cls)
|
||||
else:
|
||||
yield InstanceClassFilter(self.evaluator, self, cls, origin_scope)
|
||||
|
||||
def py__getitem__(self, index):
|
||||
try:
|
||||
names = self.get_function_slot_names(u'__getitem__')
|
||||
except KeyError:
|
||||
debug.warning('No __getitem__, cannot access the array.')
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
index_obj = compiled.create_simple_object(self.evaluator, index)
|
||||
return self.execute_function_slots(names, index_obj)
|
||||
|
||||
def py__iter__(self):
|
||||
iter_slot_names = self.get_function_slot_names(u'__iter__')
|
||||
if not iter_slot_names:
|
||||
debug.warning('No __iter__ on %s.' % self)
|
||||
return
|
||||
|
||||
for generator in self.execute_function_slots(iter_slot_names):
|
||||
if isinstance(generator, AbstractInstanceContext):
|
||||
# `__next__` logic.
|
||||
if self.evaluator.environment.version_info.major == 2:
|
||||
name = u'next'
|
||||
else:
|
||||
name = u'__next__'
|
||||
iter_slot_names = generator.get_function_slot_names(name)
|
||||
if iter_slot_names:
|
||||
yield LazyKnownContexts(
|
||||
generator.execute_function_slots(iter_slot_names)
|
||||
)
|
||||
else:
|
||||
debug.warning('Instance has no __next__ function in %s.', generator)
|
||||
else:
|
||||
for lazy_context in generator.py__iter__():
|
||||
yield lazy_context
|
||||
|
||||
@abstractproperty
|
||||
def name(self):
|
||||
pass
|
||||
|
||||
def _create_init_execution(self, class_context, bound_method):
|
||||
return bound_method.get_function_execution(self.var_args)
|
||||
|
||||
def create_init_executions(self):
|
||||
for name in self.get_function_slot_names(u'__init__'):
|
||||
if isinstance(name, LazyInstanceClassName):
|
||||
function = FunctionContext.from_context(
|
||||
self.parent_context,
|
||||
name.tree_name.parent
|
||||
)
|
||||
bound_method = BoundMethod(self, name.class_context, function)
|
||||
yield self._create_init_execution(name.class_context, bound_method)
|
||||
|
||||
@evaluator_method_cache()
|
||||
def create_instance_context(self, class_context, node):
|
||||
if node.parent.type in ('funcdef', 'classdef'):
|
||||
node = node.parent
|
||||
scope = get_parent_scope(node)
|
||||
if scope == class_context.tree_node:
|
||||
return class_context
|
||||
else:
|
||||
parent_context = self.create_instance_context(class_context, scope)
|
||||
if scope.type == 'funcdef':
|
||||
func = FunctionContext.from_context(
|
||||
parent_context,
|
||||
scope,
|
||||
)
|
||||
bound_method = BoundMethod(self, class_context, func)
|
||||
if scope.name.value == '__init__' and parent_context == class_context:
|
||||
return self._create_init_execution(class_context, bound_method)
|
||||
else:
|
||||
return bound_method.get_function_execution()
|
||||
elif scope.type == 'classdef':
|
||||
class_context = ClassContext(self.evaluator, parent_context, scope)
|
||||
return class_context
|
||||
elif scope.type == 'comp_for':
|
||||
# Comprehensions currently don't have a special scope in Jedi.
|
||||
return self.create_instance_context(class_context, scope)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
return class_context
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s(%s)>" % (self.__class__.__name__, self.class_context,
|
||||
self.var_args)
|
||||
|
||||
|
||||
class CompiledInstance(AbstractInstanceContext):
|
||||
def __init__(self, evaluator, parent_context, class_context, var_args):
|
||||
self._original_var_args = var_args
|
||||
|
||||
# I don't think that dynamic append lookups should happen here. That
|
||||
# sounds more like something that should go to py__iter__.
|
||||
if class_context.py__name__() in ['list', 'set'] \
|
||||
and parent_context.get_root_context() == evaluator.builtins_module:
|
||||
# compare the module path with the builtin name.
|
||||
if settings.dynamic_array_additions:
|
||||
var_args = iterable.get_dynamic_array_instance(self, var_args)
|
||||
|
||||
super(CompiledInstance, self).__init__(evaluator, parent_context, class_context, var_args)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return compiled.CompiledContextName(self, self.class_context.name.string_name)
|
||||
|
||||
def create_instance_context(self, class_context, node):
|
||||
if get_parent_scope(node).type == 'classdef':
|
||||
return class_context
|
||||
else:
|
||||
return super(CompiledInstance, self).create_instance_context(class_context, node)
|
||||
|
||||
def get_first_non_keyword_argument_contexts(self):
|
||||
key, lazy_context = next(self._original_var_args.unpack(), ('', None))
|
||||
if key is not None:
|
||||
return NO_CONTEXTS
|
||||
|
||||
return lazy_context.infer()
|
||||
|
||||
|
||||
class TreeInstance(AbstractInstanceContext):
|
||||
def __init__(self, evaluator, parent_context, class_context, var_args):
|
||||
super(TreeInstance, self).__init__(evaluator, parent_context,
|
||||
class_context, var_args)
|
||||
self.tree_node = class_context.tree_node
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return filters.ContextName(self, self.class_context.name.tree_name)
|
||||
|
||||
|
||||
class AnonymousInstance(TreeInstance):
|
||||
def __init__(self, evaluator, parent_context, class_context):
|
||||
super(AnonymousInstance, self).__init__(
|
||||
evaluator,
|
||||
parent_context,
|
||||
class_context,
|
||||
var_args=AnonymousInstanceArguments(self),
|
||||
)
|
||||
|
||||
|
||||
class CompiledInstanceName(compiled.CompiledName):
|
||||
|
||||
def __init__(self, evaluator, instance, klass, name):
|
||||
super(CompiledInstanceName, self).__init__(
|
||||
evaluator,
|
||||
klass.parent_context,
|
||||
name.string_name
|
||||
)
|
||||
self._instance = instance
|
||||
self._class = klass
|
||||
self._class_member_name = name
|
||||
|
||||
@iterator_to_context_set
|
||||
def infer(self):
|
||||
for result_context in self._class_member_name.infer():
|
||||
is_function = result_context.api_type == 'function'
|
||||
if result_context.tree_node is not None and is_function:
|
||||
yield BoundMethod(self._instance, self._class, result_context)
|
||||
else:
|
||||
if is_function:
|
||||
yield CompiledBoundMethod(result_context)
|
||||
else:
|
||||
yield result_context
|
||||
|
||||
|
||||
class CompiledInstanceClassFilter(filters.AbstractFilter):
|
||||
name_class = CompiledInstanceName
|
||||
|
||||
def __init__(self, evaluator, instance, klass):
|
||||
self._evaluator = evaluator
|
||||
self._instance = instance
|
||||
self._class = klass
|
||||
self._class_filter = next(klass.get_filters(is_instance=True))
|
||||
|
||||
def get(self, name):
|
||||
return self._convert(self._class_filter.get(name))
|
||||
|
||||
def values(self):
|
||||
return self._convert(self._class_filter.values())
|
||||
|
||||
def _convert(self, names):
|
||||
return [
|
||||
CompiledInstanceName(self._evaluator, self._instance, self._class, n)
|
||||
for n in names
|
||||
]
|
||||
|
||||
|
||||
class BoundMethod(AbstractFunction):
|
||||
def __init__(self, instance, klass, function):
|
||||
super(BoundMethod, self).__init__(
|
||||
function.evaluator,
|
||||
function.parent_context,
|
||||
function.tree_node,
|
||||
)
|
||||
self._instance = instance
|
||||
self._class = klass
|
||||
self._function = function
|
||||
|
||||
def py__class__(self):
|
||||
return compiled.get_special_object(self.evaluator, u'BOUND_METHOD_CLASS')
|
||||
|
||||
def get_function_execution(self, arguments=None):
|
||||
if arguments is None:
|
||||
arguments = AnonymousInstanceArguments(self._instance)
|
||||
|
||||
arguments = InstanceArguments(self._instance, arguments)
|
||||
|
||||
if isinstance(self._function, 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.
|
||||
return FunctionExecutionContext(
|
||||
self.evaluator, self.parent_context, self, arguments
|
||||
)
|
||||
|
||||
return self._function.get_function_execution(arguments)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self._function)
|
||||
|
||||
|
||||
class CompiledBoundMethod(compiled.CompiledObject):
|
||||
def __init__(self, func):
|
||||
super(CompiledBoundMethod, self).__init__(
|
||||
func.evaluator, func.access_handle, func.parent_context, func.tree_node)
|
||||
|
||||
def get_param_names(self):
|
||||
return list(super(CompiledBoundMethod, self).get_param_names())[1:]
|
||||
|
||||
|
||||
class SelfName(filters.TreeNameDefinition):
|
||||
"""
|
||||
This name calculates the parent_context lazily.
|
||||
"""
|
||||
def __init__(self, instance, class_context, tree_name):
|
||||
self._instance = instance
|
||||
self.class_context = class_context
|
||||
self.tree_name = tree_name
|
||||
|
||||
@property
|
||||
def parent_context(self):
|
||||
return self._instance.create_instance_context(self.class_context, self.tree_name)
|
||||
|
||||
|
||||
class LazyInstanceClassName(object):
|
||||
def __init__(self, instance, class_context, class_member_name):
|
||||
self._instance = instance
|
||||
self.class_context = class_context
|
||||
self._class_member_name = class_member_name
|
||||
|
||||
@iterator_to_context_set
|
||||
def infer(self):
|
||||
for result_context in self._class_member_name.infer():
|
||||
if isinstance(result_context, FunctionContext):
|
||||
# Classes are never used to resolve anything within the
|
||||
# functions. Only other functions and modules will resolve
|
||||
# those things.
|
||||
yield BoundMethod(self._instance, self.class_context, result_context)
|
||||
else:
|
||||
for c in apply_py__get__(result_context, self._instance):
|
||||
yield c
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._class_member_name, name)
|
||||
|
||||
|
||||
class InstanceClassFilter(filters.AbstractFilter):
|
||||
"""
|
||||
This filter is special in that it uses the class filter and wraps the
|
||||
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 get(self, name):
|
||||
return self._convert(self._class_filter.get(name))
|
||||
|
||||
def values(self):
|
||||
return self._convert(self._class_filter.values())
|
||||
|
||||
def _convert(self, names):
|
||||
return [LazyInstanceClassName(self._instance, self._class_context, n) for n in names]
|
||||
|
||||
|
||||
class SelfAttributeFilter(ClassFilter):
|
||||
"""
|
||||
This class basically filters all the use cases where `self.*` was assigned.
|
||||
"""
|
||||
name_class = SelfName
|
||||
|
||||
def __init__(self, evaluator, context, class_context, origin_scope):
|
||||
super(SelfAttributeFilter, self).__init__(
|
||||
evaluator=evaluator,
|
||||
context=context,
|
||||
node_context=class_context,
|
||||
origin_scope=origin_scope,
|
||||
is_instance=True,
|
||||
)
|
||||
self._class_context = class_context
|
||||
|
||||
def _filter(self, names):
|
||||
names = self._filter_self_names(names)
|
||||
if isinstance(self._parser_scope, compiled.CompiledObject) and False:
|
||||
# This would be for builtin skeletons, which are not yet supported.
|
||||
return list(names)
|
||||
else:
|
||||
start, end = self._parser_scope.start_pos, self._parser_scope.end_pos
|
||||
return [n for n in names if start < n.start_pos < end]
|
||||
|
||||
def _filter_self_names(self, names):
|
||||
for name in names:
|
||||
trailer = name.parent
|
||||
if trailer.type == 'trailer' \
|
||||
and len(trailer.children) == 2 \
|
||||
and trailer.children[0] == '.':
|
||||
if name.is_definition() and self._access_possible(name):
|
||||
yield name
|
||||
|
||||
def _convert_names(self, names):
|
||||
return [self.name_class(self.context, self._class_context, name) for name in names]
|
||||
|
||||
def _check_flows(self, names):
|
||||
return names
|
||||
|
||||
|
||||
class InstanceArguments(AbstractArguments):
|
||||
def __init__(self, instance, var_args):
|
||||
self.instance = instance
|
||||
self._var_args = var_args
|
||||
|
||||
@property
|
||||
def argument_node(self):
|
||||
return self._var_args.argument_node
|
||||
|
||||
@property
|
||||
def trailer(self):
|
||||
return self._var_args.trailer
|
||||
|
||||
def unpack(self, func=None):
|
||||
yield None, LazyKnownContext(self.instance)
|
||||
for values in self._var_args.unpack(func):
|
||||
yield values
|
||||
|
||||
def get_calling_nodes(self):
|
||||
return self._var_args.get_calling_nodes()
|
||||
|
||||
def get_executed_params(self, execution_context):
|
||||
if isinstance(self._var_args, AnonymousInstanceArguments):
|
||||
return self._var_args.get_executed_params(execution_context)
|
||||
|
||||
return super(InstanceArguments, self).get_executed_params(execution_context)
|
||||
@@ -1,732 +0,0 @@
|
||||
"""
|
||||
Contains all classes and functions to deal with lists, dicts, generators and
|
||||
iterators in general.
|
||||
|
||||
Array modifications
|
||||
*******************
|
||||
|
||||
If the content of an array (``set``/``list``) is requested somewhere, the
|
||||
current module will be checked for appearances of ``arr.append``,
|
||||
``arr.insert``, etc. If the ``arr`` name points to an actual array, the
|
||||
content will be added
|
||||
|
||||
This can be really cpu intensive, as you can imagine. Because |jedi| has to
|
||||
follow **every** ``append`` and check wheter it's the right array. However this
|
||||
works pretty good, because in *slow* cases, the recursion detector and other
|
||||
settings will stop this process.
|
||||
|
||||
It is important to note that:
|
||||
|
||||
1. Array modfications work only in the current module.
|
||||
2. Jedi only checks Array additions; ``list.pop``, etc are ignored.
|
||||
"""
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi._compatibility import force_unicode, is_py3
|
||||
from jedi.cache import memoize_method
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate import recursion
|
||||
from jedi.evaluate.lazy_context import LazyKnownContext, LazyKnownContexts, \
|
||||
LazyTreeContext
|
||||
from jedi.evaluate.helpers import get_int_or_none, is_string, \
|
||||
predefine_names, evaluate_call_of_leaf, reraise_as_evaluator, \
|
||||
EvaluatorKeyError
|
||||
from jedi.evaluate.utils import safe_property
|
||||
from jedi.evaluate.utils import to_list
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.filters import ParserTreeFilter, BuiltinOverwrite, \
|
||||
publish_method
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, Context, \
|
||||
TreeContext, ContextualizedNode
|
||||
from jedi.parser_utils import get_comp_fors
|
||||
|
||||
|
||||
class IterableMixin(object):
|
||||
def py__stop_iteration_returns(self):
|
||||
return ContextSet(compiled.builtin_from_name(self.evaluator, u'None'))
|
||||
|
||||
|
||||
class GeneratorBase(BuiltinOverwrite, IterableMixin):
|
||||
array_type = None
|
||||
special_object_identifier = u'GENERATOR_OBJECT'
|
||||
|
||||
@publish_method('send')
|
||||
@publish_method('next', python_version_match=2)
|
||||
@publish_method('__next__', python_version_match=3)
|
||||
def py__next__(self):
|
||||
return ContextSet.from_sets(lazy_context.infer() for lazy_context in self.py__iter__())
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return compiled.CompiledContextName(self, 'generator')
|
||||
|
||||
|
||||
class Generator(GeneratorBase):
|
||||
"""Handling of `yield` functions."""
|
||||
def __init__(self, evaluator, func_execution_context):
|
||||
super(Generator, self).__init__(evaluator)
|
||||
self._func_execution_context = func_execution_context
|
||||
|
||||
def py__iter__(self):
|
||||
return self._func_execution_context.get_yield_lazy_contexts()
|
||||
|
||||
def py__stop_iteration_returns(self):
|
||||
return self._func_execution_context.get_return_values()
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (type(self).__name__, self._func_execution_context)
|
||||
|
||||
|
||||
class CompForContext(TreeContext):
|
||||
@classmethod
|
||||
def from_comp_for(cls, parent_context, comp_for):
|
||||
return cls(parent_context.evaluator, parent_context, comp_for)
|
||||
|
||||
def get_node(self):
|
||||
return self.tree_node
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield ParserTreeFilter(self.evaluator, self)
|
||||
|
||||
|
||||
def comprehension_from_atom(evaluator, context, atom):
|
||||
bracket = atom.children[0]
|
||||
if bracket == '{':
|
||||
if atom.children[1].children[1] == ':':
|
||||
cls = DictComprehension
|
||||
else:
|
||||
cls = SetComprehension
|
||||
elif bracket == '(':
|
||||
cls = GeneratorComprehension
|
||||
elif bracket == '[':
|
||||
cls = ListComprehension
|
||||
return cls(evaluator, context, atom)
|
||||
|
||||
|
||||
class ComprehensionMixin(object):
|
||||
def __init__(self, evaluator, defining_context, atom):
|
||||
super(ComprehensionMixin, self).__init__(evaluator)
|
||||
self._defining_context = defining_context
|
||||
self._atom = atom
|
||||
|
||||
def _get_comprehension(self):
|
||||
"return 'a for a in b'"
|
||||
# The atom contains a testlist_comp
|
||||
return self._atom.children[1]
|
||||
|
||||
def _get_comp_for(self):
|
||||
"return CompFor('for a in b')"
|
||||
return self._get_comprehension().children[1]
|
||||
|
||||
def _eval_node(self, index=0):
|
||||
"""
|
||||
The first part `x + 1` of the list comprehension:
|
||||
|
||||
[x + 1 for x in foo]
|
||||
"""
|
||||
return self._get_comprehension().children[index]
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _get_comp_for_context(self, parent_context, comp_for):
|
||||
# TODO shouldn't this be part of create_context?
|
||||
return CompForContext.from_comp_for(parent_context, comp_for)
|
||||
|
||||
def _nested(self, comp_fors, parent_context=None):
|
||||
comp_for = comp_fors[0]
|
||||
|
||||
is_async = 'async' == comp_for.children[comp_for.children.index('for') - 1]
|
||||
|
||||
input_node = comp_for.children[comp_for.children.index('in') + 1]
|
||||
parent_context = parent_context or self._defining_context
|
||||
input_types = parent_context.eval_node(input_node)
|
||||
# TODO: simulate await if self.is_async
|
||||
|
||||
cn = ContextualizedNode(parent_context, input_node)
|
||||
iterated = input_types.iterate(cn, is_async=is_async)
|
||||
exprlist = comp_for.children[comp_for.children.index('for') + 1]
|
||||
for i, lazy_context in enumerate(iterated):
|
||||
types = lazy_context.infer()
|
||||
dct = unpack_tuple_to_dict(parent_context, types, exprlist)
|
||||
context_ = self._get_comp_for_context(
|
||||
parent_context,
|
||||
comp_for,
|
||||
)
|
||||
with predefine_names(context_, comp_for, dct):
|
||||
try:
|
||||
for result in self._nested(comp_fors[1:], context_):
|
||||
yield result
|
||||
except IndexError:
|
||||
iterated = context_.eval_node(self._eval_node())
|
||||
if self.array_type == 'dict':
|
||||
yield iterated, context_.eval_node(self._eval_node(2))
|
||||
else:
|
||||
yield iterated
|
||||
|
||||
@evaluator_method_cache(default=[])
|
||||
@to_list
|
||||
def _iterate(self):
|
||||
comp_fors = tuple(get_comp_fors(self._get_comp_for()))
|
||||
for result in self._nested(comp_fors):
|
||||
yield result
|
||||
|
||||
def py__iter__(self):
|
||||
for set_ in self._iterate():
|
||||
yield LazyKnownContexts(set_)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (type(self).__name__, self._atom)
|
||||
|
||||
|
||||
class Sequence(BuiltinOverwrite, IterableMixin):
|
||||
api_type = u'instance'
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return compiled.CompiledContextName(self, self.array_type)
|
||||
|
||||
@memoize_method
|
||||
def get_object(self):
|
||||
compiled_obj = compiled.builtin_from_name(self.evaluator, self.array_type)
|
||||
only_obj, = compiled_obj.execute_evaluated(self)
|
||||
return only_obj
|
||||
|
||||
def py__bool__(self):
|
||||
return None # We don't know the length, because of appends.
|
||||
|
||||
def py__class__(self):
|
||||
return compiled.builtin_from_name(self.evaluator, self.array_type)
|
||||
|
||||
@safe_property
|
||||
def parent(self):
|
||||
return self.evaluator.builtins_module
|
||||
|
||||
def dict_values(self):
|
||||
return ContextSet.from_sets(
|
||||
self._defining_context.eval_node(v)
|
||||
for k, v in self._items()
|
||||
)
|
||||
|
||||
|
||||
class ListComprehension(ComprehensionMixin, Sequence):
|
||||
array_type = u'list'
|
||||
|
||||
def py__getitem__(self, index):
|
||||
if isinstance(index, slice):
|
||||
return ContextSet(self)
|
||||
|
||||
all_types = list(self.py__iter__())
|
||||
with reraise_as_evaluator(IndexError, TypeError):
|
||||
lazy_context = all_types[index]
|
||||
return lazy_context.infer()
|
||||
|
||||
|
||||
class SetComprehension(ComprehensionMixin, Sequence):
|
||||
array_type = u'set'
|
||||
|
||||
|
||||
class DictComprehension(ComprehensionMixin, Sequence):
|
||||
array_type = u'dict'
|
||||
|
||||
def _get_comp_for(self):
|
||||
return self._get_comprehension().children[3]
|
||||
|
||||
def py__iter__(self):
|
||||
for keys, values in self._iterate():
|
||||
yield LazyKnownContexts(keys)
|
||||
|
||||
def py__getitem__(self, index):
|
||||
for keys, values in self._iterate():
|
||||
for k in keys:
|
||||
if isinstance(k, compiled.CompiledObject):
|
||||
if k.get_safe_value(default=object()) == index:
|
||||
return values
|
||||
return self.dict_values()
|
||||
|
||||
def dict_values(self):
|
||||
return ContextSet.from_sets(values for keys, values in self._iterate())
|
||||
|
||||
@publish_method('values')
|
||||
def _imitate_values(self):
|
||||
lazy_context = LazyKnownContexts(self.dict_values())
|
||||
return ContextSet(FakeSequence(self.evaluator, u'list', [lazy_context]))
|
||||
|
||||
@publish_method('items')
|
||||
def _imitate_items(self):
|
||||
lazy_contexts = [
|
||||
LazyKnownContext(
|
||||
FakeSequence(
|
||||
self.evaluator,
|
||||
u'tuple',
|
||||
[LazyKnownContexts(key),
|
||||
LazyKnownContexts(value)]
|
||||
)
|
||||
)
|
||||
for key, value in self._iterate()
|
||||
]
|
||||
|
||||
return ContextSet(FakeSequence(self.evaluator, u'list', lazy_contexts))
|
||||
|
||||
def exact_key_items(self):
|
||||
# NOTE: A smarter thing can probably done here to achieve better
|
||||
# completions, but at least like this jedi doesn't crash
|
||||
return []
|
||||
|
||||
|
||||
class GeneratorComprehension(ComprehensionMixin, GeneratorBase):
|
||||
pass
|
||||
|
||||
|
||||
class SequenceLiteralContext(Sequence):
|
||||
mapping = {'(': u'tuple',
|
||||
'[': u'list',
|
||||
'{': u'set'}
|
||||
|
||||
def __init__(self, evaluator, defining_context, atom):
|
||||
super(SequenceLiteralContext, self).__init__(evaluator)
|
||||
self.atom = atom
|
||||
self._defining_context = defining_context
|
||||
|
||||
if self.atom.type in ('testlist_star_expr', 'testlist'):
|
||||
self.array_type = u'tuple'
|
||||
else:
|
||||
self.array_type = SequenceLiteralContext.mapping[atom.children[0]]
|
||||
"""The builtin name of the array (list, set, tuple or dict)."""
|
||||
|
||||
def py__getitem__(self, index):
|
||||
"""Here the index is an int/str. Raises IndexError/KeyError."""
|
||||
if self.array_type == u'dict':
|
||||
compiled_obj_index = compiled.create_simple_object(self.evaluator, index)
|
||||
for key, value in self._items():
|
||||
for k in self._defining_context.eval_node(key):
|
||||
if isinstance(k, compiled.CompiledObject) \
|
||||
and k.execute_operation(compiled_obj_index, u'==').get_safe_value():
|
||||
return self._defining_context.eval_node(value)
|
||||
raise EvaluatorKeyError('No key found in dictionary %s.' % self)
|
||||
|
||||
# Can raise an IndexError
|
||||
if isinstance(index, slice):
|
||||
return ContextSet(self)
|
||||
else:
|
||||
with reraise_as_evaluator(TypeError, KeyError, IndexError):
|
||||
node = self._items()[index]
|
||||
return self._defining_context.eval_node(node)
|
||||
|
||||
def py__iter__(self):
|
||||
"""
|
||||
While values returns the possible values for any array field, this
|
||||
function returns the value for a certain index.
|
||||
"""
|
||||
if self.array_type == u'dict':
|
||||
# Get keys.
|
||||
types = ContextSet()
|
||||
for k, _ in self._items():
|
||||
types |= self._defining_context.eval_node(k)
|
||||
# We don't know which dict index comes first, therefore always
|
||||
# yield all the types.
|
||||
for _ in types:
|
||||
yield LazyKnownContexts(types)
|
||||
else:
|
||||
for node in self._items():
|
||||
yield LazyTreeContext(self._defining_context, node)
|
||||
|
||||
for addition in check_array_additions(self._defining_context, self):
|
||||
yield addition
|
||||
|
||||
def _values(self):
|
||||
"""Returns a list of a list of node."""
|
||||
if self.array_type == u'dict':
|
||||
return ContextSet.from_sets(v for k, v in self._items())
|
||||
else:
|
||||
return self._items()
|
||||
|
||||
def _items(self):
|
||||
c = self.atom.children
|
||||
|
||||
if self.atom.type in ('testlist_star_expr', 'testlist'):
|
||||
return c[::2]
|
||||
|
||||
array_node = c[1]
|
||||
if array_node in (']', '}', ')'):
|
||||
return [] # Direct closing bracket, doesn't contain items.
|
||||
|
||||
if array_node.type == 'testlist_comp':
|
||||
# filter out (for now) pep 448 single-star unpacking
|
||||
return [value for value in array_node.children[::2]
|
||||
if value.type != "star_expr"]
|
||||
elif array_node.type == 'dictorsetmaker':
|
||||
kv = []
|
||||
iterator = iter(array_node.children)
|
||||
for key in iterator:
|
||||
if key == "**":
|
||||
# dict with pep 448 double-star unpacking
|
||||
# for now ignoring the values imported by **
|
||||
next(iterator)
|
||||
next(iterator, None) # Possible comma.
|
||||
else:
|
||||
op = next(iterator, None)
|
||||
if op is None or op == ',':
|
||||
if key.type == "star_expr":
|
||||
# pep 448 single-star unpacking
|
||||
# for now ignoring values imported by *
|
||||
pass
|
||||
else:
|
||||
kv.append(key) # A set.
|
||||
else:
|
||||
assert op == ':' # A dict.
|
||||
kv.append((key, next(iterator)))
|
||||
next(iterator, None) # Possible comma.
|
||||
return kv
|
||||
else:
|
||||
if array_node.type == "star_expr":
|
||||
# pep 448 single-star unpacking
|
||||
# for now ignoring values imported by *
|
||||
return []
|
||||
else:
|
||||
return [array_node]
|
||||
|
||||
def exact_key_items(self):
|
||||
"""
|
||||
Returns a generator of tuples like dict.items(), where the key is
|
||||
resolved (as a string) and the values are still lazy contexts.
|
||||
"""
|
||||
for key_node, value in self._items():
|
||||
for key in self._defining_context.eval_node(key_node):
|
||||
if is_string(key):
|
||||
yield key.get_safe_value(), LazyTreeContext(self._defining_context, value)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (self.__class__.__name__, self.atom)
|
||||
|
||||
|
||||
class DictLiteralContext(SequenceLiteralContext):
|
||||
array_type = u'dict'
|
||||
|
||||
def __init__(self, evaluator, defining_context, atom):
|
||||
super(SequenceLiteralContext, self).__init__(evaluator)
|
||||
self._defining_context = defining_context
|
||||
self.atom = atom
|
||||
|
||||
@publish_method('values')
|
||||
def _imitate_values(self):
|
||||
lazy_context = LazyKnownContexts(self.dict_values())
|
||||
return ContextSet(FakeSequence(self.evaluator, u'list', [lazy_context]))
|
||||
|
||||
@publish_method('items')
|
||||
def _imitate_items(self):
|
||||
lazy_contexts = [
|
||||
LazyKnownContext(FakeSequence(
|
||||
self.evaluator, u'tuple',
|
||||
(LazyTreeContext(self._defining_context, key_node),
|
||||
LazyTreeContext(self._defining_context, value_node))
|
||||
)) for key_node, value_node in self._items()
|
||||
]
|
||||
|
||||
return ContextSet(FakeSequence(self.evaluator, u'list', lazy_contexts))
|
||||
|
||||
|
||||
class _FakeArray(SequenceLiteralContext):
|
||||
def __init__(self, evaluator, container, type):
|
||||
super(SequenceLiteralContext, self).__init__(evaluator)
|
||||
self.array_type = type
|
||||
self.atom = container
|
||||
# TODO is this class really needed?
|
||||
|
||||
|
||||
class FakeSequence(_FakeArray):
|
||||
def __init__(self, evaluator, array_type, lazy_context_list):
|
||||
"""
|
||||
type should be one of "tuple", "list"
|
||||
"""
|
||||
super(FakeSequence, self).__init__(evaluator, None, array_type)
|
||||
self._lazy_context_list = lazy_context_list
|
||||
|
||||
def py__getitem__(self, index):
|
||||
with reraise_as_evaluator(IndexError, TypeError):
|
||||
lazy_context = self._lazy_context_list[index]
|
||||
return lazy_context.infer()
|
||||
|
||||
def py__iter__(self):
|
||||
return self._lazy_context_list
|
||||
|
||||
def py__bool__(self):
|
||||
return bool(len(self._lazy_context_list))
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s of %s>" % (type(self).__name__, self._lazy_context_list)
|
||||
|
||||
|
||||
class FakeDict(_FakeArray):
|
||||
def __init__(self, evaluator, dct):
|
||||
super(FakeDict, self).__init__(evaluator, dct, u'dict')
|
||||
self._dct = dct
|
||||
|
||||
def py__iter__(self):
|
||||
for key in self._dct:
|
||||
yield LazyKnownContext(compiled.create_simple_object(self.evaluator, key))
|
||||
|
||||
def py__getitem__(self, index):
|
||||
if is_py3 and self.evaluator.environment.version_info.major == 2:
|
||||
# In Python 2 bytes and unicode compare.
|
||||
if isinstance(index, bytes):
|
||||
index_unicode = force_unicode(index)
|
||||
try:
|
||||
return self._dct[index_unicode].infer()
|
||||
except KeyError:
|
||||
pass
|
||||
elif isinstance(index, str):
|
||||
index_bytes = index.encode('utf-8')
|
||||
try:
|
||||
return self._dct[index_bytes].infer()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
with reraise_as_evaluator(KeyError):
|
||||
lazy_context = self._dct[index]
|
||||
return lazy_context.infer()
|
||||
|
||||
@publish_method('values')
|
||||
def _values(self):
|
||||
return ContextSet(FakeSequence(
|
||||
self.evaluator, u'tuple',
|
||||
[LazyKnownContexts(self.dict_values())]
|
||||
))
|
||||
|
||||
def dict_values(self):
|
||||
return ContextSet.from_sets(lazy_context.infer() for lazy_context in self._dct.values())
|
||||
|
||||
def exact_key_items(self):
|
||||
return self._dct.items()
|
||||
|
||||
|
||||
class MergedArray(_FakeArray):
|
||||
def __init__(self, evaluator, arrays):
|
||||
super(MergedArray, self).__init__(evaluator, arrays, arrays[-1].array_type)
|
||||
self._arrays = arrays
|
||||
|
||||
def py__iter__(self):
|
||||
for array in self._arrays:
|
||||
for lazy_context in array.py__iter__():
|
||||
yield lazy_context
|
||||
|
||||
def py__getitem__(self, index):
|
||||
return ContextSet.from_sets(lazy_context.infer() for lazy_context in self.py__iter__())
|
||||
|
||||
def _items(self):
|
||||
for array in self._arrays:
|
||||
for a in array._items():
|
||||
yield a
|
||||
|
||||
def __len__(self):
|
||||
return sum(len(a) for a in self._arrays)
|
||||
|
||||
|
||||
def unpack_tuple_to_dict(context, types, exprlist):
|
||||
"""
|
||||
Unpacking tuple assignments in for statements and expr_stmts.
|
||||
"""
|
||||
if exprlist.type == 'name':
|
||||
return {exprlist.value: types}
|
||||
elif exprlist.type == 'atom' and exprlist.children[0] in '([':
|
||||
return unpack_tuple_to_dict(context, types, exprlist.children[1])
|
||||
elif exprlist.type in ('testlist', 'testlist_comp', 'exprlist',
|
||||
'testlist_star_expr'):
|
||||
dct = {}
|
||||
parts = iter(exprlist.children[::2])
|
||||
n = 0
|
||||
for lazy_context in types.iterate(exprlist):
|
||||
n += 1
|
||||
try:
|
||||
part = next(parts)
|
||||
except StopIteration:
|
||||
# TODO this context is probably not right.
|
||||
analysis.add(context, 'value-error-too-many-values', part,
|
||||
message="ValueError: too many values to unpack (expected %s)" % n)
|
||||
else:
|
||||
dct.update(unpack_tuple_to_dict(context, lazy_context.infer(), part))
|
||||
has_parts = next(parts, None)
|
||||
if types and has_parts is not None:
|
||||
# TODO this context is probably not right.
|
||||
analysis.add(context, 'value-error-too-few-values', has_parts,
|
||||
message="ValueError: need more than %s values to unpack" % n)
|
||||
return dct
|
||||
elif exprlist.type == 'power' or exprlist.type == 'atom_expr':
|
||||
# Something like ``arr[x], var = ...``.
|
||||
# This is something that is not yet supported, would also be difficult
|
||||
# to write into a dict.
|
||||
return {}
|
||||
elif exprlist.type == 'star_expr': # `a, *b, c = x` type unpackings
|
||||
# Currently we're not supporting them.
|
||||
return {}
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def check_array_additions(context, sequence):
|
||||
""" Just a mapper function for the internal _check_array_additions """
|
||||
if sequence.array_type not in ('list', 'set'):
|
||||
# TODO also check for dict updates
|
||||
return NO_CONTEXTS
|
||||
|
||||
return _check_array_additions(context, sequence)
|
||||
|
||||
|
||||
@evaluator_method_cache(default=NO_CONTEXTS)
|
||||
@debug.increase_indent
|
||||
def _check_array_additions(context, sequence):
|
||||
"""
|
||||
Checks if a `Array` has "add" (append, insert, extend) statements:
|
||||
|
||||
>>> a = [""]
|
||||
>>> a.append(1)
|
||||
"""
|
||||
from jedi.evaluate import arguments
|
||||
|
||||
debug.dbg('Dynamic array search for %s' % sequence, color='MAGENTA')
|
||||
module_context = context.get_root_context()
|
||||
if not settings.dynamic_array_additions or isinstance(module_context, compiled.CompiledObject):
|
||||
debug.dbg('Dynamic array search aborted.', color='MAGENTA')
|
||||
return ContextSet()
|
||||
|
||||
def find_additions(context, arglist, add_name):
|
||||
params = list(arguments.TreeArguments(context.evaluator, context, arglist).unpack())
|
||||
result = set()
|
||||
if add_name in ['insert']:
|
||||
params = params[1:]
|
||||
if add_name in ['append', 'add', 'insert']:
|
||||
for key, whatever in params:
|
||||
result.add(whatever)
|
||||
elif add_name in ['extend', 'update']:
|
||||
for key, lazy_context in params:
|
||||
result |= set(lazy_context.infer().iterate())
|
||||
return result
|
||||
|
||||
temp_param_add, settings.dynamic_params_for_other_modules = \
|
||||
settings.dynamic_params_for_other_modules, False
|
||||
|
||||
is_list = sequence.name.string_name == 'list'
|
||||
search_names = (['append', 'extend', 'insert'] if is_list else ['add', 'update'])
|
||||
|
||||
added_types = set()
|
||||
for add_name in search_names:
|
||||
try:
|
||||
possible_names = module_context.tree_node.get_used_names()[add_name]
|
||||
except KeyError:
|
||||
continue
|
||||
else:
|
||||
for name in possible_names:
|
||||
context_node = context.tree_node
|
||||
if not (context_node.start_pos < name.start_pos < context_node.end_pos):
|
||||
continue
|
||||
trailer = name.parent
|
||||
power = trailer.parent
|
||||
trailer_pos = power.children.index(trailer)
|
||||
try:
|
||||
execution_trailer = power.children[trailer_pos + 1]
|
||||
except IndexError:
|
||||
continue
|
||||
else:
|
||||
if execution_trailer.type != 'trailer' \
|
||||
or execution_trailer.children[0] != '(' \
|
||||
or execution_trailer.children[1] == ')':
|
||||
continue
|
||||
|
||||
random_context = context.create_context(name)
|
||||
|
||||
with recursion.execution_allowed(context.evaluator, power) as allowed:
|
||||
if allowed:
|
||||
found = evaluate_call_of_leaf(
|
||||
random_context,
|
||||
name,
|
||||
cut_own_trailer=True
|
||||
)
|
||||
if sequence in found:
|
||||
# The arrays match. Now add the results
|
||||
added_types |= find_additions(
|
||||
random_context,
|
||||
execution_trailer.children[1],
|
||||
add_name
|
||||
)
|
||||
|
||||
# reset settings
|
||||
settings.dynamic_params_for_other_modules = temp_param_add
|
||||
debug.dbg('Dynamic array result %s' % added_types, color='MAGENTA')
|
||||
return added_types
|
||||
|
||||
|
||||
def get_dynamic_array_instance(instance, arguments):
|
||||
"""Used for set() and list() instances."""
|
||||
ai = _ArrayInstance(instance, arguments)
|
||||
from jedi.evaluate import arguments
|
||||
return arguments.ValuesArguments([ContextSet(ai)])
|
||||
|
||||
|
||||
class _ArrayInstance(object):
|
||||
"""
|
||||
Used for the usage of set() and list().
|
||||
This is definitely a hack, but a good one :-)
|
||||
It makes it possible to use set/list conversions.
|
||||
|
||||
In contrast to Array, ListComprehension and all other iterable types, this
|
||||
is something that is only used inside `evaluate/compiled/fake/builtins.py`
|
||||
and therefore doesn't need filters, `py__bool__` and so on, because
|
||||
we don't use these operations in `builtins.py`.
|
||||
"""
|
||||
def __init__(self, instance, var_args):
|
||||
self.instance = instance
|
||||
self.var_args = var_args
|
||||
|
||||
def py__iter__(self):
|
||||
var_args = self.var_args
|
||||
try:
|
||||
_, lazy_context = next(var_args.unpack())
|
||||
except StopIteration:
|
||||
pass
|
||||
else:
|
||||
for lazy in lazy_context.infer().iterate():
|
||||
yield lazy
|
||||
|
||||
from jedi.evaluate import arguments
|
||||
if isinstance(var_args, arguments.TreeArguments):
|
||||
additions = _check_array_additions(var_args.context, self.instance)
|
||||
for addition in additions:
|
||||
yield addition
|
||||
|
||||
def iterate(self, contextualized_node=None, is_async=False):
|
||||
return self.py__iter__()
|
||||
|
||||
|
||||
class Slice(Context):
|
||||
def __init__(self, context, start, stop, step):
|
||||
super(Slice, self).__init__(
|
||||
context.evaluator,
|
||||
parent_context=context.evaluator.builtins_module
|
||||
)
|
||||
self._context = context
|
||||
# all of them are either a Precedence or None.
|
||||
self._start = start
|
||||
self._stop = stop
|
||||
self._step = step
|
||||
|
||||
@property
|
||||
def obj(self):
|
||||
"""
|
||||
Imitate CompiledObject.obj behavior and return a ``builtin.slice()``
|
||||
object.
|
||||
"""
|
||||
def get(element):
|
||||
if element is None:
|
||||
return None
|
||||
|
||||
result = self._context.eval_node(element)
|
||||
if len(result) != 1:
|
||||
# For simplicity, we want slices to be clear defined with just
|
||||
# one type. Otherwise we will return an empty slice object.
|
||||
raise IndexError
|
||||
|
||||
context, = result
|
||||
return get_int_or_none(context)
|
||||
|
||||
try:
|
||||
return slice(get(self._start), get(self._stop), get(self._step))
|
||||
except IndexError:
|
||||
return slice(None, None, None)
|
||||
@@ -1,221 +0,0 @@
|
||||
"""
|
||||
Like described in the :mod:`parso.python.tree` module,
|
||||
there's a need for an ast like module to represent the states of parsed
|
||||
modules.
|
||||
|
||||
But now there are also structures in Python that need a little bit more than
|
||||
that. An ``Instance`` for example is only a ``Class`` before it is
|
||||
instantiated. This class represents these cases.
|
||||
|
||||
So, why is there also a ``Class`` class here? Well, there are decorators and
|
||||
they change classes in Python 3.
|
||||
|
||||
Representation modules also define "magic methods". Those methods look like
|
||||
``py__foo__`` and are typically mappable to the Python equivalents ``__call__``
|
||||
and others. Here's a list:
|
||||
|
||||
====================================== ========================================
|
||||
**Method** **Description**
|
||||
-------------------------------------- ----------------------------------------
|
||||
py__call__(params: Array) On callable objects, returns types.
|
||||
py__bool__() Returns True/False/None; None means that
|
||||
there's no certainty.
|
||||
py__bases__() Returns a list of base classes.
|
||||
py__mro__() Returns a list of classes (the mro).
|
||||
py__iter__() Returns a generator of a set of types.
|
||||
py__class__() Returns the class of an instance.
|
||||
py__getitem__(index: int/str) Returns a a set of types of the index.
|
||||
Can raise an IndexError/KeyError.
|
||||
py__file__() Only on modules. Returns None if does
|
||||
not exist.
|
||||
py__package__() Only on modules. For the import system.
|
||||
py__path__() Only on modules. For the import system.
|
||||
py__get__(call_object) Only on instances. Simulates
|
||||
descriptors.
|
||||
py__doc__(include_call_signature: Returns the docstring for a context.
|
||||
bool)
|
||||
====================================== ========================================
|
||||
|
||||
"""
|
||||
from jedi._compatibility import use_metaclass
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
from jedi.evaluate.cache import evaluator_method_cache, CachedMetaClass
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.lazy_context import LazyKnownContext
|
||||
from jedi.evaluate.filters import ParserTreeFilter, TreeNameDefinition, \
|
||||
ContextName
|
||||
from jedi.evaluate.base_context import ContextSet, iterator_to_context_set, \
|
||||
TreeContext
|
||||
|
||||
|
||||
def apply_py__get__(context, base_context):
|
||||
try:
|
||||
method = context.py__get__
|
||||
except AttributeError:
|
||||
yield context
|
||||
else:
|
||||
for descriptor_context in method(base_context):
|
||||
yield descriptor_context
|
||||
|
||||
|
||||
class ClassName(TreeNameDefinition):
|
||||
def __init__(self, parent_context, tree_name, name_context, apply_decorators):
|
||||
super(ClassName, self).__init__(parent_context, tree_name)
|
||||
self._name_context = name_context
|
||||
self._apply_decorators = apply_decorators
|
||||
|
||||
@iterator_to_context_set
|
||||
def infer(self):
|
||||
# TODO this _name_to_types might get refactored and be a part of the
|
||||
# parent class. Once it is, we can probably just overwrite method to
|
||||
# achieve this.
|
||||
from jedi.evaluate.syntax_tree import tree_name_to_contexts
|
||||
inferred = tree_name_to_contexts(
|
||||
self.parent_context.evaluator, self._name_context, self.tree_name)
|
||||
|
||||
for result_context in inferred:
|
||||
if self._apply_decorators:
|
||||
for c in apply_py__get__(result_context, self.parent_context):
|
||||
yield c
|
||||
else:
|
||||
yield result_context
|
||||
|
||||
|
||||
class ClassFilter(ParserTreeFilter):
|
||||
name_class = ClassName
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._is_instance = kwargs.pop('is_instance') # Python 2 :/
|
||||
super(ClassFilter, self).__init__(*args, **kwargs)
|
||||
|
||||
def _convert_names(self, names):
|
||||
return [
|
||||
self.name_class(
|
||||
parent_context=self.context,
|
||||
tree_name=name,
|
||||
name_context=self._node_context,
|
||||
apply_decorators=not self._is_instance,
|
||||
) for name in names
|
||||
]
|
||||
|
||||
def _equals_origin_scope(self):
|
||||
node = self._origin_scope
|
||||
while node is not None:
|
||||
if node == self._parser_scope or node == self.context:
|
||||
return True
|
||||
node = get_parent_scope(node)
|
||||
return False
|
||||
|
||||
def _access_possible(self, name):
|
||||
return not name.value.startswith('__') or name.value.endswith('__') \
|
||||
or self._equals_origin_scope()
|
||||
|
||||
def _filter(self, names):
|
||||
names = super(ClassFilter, self)._filter(names)
|
||||
return [name for name in names if self._access_possible(name)]
|
||||
|
||||
|
||||
class ClassContext(use_metaclass(CachedMetaClass, TreeContext)):
|
||||
"""
|
||||
This class is not only important to extend `tree.Class`, it is also a
|
||||
important for descriptors (if the descriptor methods are evaluated or not).
|
||||
"""
|
||||
api_type = u'class'
|
||||
|
||||
@evaluator_method_cache(default=())
|
||||
def py__mro__(self):
|
||||
def add(cls):
|
||||
if cls not in mro:
|
||||
mro.append(cls)
|
||||
|
||||
mro = [self]
|
||||
# TODO Do a proper mro resolution. Currently we are just listing
|
||||
# classes. However, it's a complicated algorithm.
|
||||
for lazy_cls in self.py__bases__():
|
||||
# TODO there's multiple different mro paths possible if this yields
|
||||
# multiple possibilities. Could be changed to be more correct.
|
||||
for cls in lazy_cls.infer():
|
||||
# TODO detect for TypeError: duplicate base class str,
|
||||
# e.g. `class X(str, str): pass`
|
||||
try:
|
||||
mro_method = cls.py__mro__
|
||||
except AttributeError:
|
||||
# TODO add a TypeError like:
|
||||
"""
|
||||
>>> class Y(lambda: test): pass
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: function() argument 1 must be code, not str
|
||||
>>> class Y(1): pass
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: int() takes at most 2 arguments (3 given)
|
||||
"""
|
||||
pass
|
||||
else:
|
||||
add(cls)
|
||||
for cls_new in mro_method():
|
||||
add(cls_new)
|
||||
return tuple(mro)
|
||||
|
||||
@evaluator_method_cache(default=())
|
||||
def py__bases__(self):
|
||||
arglist = self.tree_node.get_super_arglist()
|
||||
if arglist:
|
||||
from jedi.evaluate import arguments
|
||||
args = arguments.TreeArguments(self.evaluator, self.parent_context, arglist)
|
||||
return [value for key, value in args.unpack() if key is None]
|
||||
else:
|
||||
return [LazyKnownContext(compiled.builtin_from_name(self.evaluator, u'object'))]
|
||||
|
||||
def py__call__(self, params):
|
||||
from jedi.evaluate.context import TreeInstance
|
||||
return ContextSet(TreeInstance(self.evaluator, self.parent_context, self, params))
|
||||
|
||||
def py__class__(self):
|
||||
return compiled.builtin_from_name(self.evaluator, u'type')
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None, is_instance=False):
|
||||
if search_global:
|
||||
yield ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
)
|
||||
else:
|
||||
for cls in self.py__mro__():
|
||||
if isinstance(cls, compiled.CompiledObject):
|
||||
for filter in cls.get_filters(is_instance=is_instance):
|
||||
yield filter
|
||||
else:
|
||||
yield ClassFilter(
|
||||
self.evaluator, self, node_context=cls,
|
||||
origin_scope=origin_scope,
|
||||
is_instance=is_instance
|
||||
)
|
||||
|
||||
def is_class(self):
|
||||
return True
|
||||
|
||||
def get_function_slot_names(self, name):
|
||||
for filter in self.get_filters(search_global=False):
|
||||
names = filter.get(name)
|
||||
if names:
|
||||
return names
|
||||
return []
|
||||
|
||||
def get_param_names(self):
|
||||
for name in self.get_function_slot_names(u'__init__'):
|
||||
for context_ in name.infer():
|
||||
try:
|
||||
method = context_.get_param_names
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
return list(method())[1:]
|
||||
return []
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return ContextName(self, self.tree_node.name)
|
||||
@@ -1,219 +0,0 @@
|
||||
import re
|
||||
import os
|
||||
|
||||
from parso import python_bytes_to_unicode
|
||||
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi._compatibility import iter_modules, all_suffixes
|
||||
from jedi.evaluate.filters import GlobalNameFilter, ContextNameMixin, \
|
||||
AbstractNameDefinition, ParserTreeFilter, DictFilter, MergedFilter
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.base_context import TreeContext
|
||||
from jedi.evaluate.imports import SubModuleName, infer_import
|
||||
|
||||
|
||||
class _ModuleAttributeName(AbstractNameDefinition):
|
||||
"""
|
||||
For module attributes like __file__, __str__ and so on.
|
||||
"""
|
||||
api_type = u'instance'
|
||||
|
||||
def __init__(self, parent_module, string_name):
|
||||
self.parent_context = parent_module
|
||||
self.string_name = string_name
|
||||
|
||||
def infer(self):
|
||||
return compiled.get_string_context_set(self.parent_context.evaluator)
|
||||
|
||||
|
||||
class ModuleName(ContextNameMixin, AbstractNameDefinition):
|
||||
start_pos = 1, 0
|
||||
|
||||
def __init__(self, context, name):
|
||||
self._context = context
|
||||
self._name = name
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
return self._name
|
||||
|
||||
|
||||
class ModuleContext(TreeContext):
|
||||
api_type = u'module'
|
||||
parent_context = None
|
||||
|
||||
def __init__(self, evaluator, module_node, path, code_lines):
|
||||
super(ModuleContext, self).__init__(
|
||||
evaluator,
|
||||
parent_context=None,
|
||||
tree_node=module_node
|
||||
)
|
||||
self._path = path
|
||||
self.code_lines = code_lines
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield MergedFilter(
|
||||
ParserTreeFilter(
|
||||
self.evaluator,
|
||||
context=self,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope
|
||||
),
|
||||
GlobalNameFilter(self, self.tree_node),
|
||||
)
|
||||
yield DictFilter(self._sub_modules_dict())
|
||||
yield DictFilter(self._module_attributes_dict())
|
||||
for star_module in self.star_imports():
|
||||
yield next(star_module.get_filters(search_global))
|
||||
|
||||
# I'm not sure if the star import cache is really that effective anymore
|
||||
# with all the other really fast import caches. Recheck. Also we would need
|
||||
# to push the star imports into Evaluator.module_cache, if we reenable this.
|
||||
@evaluator_method_cache([])
|
||||
def star_imports(self):
|
||||
modules = []
|
||||
for i in self.tree_node.iter_imports():
|
||||
if i.is_star_import():
|
||||
name = i.get_paths()[-1][-1]
|
||||
new = infer_import(self, name)
|
||||
for module in new:
|
||||
if isinstance(module, ModuleContext):
|
||||
modules += module.star_imports()
|
||||
modules += new
|
||||
return modules
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _module_attributes_dict(self):
|
||||
names = ['__file__', '__package__', '__doc__', '__name__']
|
||||
# All the additional module attributes are strings.
|
||||
return dict((n, _ModuleAttributeName(self, n)) for n in names)
|
||||
|
||||
@property
|
||||
def _string_name(self):
|
||||
""" This is used for the goto functions. """
|
||||
if self._path is None:
|
||||
return '' # no path -> empty name
|
||||
else:
|
||||
sep = (re.escape(os.path.sep),) * 2
|
||||
r = re.search(r'([^%s]*?)(%s__init__)?(\.py|\.so)?$' % sep, self._path)
|
||||
# Remove PEP 3149 names
|
||||
return re.sub(r'\.[a-z]+-\d{2}[mud]{0,3}$', '', r.group(1))
|
||||
|
||||
@property
|
||||
@evaluator_method_cache()
|
||||
def name(self):
|
||||
return ModuleName(self, self._string_name)
|
||||
|
||||
def _get_init_directory(self):
|
||||
"""
|
||||
:return: The path to the directory of a package. None in case it's not
|
||||
a package.
|
||||
"""
|
||||
for suffix in all_suffixes():
|
||||
ending = '__init__' + suffix
|
||||
py__file__ = self.py__file__()
|
||||
if py__file__ is not None and py__file__.endswith(ending):
|
||||
# Remove the ending, including the separator.
|
||||
return self.py__file__()[:-len(ending) - 1]
|
||||
return None
|
||||
|
||||
def py__name__(self):
|
||||
for name, module in self.evaluator.module_cache.iterate_modules_with_names():
|
||||
if module == self and name != '':
|
||||
return name
|
||||
|
||||
return '__main__'
|
||||
|
||||
def py__file__(self):
|
||||
"""
|
||||
In contrast to Python's __file__ can be None.
|
||||
"""
|
||||
if self._path is None:
|
||||
return None
|
||||
|
||||
return os.path.abspath(self._path)
|
||||
|
||||
def py__package__(self):
|
||||
if self._get_init_directory() is None:
|
||||
return re.sub(r'\.?[^.]+$', '', self.py__name__())
|
||||
else:
|
||||
return self.py__name__()
|
||||
|
||||
def _py__path__(self):
|
||||
search_path = self.evaluator.get_sys_path()
|
||||
init_path = self.py__file__()
|
||||
if os.path.basename(init_path) == '__init__.py':
|
||||
with open(init_path, 'rb') as f:
|
||||
content = python_bytes_to_unicode(f.read(), errors='replace')
|
||||
# these are strings that need to be used for namespace packages,
|
||||
# the first one is ``pkgutil``, the second ``pkg_resources``.
|
||||
options = ('declare_namespace(__name__)', 'extend_path(__path__')
|
||||
if options[0] in content or options[1] in content:
|
||||
# It is a namespace, now try to find the rest of the
|
||||
# modules on sys_path or whatever the search_path is.
|
||||
paths = set()
|
||||
for s in search_path:
|
||||
other = os.path.join(s, self.name.string_name)
|
||||
if os.path.isdir(other):
|
||||
paths.add(other)
|
||||
if paths:
|
||||
return list(paths)
|
||||
# TODO I'm not sure if this is how nested namespace
|
||||
# packages work. The tests are not really good enough to
|
||||
# show that.
|
||||
# Default to this.
|
||||
return [self._get_init_directory()]
|
||||
|
||||
@property
|
||||
def py__path__(self):
|
||||
"""
|
||||
Not seen here, since it's a property. The callback actually uses a
|
||||
variable, so use it like::
|
||||
|
||||
foo.py__path__(sys_path)
|
||||
|
||||
In case of a package, this returns Python's __path__ attribute, which
|
||||
is a list of paths (strings).
|
||||
Raises an AttributeError if the module is not a package.
|
||||
"""
|
||||
path = self._get_init_directory()
|
||||
|
||||
if path is None:
|
||||
raise AttributeError('Only packages have __path__ attributes.')
|
||||
else:
|
||||
return self._py__path__
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _sub_modules_dict(self):
|
||||
"""
|
||||
Lists modules in the directory of this module (if this module is a
|
||||
package).
|
||||
"""
|
||||
names = {}
|
||||
try:
|
||||
method = self.py__path__
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
for path in method():
|
||||
mods = iter_modules([path])
|
||||
for module_loader, name, is_pkg in mods:
|
||||
# It's obviously a relative import to the current module.
|
||||
names[name] = SubModuleName(self, name)
|
||||
|
||||
# TODO add something like this in the future, its cleaner than the
|
||||
# import hacks.
|
||||
# ``os.path`` is a hardcoded exception, because it's a
|
||||
# ``sys.modules`` modification.
|
||||
# if str(self.name) == 'os':
|
||||
# names.append(Name('path', parent_context=self))
|
||||
|
||||
return names
|
||||
|
||||
def py__class__(self):
|
||||
return compiled.get_special_object(self.evaluator, u'MODULE_CLASS')
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s@%s-%s>" % (
|
||||
self.__class__.__name__, self._string_name,
|
||||
self.tree_node.start_pos[0], self.tree_node.end_pos[0])
|
||||
@@ -1,72 +0,0 @@
|
||||
import os
|
||||
from itertools import chain
|
||||
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.filters import DictFilter, AbstractNameDefinition, ContextNameMixin
|
||||
from jedi.evaluate.base_context import Context
|
||||
|
||||
|
||||
class ImplicitNSName(ContextNameMixin, AbstractNameDefinition):
|
||||
"""
|
||||
Accessing names for implicit namespace packages should infer to nothing.
|
||||
This object will prevent Jedi from raising exceptions
|
||||
"""
|
||||
def __init__(self, implicit_ns_context, string_name):
|
||||
self._context = implicit_ns_context
|
||||
self.string_name = string_name
|
||||
|
||||
|
||||
class ImplicitNamespaceContext(Context):
|
||||
"""
|
||||
Provides support for implicit namespace packages
|
||||
"""
|
||||
# Is a module like every other module, because if you import an empty
|
||||
# folder foobar it will be available as an object:
|
||||
# <module 'foobar' (namespace)>.
|
||||
api_type = u'module'
|
||||
parent_context = None
|
||||
|
||||
def __init__(self, evaluator, fullname, paths):
|
||||
super(ImplicitNamespaceContext, self).__init__(evaluator, parent_context=None)
|
||||
self.evaluator = evaluator
|
||||
self._fullname = fullname
|
||||
self.paths = paths
|
||||
|
||||
def get_filters(self, search_global, until_position=None, origin_scope=None):
|
||||
yield DictFilter(self._sub_modules_dict())
|
||||
|
||||
@property
|
||||
@evaluator_method_cache()
|
||||
def name(self):
|
||||
string_name = self.py__package__().rpartition('.')[-1]
|
||||
return ImplicitNSName(self, string_name)
|
||||
|
||||
def py__file__(self):
|
||||
return None
|
||||
|
||||
def py__package__(self):
|
||||
"""Return the fullname
|
||||
"""
|
||||
return self._fullname
|
||||
|
||||
def py__path__(self):
|
||||
return [self.paths]
|
||||
|
||||
def py__name__(self):
|
||||
return self._fullname
|
||||
|
||||
@evaluator_method_cache()
|
||||
def _sub_modules_dict(self):
|
||||
names = {}
|
||||
|
||||
file_names = chain.from_iterable(os.listdir(path) for path in self.paths)
|
||||
mods = [
|
||||
file_name.rpartition('.')[0] if '.' in file_name else file_name
|
||||
for file_name in file_names
|
||||
if file_name != '__pycache__'
|
||||
]
|
||||
|
||||
for name in mods:
|
||||
names[name] = imports.SubModuleName(self, name)
|
||||
return names
|
||||
@@ -1,231 +0,0 @@
|
||||
"""
|
||||
One of the really important features of |jedi| is to have an option to
|
||||
understand code like this::
|
||||
|
||||
def foo(bar):
|
||||
bar. # completion here
|
||||
foo(1)
|
||||
|
||||
There's no doubt wheter bar is an ``int`` or not, but if there's also a call
|
||||
like ``foo('str')``, what would happen? Well, we'll just show both. Because
|
||||
that's what a human would expect.
|
||||
|
||||
It works as follows:
|
||||
|
||||
- |Jedi| sees a param
|
||||
- search for function calls named ``foo``
|
||||
- execute these calls and check the input.
|
||||
"""
|
||||
|
||||
from parso.python import tree
|
||||
from jedi import settings
|
||||
from jedi import debug
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.arguments import TreeArguments
|
||||
from jedi.evaluate.param import create_default_params
|
||||
from jedi.evaluate.helpers import is_stdlib_path
|
||||
from jedi.evaluate.utils import to_list
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
from jedi.evaluate.context import ModuleContext, instance
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
from jedi.evaluate import recursion
|
||||
|
||||
|
||||
MAX_PARAM_SEARCHES = 20
|
||||
|
||||
|
||||
class DynamicExecutedParams(object):
|
||||
"""
|
||||
Simulates being a parameter while actually just being multiple params.
|
||||
"""
|
||||
|
||||
def __init__(self, evaluator, executed_params):
|
||||
self.evaluator = evaluator
|
||||
self._executed_params = executed_params
|
||||
|
||||
def infer(self):
|
||||
with recursion.execution_allowed(self.evaluator, self) as allowed:
|
||||
# We need to catch recursions that may occur, because an
|
||||
# anonymous functions can create an anonymous parameter that is
|
||||
# more or less self referencing.
|
||||
if allowed:
|
||||
return ContextSet.from_sets(p.infer() for p in self._executed_params)
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
@debug.increase_indent
|
||||
def search_params(evaluator, execution_context, funcdef):
|
||||
"""
|
||||
A dynamic search for param values. If you try to complete a type:
|
||||
|
||||
>>> def func(foo):
|
||||
... foo
|
||||
>>> func(1)
|
||||
>>> func("")
|
||||
|
||||
It is not known what the type ``foo`` without analysing the whole code. You
|
||||
have to look for all calls to ``func`` to find out what ``foo`` possibly
|
||||
is.
|
||||
"""
|
||||
if not settings.dynamic_params:
|
||||
return create_default_params(execution_context, funcdef)
|
||||
|
||||
evaluator.dynamic_params_depth += 1
|
||||
try:
|
||||
path = execution_context.get_root_context().py__file__()
|
||||
if path is not None and is_stdlib_path(path):
|
||||
# We don't want to search for usages in the stdlib. Usually people
|
||||
# don't work with it (except if you are a core maintainer, sorry).
|
||||
# This makes everything slower. Just disable it and run the tests,
|
||||
# you will see the slowdown, especially in 3.6.
|
||||
return create_default_params(execution_context, funcdef)
|
||||
|
||||
if funcdef.type == 'lambdef':
|
||||
string_name = _get_lambda_name(funcdef)
|
||||
if string_name is None:
|
||||
return create_default_params(execution_context, funcdef)
|
||||
else:
|
||||
string_name = funcdef.name.value
|
||||
debug.dbg('Dynamic param search in %s.', string_name, color='MAGENTA')
|
||||
|
||||
try:
|
||||
module_context = execution_context.get_root_context()
|
||||
function_executions = _search_function_executions(
|
||||
evaluator,
|
||||
module_context,
|
||||
funcdef,
|
||||
string_name=string_name,
|
||||
)
|
||||
if function_executions:
|
||||
zipped_params = zip(*list(
|
||||
function_execution.get_executed_params()
|
||||
for function_execution in function_executions
|
||||
))
|
||||
params = [DynamicExecutedParams(evaluator, executed_params) for executed_params in zipped_params]
|
||||
# Evaluate the ExecutedParams to types.
|
||||
else:
|
||||
return create_default_params(execution_context, funcdef)
|
||||
finally:
|
||||
debug.dbg('Dynamic param result finished', color='MAGENTA')
|
||||
return params
|
||||
finally:
|
||||
evaluator.dynamic_params_depth -= 1
|
||||
|
||||
|
||||
@evaluator_function_cache(default=None)
|
||||
@to_list
|
||||
def _search_function_executions(evaluator, module_context, funcdef, string_name):
|
||||
"""
|
||||
Returns a list of param names.
|
||||
"""
|
||||
compare_node = funcdef
|
||||
if string_name == '__init__':
|
||||
cls = get_parent_scope(funcdef)
|
||||
if isinstance(cls, tree.Class):
|
||||
string_name = cls.name.value
|
||||
compare_node = cls
|
||||
|
||||
found_executions = False
|
||||
i = 0
|
||||
for for_mod_context in imports.get_modules_containing_name(
|
||||
evaluator, [module_context], string_name):
|
||||
if not isinstance(module_context, ModuleContext):
|
||||
return
|
||||
for name, trailer in _get_possible_nodes(for_mod_context, string_name):
|
||||
i += 1
|
||||
|
||||
# This is a simple way to stop Jedi's dynamic param recursion
|
||||
# from going wild: The deeper Jedi's in the recursion, the less
|
||||
# code should be evaluated.
|
||||
if i * evaluator.dynamic_params_depth > MAX_PARAM_SEARCHES:
|
||||
return
|
||||
|
||||
random_context = evaluator.create_context(for_mod_context, name)
|
||||
for function_execution in _check_name_for_execution(
|
||||
evaluator, random_context, compare_node, name, trailer):
|
||||
found_executions = True
|
||||
yield function_execution
|
||||
|
||||
# If there are results after processing a module, we're probably
|
||||
# good to process. This is a speed optimization.
|
||||
if found_executions:
|
||||
return
|
||||
|
||||
|
||||
def _get_lambda_name(node):
|
||||
stmt = node.parent
|
||||
if stmt.type == 'expr_stmt':
|
||||
first_operator = next(stmt.yield_operators(), None)
|
||||
if first_operator == '=':
|
||||
first = stmt.children[0]
|
||||
if first.type == 'name':
|
||||
return first.value
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _get_possible_nodes(module_context, func_string_name):
|
||||
try:
|
||||
names = module_context.tree_node.get_used_names()[func_string_name]
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
for name in names:
|
||||
bracket = name.get_next_leaf()
|
||||
trailer = bracket.parent
|
||||
if trailer.type == 'trailer' and bracket == '(':
|
||||
yield name, trailer
|
||||
|
||||
|
||||
def _check_name_for_execution(evaluator, context, compare_node, name, trailer):
|
||||
from jedi.evaluate.context.function import FunctionExecutionContext
|
||||
|
||||
def create_func_excs():
|
||||
arglist = trailer.children[1]
|
||||
if arglist == ')':
|
||||
arglist = None
|
||||
args = TreeArguments(evaluator, context, arglist, trailer)
|
||||
if value_node.type == 'classdef':
|
||||
created_instance = instance.TreeInstance(
|
||||
evaluator,
|
||||
value.parent_context,
|
||||
value,
|
||||
args
|
||||
)
|
||||
for execution in created_instance.create_init_executions():
|
||||
yield execution
|
||||
else:
|
||||
yield value.get_function_execution(args)
|
||||
|
||||
for value in evaluator.goto_definitions(context, name):
|
||||
value_node = value.tree_node
|
||||
if compare_node == value_node:
|
||||
for func_execution in create_func_excs():
|
||||
yield func_execution
|
||||
elif isinstance(value.parent_context, FunctionExecutionContext) and \
|
||||
compare_node.type == 'funcdef':
|
||||
# Here we're trying to find decorators by checking the first
|
||||
# parameter. It's not very generic though. Should find a better
|
||||
# solution that also applies to nested decorators.
|
||||
params = value.parent_context.get_executed_params()
|
||||
if len(params) != 1:
|
||||
continue
|
||||
values = params[0].infer()
|
||||
nodes = [v.tree_node for v in values]
|
||||
if nodes == [compare_node]:
|
||||
# Found a decorator.
|
||||
module_context = context.get_root_context()
|
||||
execution_context = next(create_func_excs())
|
||||
for name, trailer in _get_possible_nodes(module_context, params[0].string_name):
|
||||
if value_node.start_pos < name.start_pos < value_node.end_pos:
|
||||
random_context = evaluator.create_context(execution_context, name)
|
||||
iterator = _check_name_for_execution(
|
||||
evaluator,
|
||||
random_context,
|
||||
compare_node,
|
||||
name,
|
||||
trailer
|
||||
)
|
||||
for function_execution in iterator:
|
||||
yield function_execution
|
||||
@@ -1,492 +0,0 @@
|
||||
"""
|
||||
Filters are objects that you can use to filter names in different scopes. They
|
||||
are needed for name resolution.
|
||||
"""
|
||||
from abc import abstractmethod
|
||||
|
||||
from parso.tree import search_ancestor
|
||||
|
||||
from jedi._compatibility import use_metaclass, Parameter
|
||||
from jedi.cache import memoize_method
|
||||
from jedi.evaluate import flow_analysis
|
||||
from jedi.evaluate.base_context import ContextSet, Context
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
from jedi.evaluate.utils import to_list
|
||||
|
||||
|
||||
class AbstractNameDefinition(object):
|
||||
start_pos = None
|
||||
string_name = None
|
||||
parent_context = None
|
||||
tree_name = None
|
||||
|
||||
@abstractmethod
|
||||
def infer(self):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def goto(self):
|
||||
# Typically names are already definitions and therefore a goto on that
|
||||
# name will always result on itself.
|
||||
return {self}
|
||||
|
||||
def get_root_context(self):
|
||||
return self.parent_context.get_root_context()
|
||||
|
||||
def __repr__(self):
|
||||
if self.start_pos is None:
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.string_name)
|
||||
return '<%s: %s@%s>' % (self.__class__.__name__, self.string_name, self.start_pos)
|
||||
|
||||
def is_import(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return self.parent_context.api_type
|
||||
|
||||
|
||||
class AbstractTreeName(AbstractNameDefinition):
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
self.tree_name = tree_name
|
||||
|
||||
def goto(self):
|
||||
return self.parent_context.evaluator.goto(self.parent_context, self.tree_name)
|
||||
|
||||
def is_import(self):
|
||||
imp = search_ancestor(self.tree_name, 'import_from', 'import_name')
|
||||
return imp is not None
|
||||
|
||||
@property
|
||||
def string_name(self):
|
||||
return self.tree_name.value
|
||||
|
||||
@property
|
||||
def start_pos(self):
|
||||
return self.tree_name.start_pos
|
||||
|
||||
|
||||
class ContextNameMixin(object):
|
||||
def infer(self):
|
||||
return ContextSet(self._context)
|
||||
|
||||
def get_root_context(self):
|
||||
if self.parent_context is None:
|
||||
return self._context
|
||||
return super(ContextNameMixin, self).get_root_context()
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return self._context.api_type
|
||||
|
||||
|
||||
class ContextName(ContextNameMixin, AbstractTreeName):
|
||||
def __init__(self, context, tree_name):
|
||||
super(ContextName, self).__init__(context.parent_context, tree_name)
|
||||
self._context = context
|
||||
|
||||
|
||||
class TreeNameDefinition(AbstractTreeName):
|
||||
_API_TYPES = dict(
|
||||
import_name='module',
|
||||
import_from='module',
|
||||
funcdef='function',
|
||||
param='param',
|
||||
classdef='class',
|
||||
)
|
||||
|
||||
def infer(self):
|
||||
# Refactor this, should probably be here.
|
||||
from jedi.evaluate.syntax_tree import tree_name_to_contexts
|
||||
return tree_name_to_contexts(self.parent_context.evaluator, self.parent_context, self.tree_name)
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
definition = self.tree_name.get_definition(import_name_always=True)
|
||||
if definition is None:
|
||||
return 'statement'
|
||||
return self._API_TYPES.get(definition.type, 'statement')
|
||||
|
||||
|
||||
class ParamName(AbstractTreeName):
|
||||
api_type = u'param'
|
||||
|
||||
def __init__(self, parent_context, tree_name):
|
||||
self.parent_context = parent_context
|
||||
self.tree_name = tree_name
|
||||
|
||||
def get_kind(self):
|
||||
tree_param = search_ancestor(self.tree_name, 'param')
|
||||
if tree_param.star_count == 1: # *args
|
||||
return Parameter.VAR_POSITIONAL
|
||||
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
|
||||
|
||||
def infer(self):
|
||||
return self.get_param().infer()
|
||||
|
||||
def get_param(self):
|
||||
params = self.parent_context.get_executed_params()
|
||||
param_node = search_ancestor(self.tree_name, 'param')
|
||||
return params[param_node.position_index]
|
||||
|
||||
|
||||
class AbstractFilter(object):
|
||||
_until_position = None
|
||||
|
||||
def _filter(self, names):
|
||||
if self._until_position is not None:
|
||||
return [n for n in names if n.start_pos < self._until_position]
|
||||
return names
|
||||
|
||||
@abstractmethod
|
||||
def get(self, name):
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def values(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AbstractUsedNamesFilter(AbstractFilter):
|
||||
name_class = TreeNameDefinition
|
||||
|
||||
def __init__(self, context, parser_scope):
|
||||
self._parser_scope = parser_scope
|
||||
self._used_names = self._parser_scope.get_root_node().get_used_names()
|
||||
self.context = context
|
||||
|
||||
def get(self, name):
|
||||
try:
|
||||
names = self._used_names[name]
|
||||
except KeyError:
|
||||
return []
|
||||
|
||||
return self._convert_names(self._filter(names))
|
||||
|
||||
def _convert_names(self, names):
|
||||
return [self.name_class(self.context, name) for name in names]
|
||||
|
||||
def values(self):
|
||||
return self._convert_names(name for name_list in self._used_names.values()
|
||||
for name in self._filter(name_list))
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.context)
|
||||
|
||||
|
||||
class ParserTreeFilter(AbstractUsedNamesFilter):
|
||||
def __init__(self, evaluator, context, node_context=None, until_position=None,
|
||||
origin_scope=None):
|
||||
"""
|
||||
node_context is an option to specify a second context for use cases
|
||||
like the class mro where the parent class of a new name would be the
|
||||
context, but for some type inference it's important to have a local
|
||||
context of the other classes.
|
||||
"""
|
||||
if node_context is None:
|
||||
node_context = context
|
||||
super(ParserTreeFilter, self).__init__(context, node_context.tree_node)
|
||||
self._node_context = node_context
|
||||
self._origin_scope = origin_scope
|
||||
self._until_position = until_position
|
||||
|
||||
def _filter(self, names):
|
||||
names = super(ParserTreeFilter, self)._filter(names)
|
||||
names = [n for n in names if self._is_name_reachable(n)]
|
||||
return list(self._check_flows(names))
|
||||
|
||||
def _is_name_reachable(self, name):
|
||||
if not name.is_definition():
|
||||
return False
|
||||
parent = name.parent
|
||||
if parent.type == 'trailer':
|
||||
return False
|
||||
base_node = parent if parent.type in ('classdef', 'funcdef') else name
|
||||
return get_parent_scope(base_node) == self._parser_scope
|
||||
|
||||
def _check_flows(self, names):
|
||||
for name in sorted(names, key=lambda name: name.start_pos, reverse=True):
|
||||
check = flow_analysis.reachability_check(
|
||||
context=self._node_context,
|
||||
context_scope=self._parser_scope,
|
||||
node=name,
|
||||
origin_scope=self._origin_scope
|
||||
)
|
||||
if check is not flow_analysis.UNREACHABLE:
|
||||
yield name
|
||||
|
||||
if check is flow_analysis.REACHABLE:
|
||||
break
|
||||
|
||||
|
||||
class FunctionExecutionFilter(ParserTreeFilter):
|
||||
param_name = ParamName
|
||||
|
||||
def __init__(self, evaluator, context, node_context=None,
|
||||
until_position=None, origin_scope=None):
|
||||
super(FunctionExecutionFilter, self).__init__(
|
||||
evaluator,
|
||||
context,
|
||||
node_context,
|
||||
until_position,
|
||||
origin_scope
|
||||
)
|
||||
|
||||
@to_list
|
||||
def _convert_names(self, names):
|
||||
for name in names:
|
||||
param = search_ancestor(name, 'param')
|
||||
if param:
|
||||
yield self.param_name(self.context, name)
|
||||
else:
|
||||
yield TreeNameDefinition(self.context, name)
|
||||
|
||||
|
||||
class GlobalNameFilter(AbstractUsedNamesFilter):
|
||||
def __init__(self, context, parser_scope):
|
||||
super(GlobalNameFilter, self).__init__(context, parser_scope)
|
||||
|
||||
@to_list
|
||||
def _filter(self, names):
|
||||
for name in names:
|
||||
if name.parent.type == 'global_stmt':
|
||||
yield name
|
||||
|
||||
|
||||
class DictFilter(AbstractFilter):
|
||||
def __init__(self, dct):
|
||||
self._dct = dct
|
||||
|
||||
def get(self, name):
|
||||
try:
|
||||
value = self._convert(name, self._dct[name])
|
||||
except KeyError:
|
||||
return []
|
||||
else:
|
||||
return list(self._filter([value]))
|
||||
|
||||
def values(self):
|
||||
def yielder():
|
||||
for item in self._dct.items():
|
||||
try:
|
||||
yield self._convert(*item)
|
||||
except KeyError:
|
||||
pass
|
||||
return self._filter(yielder())
|
||||
|
||||
def _convert(self, name, value):
|
||||
return value
|
||||
|
||||
|
||||
class MergedFilter(object):
|
||||
def __init__(self, *filters):
|
||||
self._filters = filters
|
||||
|
||||
def get(self, name):
|
||||
return [n for filter in self._filters for n in filter.get(name)]
|
||||
|
||||
def values(self):
|
||||
return [n for filter in self._filters for n in filter.values()]
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (self.__class__.__name__, ', '.join(str(f) for f in self._filters))
|
||||
|
||||
|
||||
class _BuiltinMappedMethod(Context):
|
||||
"""``Generator.__next__`` ``dict.values`` methods and so on."""
|
||||
api_type = u'function'
|
||||
|
||||
def __init__(self, builtin_context, method, builtin_func):
|
||||
super(_BuiltinMappedMethod, self).__init__(
|
||||
builtin_context.evaluator,
|
||||
parent_context=builtin_context
|
||||
)
|
||||
self._method = method
|
||||
self._builtin_func = builtin_func
|
||||
|
||||
def py__call__(self, params):
|
||||
# TODO add TypeError if params are given/or not correct.
|
||||
return self._method(self.parent_context)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._builtin_func, name)
|
||||
|
||||
|
||||
class SpecialMethodFilter(DictFilter):
|
||||
"""
|
||||
A filter for methods that are defined in this module on the corresponding
|
||||
classes like Generator (for __next__, etc).
|
||||
"""
|
||||
class SpecialMethodName(AbstractNameDefinition):
|
||||
api_type = u'function'
|
||||
|
||||
def __init__(self, parent_context, string_name, value, builtin_context):
|
||||
callable_, python_version = value
|
||||
if python_version is not None and \
|
||||
python_version != parent_context.evaluator.environment.version_info.major:
|
||||
raise KeyError
|
||||
|
||||
self.parent_context = parent_context
|
||||
self.string_name = string_name
|
||||
self._callable = callable_
|
||||
self._builtin_context = builtin_context
|
||||
|
||||
def infer(self):
|
||||
for filter in self._builtin_context.get_filters():
|
||||
# We can take the first index, because on builtin methods there's
|
||||
# always only going to be one name. The same is true for the
|
||||
# inferred values.
|
||||
for name in filter.get(self.string_name):
|
||||
builtin_func = next(iter(name.infer()))
|
||||
break
|
||||
else:
|
||||
continue
|
||||
break
|
||||
return ContextSet(
|
||||
_BuiltinMappedMethod(self.parent_context, self._callable, builtin_func)
|
||||
)
|
||||
|
||||
def __init__(self, context, dct, builtin_context):
|
||||
super(SpecialMethodFilter, self).__init__(dct)
|
||||
self.context = context
|
||||
self._builtin_context = builtin_context
|
||||
"""
|
||||
This context is what will be used to introspect the name, where as the
|
||||
other context will be used to execute the function.
|
||||
|
||||
We distinguish, because we have to.
|
||||
"""
|
||||
|
||||
def _convert(self, name, value):
|
||||
return self.SpecialMethodName(self.context, name, value, self._builtin_context)
|
||||
|
||||
|
||||
class _OverwriteMeta(type):
|
||||
def __init__(cls, name, bases, dct):
|
||||
super(_OverwriteMeta, cls).__init__(name, bases, dct)
|
||||
|
||||
base_dct = {}
|
||||
for base_cls in reversed(cls.__bases__):
|
||||
try:
|
||||
base_dct.update(base_cls.overwritten_methods)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
for func in cls.__dict__.values():
|
||||
try:
|
||||
base_dct.update(func.registered_overwritten_methods)
|
||||
except AttributeError:
|
||||
pass
|
||||
cls.overwritten_methods = base_dct
|
||||
|
||||
|
||||
class AbstractObjectOverwrite(use_metaclass(_OverwriteMeta, object)):
|
||||
def get_object(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_filters(self, search_global, *args, **kwargs):
|
||||
yield SpecialMethodFilter(self, self.overwritten_methods, self.get_object())
|
||||
|
||||
for filter in self.get_object().get_filters(search_global):
|
||||
yield filter
|
||||
|
||||
|
||||
class BuiltinOverwrite(Context, AbstractObjectOverwrite):
|
||||
special_object_identifier = None
|
||||
|
||||
def __init__(self, evaluator):
|
||||
super(BuiltinOverwrite, self).__init__(evaluator, evaluator.builtins_module)
|
||||
|
||||
@memoize_method
|
||||
def get_object(self):
|
||||
from jedi.evaluate import compiled
|
||||
assert self.special_object_identifier
|
||||
return compiled.get_special_object(self.evaluator, self.special_object_identifier)
|
||||
|
||||
def py__class__(self):
|
||||
return self.get_object().py__class__()
|
||||
|
||||
|
||||
def publish_method(method_name, python_version_match=None):
|
||||
def decorator(func):
|
||||
dct = func.__dict__.setdefault('registered_overwritten_methods', {})
|
||||
dct[method_name] = func, python_version_match
|
||||
return func
|
||||
return decorator
|
||||
|
||||
|
||||
def get_global_filters(evaluator, context, until_position, origin_scope):
|
||||
"""
|
||||
Returns all filters in order of priority for name resolution.
|
||||
|
||||
For global name lookups. The filters will handle name resolution
|
||||
themselves, but here we gather possible filters downwards.
|
||||
|
||||
>>> from jedi._compatibility import u, no_unicode_pprint
|
||||
>>> from jedi import Script
|
||||
>>> script = Script(u('''
|
||||
... x = ['a', 'b', 'c']
|
||||
... def func():
|
||||
... y = None
|
||||
... '''))
|
||||
>>> module_node = script._module_node
|
||||
>>> scope = next(module_node.iter_funcdefs())
|
||||
>>> scope
|
||||
<Function: func@3-5>
|
||||
>>> context = script._get_module().create_context(scope)
|
||||
>>> filters = list(get_global_filters(context.evaluator, context, (4, 0), None))
|
||||
|
||||
First we get the names from the function scope.
|
||||
|
||||
>>> no_unicode_pprint(filters[0]) #doctest: +ELLIPSIS
|
||||
MergedFilter(<ParserTreeFilter: ...>, <GlobalNameFilter: ...>)
|
||||
>>> sorted(str(n) for n in filters[0].values())
|
||||
['<TreeNameDefinition: func@(3, 4)>', '<TreeNameDefinition: x@(2, 0)>']
|
||||
>>> filters[0]._filters[0]._until_position
|
||||
(4, 0)
|
||||
>>> filters[0]._filters[1]._until_position
|
||||
|
||||
Then it yields the names from one level "lower". In this example, this is
|
||||
the module scope (including globals).
|
||||
As a side note, you can see, that the position in the filter is None on the
|
||||
globals filter, because there the whole module is searched.
|
||||
|
||||
>>> list(filters[1].values()) # package modules -> Also empty.
|
||||
[]
|
||||
>>> sorted(name.string_name for name in filters[2].values()) # Module attributes
|
||||
['__doc__', '__file__', '__name__', '__package__']
|
||||
|
||||
Finally, it yields the builtin filter, if `include_builtin` is
|
||||
true (default).
|
||||
|
||||
>>> filters[3].values() #doctest: +ELLIPSIS
|
||||
[<CompiledName: ...>, ...]
|
||||
"""
|
||||
from jedi.evaluate.context.function import FunctionExecutionContext
|
||||
while context is not None:
|
||||
# Names in methods cannot be resolved within the class.
|
||||
for filter in context.get_filters(
|
||||
search_global=True,
|
||||
until_position=until_position,
|
||||
origin_scope=origin_scope):
|
||||
yield filter
|
||||
if isinstance(context, FunctionExecutionContext):
|
||||
# The position should be reset if the current scope is a function.
|
||||
until_position = None
|
||||
|
||||
context = context.parent_context
|
||||
|
||||
# Add builtins to the global scope.
|
||||
for filter in evaluator.builtins_module.get_filters(search_global=True):
|
||||
yield filter
|
||||
@@ -1,278 +0,0 @@
|
||||
"""
|
||||
Searching for names with given scope and name. This is very central in Jedi and
|
||||
Python. The name resolution is quite complicated with descripter,
|
||||
``__getattribute__``, ``__getattr__``, ``global``, etc.
|
||||
|
||||
If you want to understand name resolution, please read the first few chapters
|
||||
in http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/.
|
||||
|
||||
Flow checks
|
||||
+++++++++++
|
||||
|
||||
Flow checks are not really mature. There's only a check for ``isinstance``. It
|
||||
would check whether a flow has the form of ``if isinstance(a, type_or_tuple)``.
|
||||
Unfortunately every other thing is being ignored (e.g. a == '' would be easy to
|
||||
check for -> a is a string). There's big potential in these checks.
|
||||
"""
|
||||
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.evaluate.context import AbstractInstanceContext
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate import flow_analysis
|
||||
from jedi.evaluate.arguments import TreeArguments
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.filters import get_global_filters, TreeNameDefinition
|
||||
from jedi.evaluate.base_context import ContextSet
|
||||
from jedi.parser_utils import is_scope, get_parent_scope
|
||||
|
||||
|
||||
class NameFinder(object):
|
||||
def __init__(self, evaluator, context, name_context, name_or_str,
|
||||
position=None, analysis_errors=True):
|
||||
self._evaluator = evaluator
|
||||
# Make sure that it's not just a syntax tree node.
|
||||
self._context = context
|
||||
self._name_context = name_context
|
||||
self._name = name_or_str
|
||||
if isinstance(name_or_str, tree.Name):
|
||||
self._string_name = name_or_str.value
|
||||
else:
|
||||
self._string_name = name_or_str
|
||||
self._position = position
|
||||
self._found_predefined_types = None
|
||||
self._analysis_errors = analysis_errors
|
||||
|
||||
@debug.increase_indent
|
||||
def find(self, filters, attribute_lookup):
|
||||
"""
|
||||
:params bool attribute_lookup: Tell to logic if we're accessing the
|
||||
attribute or the contents of e.g. a function.
|
||||
"""
|
||||
names = self.filter_name(filters)
|
||||
if self._found_predefined_types is not None and names:
|
||||
check = flow_analysis.reachability_check(
|
||||
context=self._context,
|
||||
context_scope=self._context.tree_node,
|
||||
node=self._name,
|
||||
)
|
||||
if check is flow_analysis.UNREACHABLE:
|
||||
return ContextSet()
|
||||
return self._found_predefined_types
|
||||
|
||||
types = self._names_to_types(names, attribute_lookup)
|
||||
|
||||
if not names and self._analysis_errors and not types \
|
||||
and not (isinstance(self._name, tree.Name) and
|
||||
isinstance(self._name.parent.parent, tree.Param)):
|
||||
if isinstance(self._name, tree.Name):
|
||||
if attribute_lookup:
|
||||
analysis.add_attribute_error(
|
||||
self._name_context, self._context, self._name)
|
||||
else:
|
||||
message = ("NameError: name '%s' is not defined."
|
||||
% self._string_name)
|
||||
analysis.add(self._name_context, 'name-error', self._name, message)
|
||||
|
||||
return types
|
||||
|
||||
def _get_origin_scope(self):
|
||||
if isinstance(self._name, tree.Name):
|
||||
scope = self._name
|
||||
while scope.parent is not None:
|
||||
# TODO why if classes?
|
||||
if not isinstance(scope, tree.Scope):
|
||||
break
|
||||
scope = scope.parent
|
||||
return scope
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_filters(self, search_global=False):
|
||||
origin_scope = self._get_origin_scope()
|
||||
if search_global:
|
||||
position = self._position
|
||||
|
||||
# For functions and classes the defaults don't belong to the
|
||||
# function and get evaluated in the context before the function. So
|
||||
# make sure to exclude the function/class name.
|
||||
if origin_scope is not None:
|
||||
ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef', 'lambdef')
|
||||
lambdef = None
|
||||
if ancestor == 'lambdef':
|
||||
# For lambdas it's even more complicated since parts will
|
||||
# be evaluated later.
|
||||
lambdef = ancestor
|
||||
ancestor = search_ancestor(origin_scope, 'funcdef', 'classdef')
|
||||
if ancestor is not None:
|
||||
colon = ancestor.children[-2]
|
||||
if position < colon.start_pos:
|
||||
if lambdef is None or position < lambdef.children[-2].start_pos:
|
||||
position = ancestor.start_pos
|
||||
|
||||
return get_global_filters(self._evaluator, self._context, position, origin_scope)
|
||||
else:
|
||||
return self._context.get_filters(search_global, self._position, origin_scope=origin_scope)
|
||||
|
||||
def filter_name(self, filters):
|
||||
"""
|
||||
Searches names that are defined in a scope (the different
|
||||
``filters``), until a name fits.
|
||||
"""
|
||||
names = []
|
||||
if self._context.predefined_names and isinstance(self._name, tree.Name):
|
||||
node = self._name
|
||||
while node is not None and not is_scope(node):
|
||||
node = node.parent
|
||||
if node.type in ("if_stmt", "for_stmt", "comp_for"):
|
||||
try:
|
||||
name_dict = self._context.predefined_names[node]
|
||||
types = name_dict[self._string_name]
|
||||
except KeyError:
|
||||
continue
|
||||
else:
|
||||
self._found_predefined_types = types
|
||||
break
|
||||
|
||||
for filter in filters:
|
||||
names = filter.get(self._string_name)
|
||||
if names:
|
||||
if len(names) == 1:
|
||||
n, = names
|
||||
if isinstance(n, TreeNameDefinition):
|
||||
# Something somewhere went terribly wrong. This
|
||||
# typically happens when using goto on an import in an
|
||||
# __init__ file. I think we need a better solution, but
|
||||
# it's kind of hard, because for Jedi it's not clear
|
||||
# that that name has not been defined, yet.
|
||||
if n.tree_name == self._name:
|
||||
if self._name.get_definition().type == 'import_from':
|
||||
continue
|
||||
break
|
||||
|
||||
debug.dbg('finder.filter_name %s in (%s): %s@%s',
|
||||
self._string_name, self._context, names, self._position)
|
||||
return list(names)
|
||||
|
||||
def _check_getattr(self, inst):
|
||||
"""Checks for both __getattr__ and __getattribute__ methods"""
|
||||
# str is important, because it shouldn't be `Name`!
|
||||
name = compiled.create_simple_object(self._evaluator, self._string_name)
|
||||
|
||||
# This is a little bit special. `__getattribute__` is in Python
|
||||
# executed before `__getattr__`. But: I know no use case, where
|
||||
# this could be practical and where Jedi would return wrong types.
|
||||
# If you ever find something, let me know!
|
||||
# We are inversing this, because a hand-crafted `__getattribute__`
|
||||
# could still call another hand-crafted `__getattr__`, but not the
|
||||
# other way around.
|
||||
names = (inst.get_function_slot_names(u'__getattr__') or
|
||||
inst.get_function_slot_names(u'__getattribute__'))
|
||||
return inst.execute_function_slots(names, name)
|
||||
|
||||
def _names_to_types(self, names, attribute_lookup):
|
||||
contexts = ContextSet.from_sets(name.infer() for name in names)
|
||||
|
||||
debug.dbg('finder._names_to_types: %s -> %s', names, contexts)
|
||||
if not names and isinstance(self._context, AbstractInstanceContext):
|
||||
# handling __getattr__ / __getattribute__
|
||||
return self._check_getattr(self._context)
|
||||
|
||||
# Add isinstance and other if/assert knowledge.
|
||||
if not contexts and isinstance(self._name, tree.Name) and \
|
||||
not isinstance(self._name_context, AbstractInstanceContext):
|
||||
flow_scope = self._name
|
||||
base_node = self._name_context.tree_node
|
||||
if base_node.type == 'comp_for':
|
||||
return contexts
|
||||
while True:
|
||||
flow_scope = get_parent_scope(flow_scope, include_flows=True)
|
||||
n = _check_flow_information(self._name_context, flow_scope,
|
||||
self._name, self._position)
|
||||
if n is not None:
|
||||
return n
|
||||
if flow_scope == base_node:
|
||||
break
|
||||
return contexts
|
||||
|
||||
|
||||
def _check_flow_information(context, flow, search_name, pos):
|
||||
""" Try to find out the type of a variable just with the information that
|
||||
is given by the flows: e.g. It is also responsible for assert checks.::
|
||||
|
||||
if isinstance(k, str):
|
||||
k. # <- completion here
|
||||
|
||||
ensures that `k` is a string.
|
||||
"""
|
||||
if not settings.dynamic_flow_information:
|
||||
return None
|
||||
|
||||
result = None
|
||||
if is_scope(flow):
|
||||
# Check for asserts.
|
||||
module_node = flow.get_root_node()
|
||||
try:
|
||||
names = module_node.get_used_names()[search_name.value]
|
||||
except KeyError:
|
||||
return None
|
||||
names = reversed([
|
||||
n for n in names
|
||||
if flow.start_pos <= n.start_pos < (pos or flow.end_pos)
|
||||
])
|
||||
|
||||
for name in names:
|
||||
ass = search_ancestor(name, 'assert_stmt')
|
||||
if ass is not None:
|
||||
result = _check_isinstance_type(context, ass.assertion, search_name)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
if flow.type in ('if_stmt', 'while_stmt'):
|
||||
potential_ifs = [c for c in flow.children[1::4] if c != ':']
|
||||
for if_test in reversed(potential_ifs):
|
||||
if search_name.start_pos > if_test.end_pos:
|
||||
return _check_isinstance_type(context, if_test, search_name)
|
||||
return result
|
||||
|
||||
|
||||
def _check_isinstance_type(context, element, search_name):
|
||||
try:
|
||||
assert element.type in ('power', 'atom_expr')
|
||||
# this might be removed if we analyze and, etc
|
||||
assert len(element.children) == 2
|
||||
first, trailer = element.children
|
||||
assert first.type == 'name' and first.value == 'isinstance'
|
||||
assert trailer.type == 'trailer' and trailer.children[0] == '('
|
||||
assert len(trailer.children) == 3
|
||||
|
||||
# arglist stuff
|
||||
arglist = trailer.children[1]
|
||||
args = TreeArguments(context.evaluator, context, arglist, trailer)
|
||||
param_list = list(args.unpack())
|
||||
# Disallow keyword arguments
|
||||
assert len(param_list) == 2
|
||||
(key1, lazy_context_object), (key2, lazy_context_cls) = param_list
|
||||
assert key1 is None and key2 is None
|
||||
call = helpers.call_of_leaf(search_name)
|
||||
is_instance_call = helpers.call_of_leaf(lazy_context_object.data)
|
||||
# Do a simple get_code comparison. They should just have the same code,
|
||||
# and everything will be all right.
|
||||
normalize = context.evaluator.grammar._normalize
|
||||
assert normalize(is_instance_call) == normalize(call)
|
||||
except AssertionError:
|
||||
return None
|
||||
|
||||
context_set = ContextSet()
|
||||
for cls_or_tup in lazy_context_cls.infer():
|
||||
if isinstance(cls_or_tup, iterable.Sequence) and cls_or_tup.array_type == 'tuple':
|
||||
for lazy_context in cls_or_tup.py__iter__():
|
||||
for context in lazy_context.infer():
|
||||
context_set |= context.execute_evaluated()
|
||||
else:
|
||||
context_set |= cls_or_tup.execute_evaluated()
|
||||
return context_set
|
||||
@@ -1,239 +0,0 @@
|
||||
import copy
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
from itertools import chain
|
||||
from contextlib import contextmanager
|
||||
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import unicode
|
||||
from jedi.parser_utils import get_parent_scope
|
||||
|
||||
|
||||
def is_stdlib_path(path):
|
||||
# Python standard library paths look like this:
|
||||
# /usr/lib/python3.5/...
|
||||
# TODO The implementation below is probably incorrect and not complete.
|
||||
if 'dist-packages' in path or 'site-packages' in path:
|
||||
return False
|
||||
|
||||
base_path = os.path.join(sys.prefix, 'lib', 'python')
|
||||
return bool(re.match(re.escape(base_path) + r'\d.\d', path))
|
||||
|
||||
|
||||
def deep_ast_copy(obj):
|
||||
"""
|
||||
Much, much faster than copy.deepcopy, but just for parser tree nodes.
|
||||
"""
|
||||
# If it's already in the cache, just return it.
|
||||
new_obj = copy.copy(obj)
|
||||
|
||||
# Copy children
|
||||
new_children = []
|
||||
for child in obj.children:
|
||||
if isinstance(child, tree.Leaf):
|
||||
new_child = copy.copy(child)
|
||||
new_child.parent = new_obj
|
||||
else:
|
||||
new_child = deep_ast_copy(child)
|
||||
new_child.parent = new_obj
|
||||
new_children.append(new_child)
|
||||
new_obj.children = new_children
|
||||
|
||||
return new_obj
|
||||
|
||||
|
||||
def evaluate_call_of_leaf(context, leaf, cut_own_trailer=False):
|
||||
"""
|
||||
Creates a "call" node that consist of all ``trailer`` and ``power``
|
||||
objects. E.g. if you call it with ``append``::
|
||||
|
||||
list([]).append(3) or None
|
||||
|
||||
You would get a node with the content ``list([]).append`` back.
|
||||
|
||||
This generates a copy of the original ast node.
|
||||
|
||||
If you're using the leaf, e.g. the bracket `)` it will return ``list([])``.
|
||||
|
||||
We use this function for two purposes. Given an expression ``bar.foo``,
|
||||
we may want to
|
||||
- infer the type of ``foo`` to offer completions after foo
|
||||
- infer the type of ``bar`` to be able to jump to the definition of foo
|
||||
The option ``cut_own_trailer`` must be set to true for the second purpose.
|
||||
"""
|
||||
trailer = leaf.parent
|
||||
if trailer.type == 'fstring':
|
||||
from jedi.evaluate import compiled
|
||||
return compiled.get_string_context_set(context.evaluator)
|
||||
|
||||
# The leaf may not be the last or first child, because there exist three
|
||||
# different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples
|
||||
# we should not match anything more than x.
|
||||
if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]):
|
||||
if trailer.type == 'atom':
|
||||
return context.eval_node(trailer)
|
||||
return context.eval_node(leaf)
|
||||
|
||||
power = trailer.parent
|
||||
index = power.children.index(trailer)
|
||||
if cut_own_trailer:
|
||||
cut = index
|
||||
else:
|
||||
cut = index + 1
|
||||
|
||||
if power.type == 'error_node':
|
||||
start = index
|
||||
while True:
|
||||
start -= 1
|
||||
base = power.children[start]
|
||||
if base.type != 'trailer':
|
||||
break
|
||||
trailers = power.children[start + 1: index + 1]
|
||||
else:
|
||||
base = power.children[0]
|
||||
trailers = power.children[1:cut]
|
||||
|
||||
if base == 'await':
|
||||
base = trailers[0]
|
||||
trailers = trailers[1:]
|
||||
|
||||
values = context.eval_node(base)
|
||||
from jedi.evaluate.syntax_tree import eval_trailer
|
||||
for trailer in trailers:
|
||||
values = eval_trailer(context, values, trailer)
|
||||
return values
|
||||
|
||||
|
||||
def call_of_leaf(leaf):
|
||||
"""
|
||||
Creates a "call" node that consist of all ``trailer`` and ``power``
|
||||
objects. E.g. if you call it with ``append``::
|
||||
|
||||
list([]).append(3) or None
|
||||
|
||||
You would get a node with the content ``list([]).append`` back.
|
||||
|
||||
This generates a copy of the original ast node.
|
||||
|
||||
If you're using the leaf, e.g. the bracket `)` it will return ``list([])``.
|
||||
"""
|
||||
# TODO this is the old version of this call. Try to remove it.
|
||||
trailer = leaf.parent
|
||||
# The leaf may not be the last or first child, because there exist three
|
||||
# different trailers: `( x )`, `[ x ]` and `.x`. In the first two examples
|
||||
# we should not match anything more than x.
|
||||
if trailer.type != 'trailer' or leaf not in (trailer.children[0], trailer.children[-1]):
|
||||
if trailer.type == 'atom':
|
||||
return trailer
|
||||
return leaf
|
||||
|
||||
power = trailer.parent
|
||||
index = power.children.index(trailer)
|
||||
|
||||
new_power = copy.copy(power)
|
||||
new_power.children = list(new_power.children)
|
||||
new_power.children[index + 1:] = []
|
||||
|
||||
if power.type == 'error_node':
|
||||
start = index
|
||||
while True:
|
||||
start -= 1
|
||||
if power.children[start].type != 'trailer':
|
||||
break
|
||||
transformed = tree.Node('power', power.children[start:])
|
||||
transformed.parent = power.parent
|
||||
return transformed
|
||||
|
||||
return power
|
||||
|
||||
|
||||
def get_names_of_node(node):
|
||||
try:
|
||||
children = node.children
|
||||
except AttributeError:
|
||||
if node.type == 'name':
|
||||
return [node]
|
||||
else:
|
||||
return []
|
||||
else:
|
||||
return list(chain.from_iterable(get_names_of_node(c) for c in children))
|
||||
|
||||
|
||||
def get_module_names(module, all_scopes):
|
||||
"""
|
||||
Returns a dictionary with name parts as keys and their call paths as
|
||||
values.
|
||||
"""
|
||||
names = chain.from_iterable(module.get_used_names().values())
|
||||
if not all_scopes:
|
||||
# We have to filter all the names that don't have the module as a
|
||||
# parent_scope. There's None as a parent, because nodes in the module
|
||||
# node have the parent module and not suite as all the others.
|
||||
# Therefore it's important to catch that case.
|
||||
names = [n for n in names if get_parent_scope(n).parent in (module, None)]
|
||||
return names
|
||||
|
||||
|
||||
@contextmanager
|
||||
def predefine_names(context, flow_scope, dct):
|
||||
predefined = context.predefined_names
|
||||
predefined[flow_scope] = dct
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
del predefined[flow_scope]
|
||||
|
||||
|
||||
def is_compiled(context):
|
||||
from jedi.evaluate.compiled import CompiledObject
|
||||
return isinstance(context, CompiledObject)
|
||||
|
||||
|
||||
def is_string(context):
|
||||
if context.evaluator.environment.version_info.major == 2:
|
||||
str_classes = (unicode, bytes)
|
||||
else:
|
||||
str_classes = (unicode,)
|
||||
return is_compiled(context) and isinstance(context.get_safe_value(default=None), str_classes)
|
||||
|
||||
|
||||
def is_literal(context):
|
||||
return is_number(context) or is_string(context)
|
||||
|
||||
|
||||
def _get_safe_value_or_none(context, accept):
|
||||
if is_compiled(context):
|
||||
value = context.get_safe_value(default=None)
|
||||
if isinstance(value, accept):
|
||||
return value
|
||||
|
||||
|
||||
def get_int_or_none(context):
|
||||
return _get_safe_value_or_none(context, int)
|
||||
|
||||
|
||||
def is_number(context):
|
||||
return _get_safe_value_or_none(context, (int, float)) is not None
|
||||
|
||||
|
||||
class EvaluatorTypeError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class EvaluatorIndexError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class EvaluatorKeyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def reraise_as_evaluator(*exception_classes):
|
||||
try:
|
||||
yield
|
||||
except exception_classes as e:
|
||||
new_exc_cls = globals()['Evaluator' + e.__class__.__name__]
|
||||
raise new_exc_cls(e)
|
||||
@@ -1,594 +0,0 @@
|
||||
"""
|
||||
:mod:`jedi.evaluate.imports` is here to resolve import statements and return
|
||||
the modules/classes/functions/whatever, which they stand for. However there's
|
||||
not any actual importing done. This module is about finding modules in the
|
||||
filesystem. This can be quite tricky sometimes, because Python imports are not
|
||||
always that simple.
|
||||
|
||||
This module uses imp for python up to 3.2 and importlib for python 3.3 on; the
|
||||
correct implementation is delegated to _compatibility.
|
||||
|
||||
This module also supports import autocompletion, which means to complete
|
||||
statements like ``from datetim`` (cursor at the end would return ``datetime``).
|
||||
"""
|
||||
import os
|
||||
|
||||
from parso.python import tree
|
||||
from parso.tree import search_ancestor
|
||||
from parso import python_bytes_to_unicode
|
||||
|
||||
from jedi._compatibility import (FileNotFoundError, ImplicitNSInfo,
|
||||
force_unicode, unicode)
|
||||
from jedi import debug
|
||||
from jedi import settings
|
||||
from jedi.parser_utils import get_cached_code_lines
|
||||
from jedi.evaluate import sys_path
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate.utils import unite
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate.filters import AbstractNameDefinition
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
|
||||
|
||||
class ModuleCache(object):
|
||||
def __init__(self):
|
||||
self._path_cache = {}
|
||||
self._name_cache = {}
|
||||
|
||||
def add(self, module, name):
|
||||
path = module.py__file__()
|
||||
self._path_cache[path] = module
|
||||
self._name_cache[name] = module
|
||||
|
||||
def iterate_modules_with_names(self):
|
||||
return self._name_cache.items()
|
||||
|
||||
def get(self, name):
|
||||
return self._name_cache[name]
|
||||
|
||||
def get_from_path(self, path):
|
||||
return self._path_cache[path]
|
||||
|
||||
|
||||
# This memoization is needed, because otherwise we will infinitely loop on
|
||||
# certain imports.
|
||||
@evaluator_method_cache(default=NO_CONTEXTS)
|
||||
def infer_import(context, tree_name, is_goto=False):
|
||||
module_context = context.get_root_context()
|
||||
import_node = search_ancestor(tree_name, 'import_name', 'import_from')
|
||||
import_path = import_node.get_path_for_name(tree_name)
|
||||
from_import_name = None
|
||||
evaluator = context.evaluator
|
||||
try:
|
||||
from_names = import_node.get_from_names()
|
||||
except AttributeError:
|
||||
# Is an import_name
|
||||
pass
|
||||
else:
|
||||
if len(from_names) + 1 == len(import_path):
|
||||
# We have to fetch the from_names part first and then check
|
||||
# if from_names exists in the modules.
|
||||
from_import_name = import_path[-1]
|
||||
import_path = from_names
|
||||
|
||||
importer = Importer(evaluator, tuple(import_path),
|
||||
module_context, import_node.level)
|
||||
|
||||
types = importer.follow()
|
||||
|
||||
#if import_node.is_nested() and not self.nested_resolve:
|
||||
# scopes = [NestedImportModule(module, import_node)]
|
||||
|
||||
if not types:
|
||||
return NO_CONTEXTS
|
||||
|
||||
if from_import_name is not None:
|
||||
types = unite(
|
||||
t.py__getattribute__(
|
||||
from_import_name,
|
||||
name_context=context,
|
||||
is_goto=is_goto,
|
||||
analysis_errors=False
|
||||
)
|
||||
for t in types
|
||||
)
|
||||
if not is_goto:
|
||||
types = ContextSet.from_set(types)
|
||||
|
||||
if not types:
|
||||
path = import_path + [from_import_name]
|
||||
importer = Importer(evaluator, tuple(path),
|
||||
module_context, import_node.level)
|
||||
types = importer.follow()
|
||||
# goto only accepts `Name`
|
||||
if is_goto:
|
||||
types = set(s.name for s in types)
|
||||
else:
|
||||
# goto only accepts `Name`
|
||||
if is_goto:
|
||||
types = set(s.name for s in types)
|
||||
|
||||
debug.dbg('after import: %s', types)
|
||||
return types
|
||||
|
||||
|
||||
class NestedImportModule(tree.Module):
|
||||
"""
|
||||
TODO while there's no use case for nested import module right now, we might
|
||||
be able to use them for static analysis checks later on.
|
||||
"""
|
||||
def __init__(self, module, nested_import):
|
||||
self._module = module
|
||||
self._nested_import = nested_import
|
||||
|
||||
def _get_nested_import_name(self):
|
||||
"""
|
||||
Generates an Import statement, that can be used to fake nested imports.
|
||||
"""
|
||||
i = self._nested_import
|
||||
# This is not an existing Import statement. Therefore, set position to
|
||||
# 0 (0 is not a valid line number).
|
||||
zero = (0, 0)
|
||||
names = [unicode(name) for name in i.namespace_names[1:]]
|
||||
name = helpers.FakeName(names, self._nested_import)
|
||||
new = tree.Import(i._sub_module, zero, zero, name)
|
||||
new.parent = self._module
|
||||
debug.dbg('Generated a nested import: %s', new)
|
||||
return helpers.FakeName(str(i.namespace_names[1]), new)
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._module, name)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s: %s of %s>" % (self.__class__.__name__, self._module,
|
||||
self._nested_import)
|
||||
|
||||
|
||||
def _add_error(context, name, message=None):
|
||||
# Should be a name, not a string!
|
||||
if message is None:
|
||||
name_str = str(name.value) if isinstance(name, tree.Name) else name
|
||||
message = 'No module named ' + name_str
|
||||
if hasattr(name, 'parent'):
|
||||
analysis.add(context, 'import-error', name, message)
|
||||
else:
|
||||
debug.warning('ImportError without origin: ' + message)
|
||||
|
||||
|
||||
class ImportName(AbstractNameDefinition):
|
||||
start_pos = (1, 0)
|
||||
_level = 0
|
||||
|
||||
def __init__(self, parent_context, string_name):
|
||||
self.parent_context = parent_context
|
||||
self.string_name = string_name
|
||||
|
||||
def infer(self):
|
||||
return Importer(
|
||||
self.parent_context.evaluator,
|
||||
[self.string_name],
|
||||
self.parent_context,
|
||||
level=self._level,
|
||||
).follow()
|
||||
|
||||
def goto(self):
|
||||
return [m.name for m in self.infer()]
|
||||
|
||||
def get_root_context(self):
|
||||
# Not sure if this is correct.
|
||||
return self.parent_context.get_root_context()
|
||||
|
||||
@property
|
||||
def api_type(self):
|
||||
return 'module'
|
||||
|
||||
|
||||
class SubModuleName(ImportName):
|
||||
_level = 1
|
||||
|
||||
|
||||
class Importer(object):
|
||||
def __init__(self, evaluator, import_path, module_context, level=0):
|
||||
"""
|
||||
An implementation similar to ``__import__``. Use `follow`
|
||||
to actually follow the imports.
|
||||
|
||||
*level* specifies whether to use absolute or relative imports. 0 (the
|
||||
default) means only perform absolute imports. Positive values for level
|
||||
indicate the number of parent directories to search relative to the
|
||||
directory of the module calling ``__import__()`` (see PEP 328 for the
|
||||
details).
|
||||
|
||||
:param import_path: List of namespaces (strings or Names).
|
||||
"""
|
||||
debug.speed('import %s' % (import_path,))
|
||||
self._evaluator = evaluator
|
||||
self.level = level
|
||||
self.module_context = module_context
|
||||
try:
|
||||
self.file_path = module_context.py__file__()
|
||||
except AttributeError:
|
||||
# Can be None for certain compiled modules like 'builtins'.
|
||||
self.file_path = None
|
||||
|
||||
if level:
|
||||
base = module_context.py__package__().split('.')
|
||||
if base == [''] or base == ['__main__']:
|
||||
base = []
|
||||
if level > len(base):
|
||||
path = module_context.py__file__()
|
||||
if path is not None:
|
||||
import_path = list(import_path)
|
||||
p = path
|
||||
for i in range(level):
|
||||
p = os.path.dirname(p)
|
||||
dir_name = os.path.basename(p)
|
||||
# This is not the proper way to do relative imports. However, since
|
||||
# Jedi cannot be sure about the entry point, we just calculate an
|
||||
# absolute path here.
|
||||
if dir_name:
|
||||
# TODO those sys.modules modifications are getting
|
||||
# really stupid. this is the 3rd time that we're using
|
||||
# this. We should probably refactor.
|
||||
if path.endswith(os.path.sep + 'os.py'):
|
||||
import_path.insert(0, 'os')
|
||||
else:
|
||||
import_path.insert(0, dir_name)
|
||||
else:
|
||||
_add_error(
|
||||
module_context, import_path[-1],
|
||||
message='Attempted relative import beyond top-level package.'
|
||||
)
|
||||
import_path = []
|
||||
# If no path is defined in the module we have no ideas where we
|
||||
# are in the file system. Therefore we cannot know what to do.
|
||||
# In this case we just let the path there and ignore that it's
|
||||
# a relative path. Not sure if that's a good idea.
|
||||
else:
|
||||
# Here we basically rewrite the level to 0.
|
||||
base = tuple(base)
|
||||
if level > 1:
|
||||
base = base[:-level + 1]
|
||||
|
||||
import_path = base + tuple(import_path)
|
||||
self.import_path = import_path
|
||||
|
||||
@property
|
||||
def str_import_path(self):
|
||||
"""Returns the import path as pure strings instead of `Name`."""
|
||||
return tuple(
|
||||
name.value if isinstance(name, tree.Name) else name
|
||||
for name in self.import_path
|
||||
)
|
||||
|
||||
def sys_path_with_modifications(self):
|
||||
|
||||
sys_path_mod = (
|
||||
self._evaluator.get_sys_path()
|
||||
+ sys_path.check_sys_path_modifications(self.module_context)
|
||||
)
|
||||
|
||||
if self.import_path and self.file_path is not None \
|
||||
and self._evaluator.environment.version_info.major == 2:
|
||||
# Python2 uses an old strange way of importing relative imports.
|
||||
sys_path_mod.append(force_unicode(os.path.dirname(self.file_path)))
|
||||
|
||||
return sys_path_mod
|
||||
|
||||
def follow(self):
|
||||
if not self.import_path or not self._evaluator.infer_enabled:
|
||||
return NO_CONTEXTS
|
||||
|
||||
return self._do_import(self.import_path, self.sys_path_with_modifications())
|
||||
|
||||
def _do_import(self, import_path, sys_path):
|
||||
"""
|
||||
This method is very similar to importlib's `_gcd_import`.
|
||||
"""
|
||||
import_parts = [
|
||||
force_unicode(i.value if isinstance(i, tree.Name) else i)
|
||||
for i in import_path
|
||||
]
|
||||
|
||||
# Handle "magic" Flask extension imports:
|
||||
# ``flask.ext.foo`` is really ``flask_foo`` or ``flaskext.foo``.
|
||||
if len(import_path) > 2 and import_parts[:2] == ['flask', 'ext']:
|
||||
# New style.
|
||||
ipath = ('flask_' + str(import_parts[2]),) + import_path[3:]
|
||||
modules = self._do_import(ipath, sys_path)
|
||||
if modules:
|
||||
return modules
|
||||
else:
|
||||
# Old style
|
||||
return self._do_import(('flaskext',) + import_path[2:], sys_path)
|
||||
|
||||
if import_parts[0] in settings.auto_import_modules:
|
||||
module = _load_module(
|
||||
self._evaluator,
|
||||
import_names=import_parts,
|
||||
sys_path=sys_path,
|
||||
)
|
||||
return ContextSet(module)
|
||||
|
||||
module_name = '.'.join(import_parts)
|
||||
try:
|
||||
return ContextSet(self._evaluator.module_cache.get(module_name))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if len(import_path) > 1:
|
||||
# This is a recursive way of importing that works great with
|
||||
# the module cache.
|
||||
bases = self._do_import(import_path[:-1], sys_path)
|
||||
if not bases:
|
||||
return NO_CONTEXTS
|
||||
# We can take the first element, because only the os special
|
||||
# case yields multiple modules, which is not important for
|
||||
# further imports.
|
||||
parent_module = list(bases)[0]
|
||||
|
||||
# This is a huge exception, we follow a nested import
|
||||
# ``os.path``, because it's a very important one in Python
|
||||
# that is being achieved by messing with ``sys.modules`` in
|
||||
# ``os``.
|
||||
if import_parts == ['os', 'path']:
|
||||
return parent_module.py__getattribute__('path')
|
||||
|
||||
try:
|
||||
method = parent_module.py__path__
|
||||
except AttributeError:
|
||||
# The module is not a package.
|
||||
_add_error(self.module_context, import_path[-1])
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
paths = method()
|
||||
debug.dbg('search_module %s in paths %s', module_name, paths)
|
||||
for path in paths:
|
||||
# At the moment we are only using one path. So this is
|
||||
# not important to be correct.
|
||||
if not isinstance(path, list):
|
||||
path = [path]
|
||||
code, module_path, is_pkg = self._evaluator.compiled_subprocess.get_module_info(
|
||||
string=import_parts[-1],
|
||||
path=path,
|
||||
full_name=module_name,
|
||||
is_global_search=False,
|
||||
)
|
||||
if module_path is not None:
|
||||
break
|
||||
else:
|
||||
_add_error(self.module_context, import_path[-1])
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
debug.dbg('global search_module %s in %s', import_parts[-1], self.file_path)
|
||||
# Override the sys.path. It works only good that way.
|
||||
# Injecting the path directly into `find_module` did not work.
|
||||
code, module_path, is_pkg = self._evaluator.compiled_subprocess.get_module_info(
|
||||
string=import_parts[-1],
|
||||
full_name=module_name,
|
||||
sys_path=sys_path,
|
||||
is_global_search=True,
|
||||
)
|
||||
if module_path is None:
|
||||
# The module is not a package.
|
||||
_add_error(self.module_context, import_path[-1])
|
||||
return NO_CONTEXTS
|
||||
|
||||
module = _load_module(
|
||||
self._evaluator, module_path, code, sys_path,
|
||||
import_names=import_parts,
|
||||
safe_module_name=True,
|
||||
)
|
||||
|
||||
if module is None:
|
||||
# The file might raise an ImportError e.g. and therefore not be
|
||||
# importable.
|
||||
return NO_CONTEXTS
|
||||
|
||||
return ContextSet(module)
|
||||
|
||||
def _generate_name(self, name, in_module=None):
|
||||
# Create a pseudo import to be able to follow them.
|
||||
if in_module is None:
|
||||
return ImportName(self.module_context, name)
|
||||
return SubModuleName(in_module, name)
|
||||
|
||||
def _get_module_names(self, search_path=None, in_module=None):
|
||||
"""
|
||||
Get the names of all modules in the search_path. This means file names
|
||||
and not names defined in the files.
|
||||
"""
|
||||
sub = self._evaluator.compiled_subprocess
|
||||
|
||||
names = []
|
||||
# add builtin module names
|
||||
if search_path is None and in_module is None:
|
||||
names += [self._generate_name(name) for name in sub.get_builtin_module_names()]
|
||||
|
||||
if search_path is None:
|
||||
search_path = self.sys_path_with_modifications()
|
||||
|
||||
for name in sub.list_module_names(search_path):
|
||||
names.append(self._generate_name(name, in_module=in_module))
|
||||
return names
|
||||
|
||||
def completion_names(self, evaluator, only_modules=False):
|
||||
"""
|
||||
:param only_modules: Indicates wheter it's possible to import a
|
||||
definition that is not defined in a module.
|
||||
"""
|
||||
from jedi.evaluate.context import ModuleContext
|
||||
from jedi.evaluate.context.namespace import ImplicitNamespaceContext
|
||||
names = []
|
||||
if self.import_path:
|
||||
# flask
|
||||
if self.str_import_path == ('flask', 'ext'):
|
||||
# List Flask extensions like ``flask_foo``
|
||||
for mod in self._get_module_names():
|
||||
modname = mod.string_name
|
||||
if modname.startswith('flask_'):
|
||||
extname = modname[len('flask_'):]
|
||||
names.append(self._generate_name(extname))
|
||||
# Now the old style: ``flaskext.foo``
|
||||
for dir in self.sys_path_with_modifications():
|
||||
flaskext = os.path.join(dir, 'flaskext')
|
||||
if os.path.isdir(flaskext):
|
||||
names += self._get_module_names([flaskext])
|
||||
|
||||
for context in self.follow():
|
||||
# Non-modules are not completable.
|
||||
if context.api_type != 'module': # not a module
|
||||
continue
|
||||
# namespace packages
|
||||
if isinstance(context, ModuleContext) and context.py__file__().endswith('__init__.py'):
|
||||
paths = context.py__path__()
|
||||
names += self._get_module_names(paths, in_module=context)
|
||||
|
||||
# implicit namespace packages
|
||||
elif isinstance(context, ImplicitNamespaceContext):
|
||||
paths = context.paths
|
||||
names += self._get_module_names(paths, in_module=context)
|
||||
|
||||
if only_modules:
|
||||
# In the case of an import like `from x.` we don't need to
|
||||
# add all the variables.
|
||||
if ('os',) == self.str_import_path and not self.level:
|
||||
# os.path is a hardcoded exception, because it's a
|
||||
# ``sys.modules`` modification.
|
||||
names.append(self._generate_name('path', context))
|
||||
|
||||
continue
|
||||
|
||||
for filter in context.get_filters(search_global=False):
|
||||
names += filter.values()
|
||||
else:
|
||||
# Empty import path=completion after import
|
||||
if not self.level:
|
||||
names += self._get_module_names()
|
||||
|
||||
if self.file_path is not None:
|
||||
path = os.path.abspath(self.file_path)
|
||||
for i in range(self.level - 1):
|
||||
path = os.path.dirname(path)
|
||||
names += self._get_module_names([path])
|
||||
|
||||
return names
|
||||
|
||||
|
||||
def _load_module(evaluator, path=None, code=None, sys_path=None,
|
||||
import_names=None, safe_module_name=False):
|
||||
if import_names is None:
|
||||
dotted_name = None
|
||||
else:
|
||||
dotted_name = '.'.join(import_names)
|
||||
try:
|
||||
return evaluator.module_cache.get(dotted_name)
|
||||
except KeyError:
|
||||
pass
|
||||
try:
|
||||
return evaluator.module_cache.get_from_path(path)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if isinstance(path, ImplicitNSInfo):
|
||||
from jedi.evaluate.context.namespace import ImplicitNamespaceContext
|
||||
module = ImplicitNamespaceContext(
|
||||
evaluator,
|
||||
fullname=path.name,
|
||||
paths=path.paths,
|
||||
)
|
||||
else:
|
||||
if sys_path is None:
|
||||
sys_path = evaluator.get_sys_path()
|
||||
|
||||
if path is not None and path.endswith(('.py', '.zip', '.egg')):
|
||||
module_node = evaluator.parse(
|
||||
code=code, path=path, cache=True,
|
||||
diff_cache=settings.fast_parser,
|
||||
cache_path=settings.cache_directory)
|
||||
|
||||
from jedi.evaluate.context import ModuleContext
|
||||
module = ModuleContext(
|
||||
evaluator, module_node,
|
||||
path=path,
|
||||
code_lines=get_cached_code_lines(evaluator.grammar, path),
|
||||
)
|
||||
else:
|
||||
assert dotted_name is not None
|
||||
module = compiled.load_module(evaluator, dotted_name=dotted_name, sys_path=sys_path)
|
||||
|
||||
if module is not None and dotted_name is not None:
|
||||
add_module_to_cache(evaluator, dotted_name, module, safe=safe_module_name)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
def add_module_to_cache(evaluator, module_name, module, safe=False):
|
||||
if not safe and '.' not in module_name:
|
||||
# We cannot add paths with dots, because that would collide with
|
||||
# the sepatator dots for nested packages. Therefore we return
|
||||
# `__main__` in ModuleWrapper.py__name__(), which is similar to
|
||||
# Python behavior.
|
||||
return
|
||||
evaluator.module_cache.add(module, module_name)
|
||||
|
||||
|
||||
def get_modules_containing_name(evaluator, modules, name):
|
||||
"""
|
||||
Search a name in the directories of modules.
|
||||
"""
|
||||
def check_directories(paths):
|
||||
for p in paths:
|
||||
if p is not None:
|
||||
# We need abspath, because the seetings paths might not already
|
||||
# have been converted to absolute paths.
|
||||
d = os.path.dirname(os.path.abspath(p))
|
||||
for file_name in os.listdir(d):
|
||||
path = os.path.join(d, file_name)
|
||||
if file_name.endswith('.py'):
|
||||
yield path
|
||||
|
||||
def check_fs(path):
|
||||
try:
|
||||
f = open(path, 'rb')
|
||||
except FileNotFoundError:
|
||||
return
|
||||
with f:
|
||||
code = python_bytes_to_unicode(f.read(), errors='replace')
|
||||
if name in code:
|
||||
e_sys_path = evaluator.get_sys_path()
|
||||
import_names = sys_path.dotted_path_in_sys_path(e_sys_path, path)
|
||||
module = _load_module(
|
||||
evaluator, path, code,
|
||||
sys_path=e_sys_path,
|
||||
import_names=import_names,
|
||||
)
|
||||
return module
|
||||
|
||||
# skip non python modules
|
||||
used_mod_paths = set()
|
||||
for m in modules:
|
||||
try:
|
||||
path = m.py__file__()
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
used_mod_paths.add(path)
|
||||
yield m
|
||||
|
||||
if not settings.dynamic_params_for_other_modules:
|
||||
return
|
||||
|
||||
additional = set(os.path.abspath(p) for p in settings.additional_dynamic_modules)
|
||||
# Check the directories of used modules.
|
||||
paths = (additional | set(check_directories(used_mod_paths))) \
|
||||
- used_mod_paths
|
||||
|
||||
# Sort here to make issues less random.
|
||||
for p in sorted(paths):
|
||||
# make testing easier, sort it - same results on every interpreter
|
||||
m = check_fs(p)
|
||||
if m is not None and not isinstance(m, compiled.CompiledObject):
|
||||
yield m
|
||||
@@ -1,104 +0,0 @@
|
||||
"""
|
||||
This module is not intended to be used in jedi, rather it will be fed to the
|
||||
jedi-parser to replace classes in the typing module
|
||||
"""
|
||||
|
||||
try:
|
||||
from collections import abc
|
||||
except ImportError:
|
||||
# python 2
|
||||
import collections as abc
|
||||
|
||||
|
||||
def factory(typing_name, indextypes):
|
||||
class Iterable(abc.Iterable):
|
||||
def __iter__(self):
|
||||
while True:
|
||||
yield indextypes[0]()
|
||||
|
||||
class Iterator(Iterable, abc.Iterator):
|
||||
def next(self):
|
||||
""" needed for python 2 """
|
||||
return self.__next__()
|
||||
|
||||
def __next__(self):
|
||||
return indextypes[0]()
|
||||
|
||||
class Sequence(abc.Sequence):
|
||||
def __getitem__(self, index):
|
||||
return indextypes[0]()
|
||||
|
||||
class MutableSequence(Sequence, abc.MutableSequence):
|
||||
pass
|
||||
|
||||
class List(MutableSequence, list):
|
||||
pass
|
||||
|
||||
class Tuple(Sequence, tuple):
|
||||
def __getitem__(self, index):
|
||||
if indextypes[1] == Ellipsis:
|
||||
# https://www.python.org/dev/peps/pep-0484/#the-typing-module
|
||||
# Tuple[int, ...] means a tuple of ints of indetermined length
|
||||
return indextypes[0]()
|
||||
else:
|
||||
return indextypes[index]()
|
||||
|
||||
class AbstractSet(Iterable, abc.Set):
|
||||
pass
|
||||
|
||||
class MutableSet(AbstractSet, abc.MutableSet):
|
||||
pass
|
||||
|
||||
class KeysView(Iterable, abc.KeysView):
|
||||
pass
|
||||
|
||||
class ValuesView(abc.ValuesView):
|
||||
def __iter__(self):
|
||||
while True:
|
||||
yield indextypes[1]()
|
||||
|
||||
class ItemsView(abc.ItemsView):
|
||||
def __iter__(self):
|
||||
while True:
|
||||
yield (indextypes[0](), indextypes[1]())
|
||||
|
||||
class Mapping(Iterable, abc.Mapping):
|
||||
def __getitem__(self, item):
|
||||
return indextypes[1]()
|
||||
|
||||
def keys(self):
|
||||
return KeysView()
|
||||
|
||||
def values(self):
|
||||
return ValuesView()
|
||||
|
||||
def items(self):
|
||||
return ItemsView()
|
||||
|
||||
class MutableMapping(Mapping, abc.MutableMapping):
|
||||
pass
|
||||
|
||||
class Dict(MutableMapping, dict):
|
||||
pass
|
||||
|
||||
class DefaultDict(MutableMapping, dict):
|
||||
pass
|
||||
|
||||
dct = {
|
||||
"Sequence": Sequence,
|
||||
"MutableSequence": MutableSequence,
|
||||
"List": List,
|
||||
"Iterable": Iterable,
|
||||
"Iterator": Iterator,
|
||||
"AbstractSet": AbstractSet,
|
||||
"MutableSet": MutableSet,
|
||||
"Mapping": Mapping,
|
||||
"MutableMapping": MutableMapping,
|
||||
"Tuple": Tuple,
|
||||
"KeysView": KeysView,
|
||||
"ItemsView": ItemsView,
|
||||
"ValuesView": ValuesView,
|
||||
"Dict": Dict,
|
||||
"DefaultDict": DefaultDict,
|
||||
}
|
||||
return dct[typing_name]
|
||||
@@ -1,59 +0,0 @@
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS
|
||||
from jedi.common.utils import monkeypatch
|
||||
|
||||
|
||||
class AbstractLazyContext(object):
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.data)
|
||||
|
||||
def infer(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class LazyKnownContext(AbstractLazyContext):
|
||||
"""data is a context."""
|
||||
def infer(self):
|
||||
return ContextSet(self.data)
|
||||
|
||||
|
||||
class LazyKnownContexts(AbstractLazyContext):
|
||||
"""data is a ContextSet."""
|
||||
def infer(self):
|
||||
return self.data
|
||||
|
||||
|
||||
class LazyUnknownContext(AbstractLazyContext):
|
||||
def __init__(self):
|
||||
super(LazyUnknownContext, self).__init__(None)
|
||||
|
||||
def infer(self):
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
class LazyTreeContext(AbstractLazyContext):
|
||||
def __init__(self, context, node):
|
||||
super(LazyTreeContext, self).__init__(node)
|
||||
self._context = context
|
||||
# We need to save the predefined names. It's an unfortunate side effect
|
||||
# that needs to be tracked otherwise results will be wrong.
|
||||
self._predefined_names = dict(context.predefined_names)
|
||||
|
||||
def infer(self):
|
||||
with monkeypatch(self._context, 'predefined_names', self._predefined_names):
|
||||
return self._context.eval_node(self.data)
|
||||
|
||||
|
||||
def get_merged_lazy_context(lazy_contexts):
|
||||
if len(lazy_contexts) > 1:
|
||||
return MergedLazyContexts(lazy_contexts)
|
||||
else:
|
||||
return lazy_contexts[0]
|
||||
|
||||
|
||||
class MergedLazyContexts(AbstractLazyContext):
|
||||
"""data is a list of lazy contexts."""
|
||||
def infer(self):
|
||||
return ContextSet.from_sets(l.infer() for l in self.data)
|
||||
@@ -1,194 +0,0 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from jedi.evaluate.utils import PushBackIterator
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate.lazy_context import LazyKnownContext, \
|
||||
LazyTreeContext, LazyUnknownContext
|
||||
from jedi.evaluate import docstrings
|
||||
from jedi.evaluate import pep0484
|
||||
from jedi.evaluate.context import iterable
|
||||
|
||||
|
||||
def _add_argument_issue(parent_context, error_name, lazy_context, message):
|
||||
if isinstance(lazy_context, LazyTreeContext):
|
||||
node = lazy_context.data
|
||||
if node.parent.type == 'argument':
|
||||
node = node.parent
|
||||
analysis.add(parent_context, error_name, node, message)
|
||||
|
||||
|
||||
class ExecutedParam(object):
|
||||
"""Fake a param and give it values."""
|
||||
def __init__(self, execution_context, param_node, lazy_context):
|
||||
self._execution_context = execution_context
|
||||
self._param_node = param_node
|
||||
self._lazy_context = lazy_context
|
||||
self.string_name = param_node.name.value
|
||||
|
||||
def infer(self):
|
||||
pep0484_hints = pep0484.infer_param(self._execution_context, self._param_node)
|
||||
doc_params = docstrings.infer_param(self._execution_context, self._param_node)
|
||||
if pep0484_hints or doc_params:
|
||||
return pep0484_hints | doc_params
|
||||
|
||||
return self._lazy_context.infer()
|
||||
|
||||
@property
|
||||
def var_args(self):
|
||||
return self._execution_context.var_args
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, self.string_name)
|
||||
|
||||
|
||||
def get_executed_params(execution_context, var_args):
|
||||
result_params = []
|
||||
param_dict = {}
|
||||
funcdef = execution_context.tree_node
|
||||
parent_context = execution_context.parent_context
|
||||
|
||||
for param in funcdef.get_params():
|
||||
param_dict[param.name.value] = param
|
||||
unpacked_va = list(var_args.unpack(funcdef))
|
||||
var_arg_iterator = PushBackIterator(iter(unpacked_va))
|
||||
|
||||
non_matching_keys = defaultdict(lambda: [])
|
||||
keys_used = {}
|
||||
keys_only = False
|
||||
had_multiple_value_error = False
|
||||
for param in funcdef.get_params():
|
||||
# The value and key can both be null. There, the defaults apply.
|
||||
# args / kwargs will just be empty arrays / dicts, respectively.
|
||||
# Wrong value count is just ignored. If you try to test cases that are
|
||||
# not allowed in Python, Jedi will maybe not show any completions.
|
||||
key, argument = next(var_arg_iterator, (None, None))
|
||||
while key is not None:
|
||||
keys_only = True
|
||||
try:
|
||||
key_param = param_dict[key]
|
||||
except KeyError:
|
||||
non_matching_keys[key] = argument
|
||||
else:
|
||||
if key in keys_used:
|
||||
had_multiple_value_error = True
|
||||
m = ("TypeError: %s() got multiple values for keyword argument '%s'."
|
||||
% (funcdef.name, key))
|
||||
for node in var_args.get_calling_nodes():
|
||||
analysis.add(parent_context, 'type-error-multiple-values',
|
||||
node, message=m)
|
||||
else:
|
||||
keys_used[key] = ExecutedParam(execution_context, key_param, argument)
|
||||
key, argument = next(var_arg_iterator, (None, None))
|
||||
|
||||
try:
|
||||
result_params.append(keys_used[param.name.value])
|
||||
continue
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if param.star_count == 1:
|
||||
# *args param
|
||||
lazy_context_list = []
|
||||
if argument is not None:
|
||||
lazy_context_list.append(argument)
|
||||
for key, argument in var_arg_iterator:
|
||||
# Iterate until a key argument is found.
|
||||
if key:
|
||||
var_arg_iterator.push_back((key, argument))
|
||||
break
|
||||
lazy_context_list.append(argument)
|
||||
seq = iterable.FakeSequence(execution_context.evaluator, u'tuple', lazy_context_list)
|
||||
result_arg = LazyKnownContext(seq)
|
||||
elif param.star_count == 2:
|
||||
# **kwargs param
|
||||
dct = iterable.FakeDict(execution_context.evaluator, dict(non_matching_keys))
|
||||
result_arg = LazyKnownContext(dct)
|
||||
non_matching_keys = {}
|
||||
else:
|
||||
# normal param
|
||||
if argument is None:
|
||||
# No value: Return an empty container
|
||||
if param.default is None:
|
||||
result_arg = LazyUnknownContext()
|
||||
if not keys_only:
|
||||
for node in var_args.get_calling_nodes():
|
||||
m = _error_argument_count(funcdef, len(unpacked_va))
|
||||
analysis.add(parent_context, 'type-error-too-few-arguments',
|
||||
node, message=m)
|
||||
else:
|
||||
result_arg = LazyTreeContext(parent_context, param.default)
|
||||
else:
|
||||
result_arg = argument
|
||||
|
||||
result_params.append(ExecutedParam(execution_context, param, result_arg))
|
||||
if not isinstance(result_arg, LazyUnknownContext):
|
||||
keys_used[param.name.value] = result_params[-1]
|
||||
|
||||
if keys_only:
|
||||
# All arguments should be handed over to the next function. It's not
|
||||
# about the values inside, it's about the names. Jedi needs to now that
|
||||
# there's nothing to find for certain names.
|
||||
for k in set(param_dict) - set(keys_used):
|
||||
param = param_dict[k]
|
||||
|
||||
if not (non_matching_keys or had_multiple_value_error or
|
||||
param.star_count or param.default):
|
||||
# add a warning only if there's not another one.
|
||||
for node in var_args.get_calling_nodes():
|
||||
m = _error_argument_count(funcdef, len(unpacked_va))
|
||||
analysis.add(parent_context, 'type-error-too-few-arguments',
|
||||
node, message=m)
|
||||
|
||||
for key, lazy_context in non_matching_keys.items():
|
||||
m = "TypeError: %s() got an unexpected keyword argument '%s'." \
|
||||
% (funcdef.name, key)
|
||||
_add_argument_issue(
|
||||
parent_context,
|
||||
'type-error-keyword-argument',
|
||||
lazy_context,
|
||||
message=m
|
||||
)
|
||||
|
||||
remaining_arguments = list(var_arg_iterator)
|
||||
if remaining_arguments:
|
||||
m = _error_argument_count(funcdef, len(unpacked_va))
|
||||
# Just report an error for the first param that is not needed (like
|
||||
# cPython).
|
||||
first_key, lazy_context = remaining_arguments[0]
|
||||
if var_args.get_calling_nodes():
|
||||
# There might not be a valid calling node so check for that first.
|
||||
_add_argument_issue(parent_context, 'type-error-too-many-arguments', lazy_context, message=m)
|
||||
return result_params
|
||||
|
||||
|
||||
def _error_argument_count(funcdef, actual_count):
|
||||
params = funcdef.get_params()
|
||||
default_arguments = sum(1 for p in params if p.default or p.star_count)
|
||||
|
||||
if default_arguments == 0:
|
||||
before = 'exactly '
|
||||
else:
|
||||
before = 'from %s to ' % (len(params) - default_arguments)
|
||||
return ('TypeError: %s() takes %s%s arguments (%s given).'
|
||||
% (funcdef.name, before, len(params), actual_count))
|
||||
|
||||
|
||||
def _create_default_param(execution_context, param):
|
||||
if param.star_count == 1:
|
||||
result_arg = LazyKnownContext(
|
||||
iterable.FakeSequence(execution_context.evaluator, u'tuple', [])
|
||||
)
|
||||
elif param.star_count == 2:
|
||||
result_arg = LazyKnownContext(
|
||||
iterable.FakeDict(execution_context.evaluator, {})
|
||||
)
|
||||
elif param.default is None:
|
||||
result_arg = LazyUnknownContext()
|
||||
else:
|
||||
result_arg = LazyTreeContext(execution_context.parent_context, param.default)
|
||||
return ExecutedParam(execution_context, param, result_arg)
|
||||
|
||||
|
||||
def create_default_params(execution_context, funcdef):
|
||||
return [_create_default_param(execution_context, p)
|
||||
for p in funcdef.get_params()]
|
||||
@@ -1,6 +0,0 @@
|
||||
from jedi.evaluate.cache import evaluator_function_cache
|
||||
|
||||
|
||||
@evaluator_function_cache()
|
||||
def get_yield_exprs(evaluator, funcdef):
|
||||
return list(funcdef.iter_yield_exprs())
|
||||
@@ -1,330 +0,0 @@
|
||||
"""
|
||||
PEP 0484 ( https://www.python.org/dev/peps/pep-0484/ ) describes type hints
|
||||
through function annotations. There is a strong suggestion in this document
|
||||
that only the type of type hinting defined in PEP0484 should be allowed
|
||||
as annotations in future python versions.
|
||||
|
||||
The (initial / probably incomplete) implementation todo list for pep-0484:
|
||||
v Function parameter annotations with builtin/custom type classes
|
||||
v Function returntype annotations with builtin/custom type classes
|
||||
v Function parameter annotations with strings (forward reference)
|
||||
v Function return type annotations with strings (forward reference)
|
||||
v Local variable type hints
|
||||
v Assigned types: `Url = str\ndef get(url:Url) -> str:`
|
||||
v Type hints in `with` statements
|
||||
x Stub files support
|
||||
x support `@no_type_check` and `@no_type_check_decorator`
|
||||
x support for typing.cast() operator
|
||||
x support for type hint comments for functions, `# type: (int, str) -> int`.
|
||||
See comment from Guido https://github.com/davidhalter/jedi/issues/662
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from parso import ParserSyntaxError, parse, split_lines
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import unicode, force_unicode
|
||||
from jedi.evaluate.cache import evaluator_method_cache
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.base_context import NO_CONTEXTS, ContextSet
|
||||
from jedi.evaluate.lazy_context import LazyTreeContext
|
||||
from jedi.evaluate.context import ModuleContext
|
||||
from jedi.evaluate.helpers import is_string
|
||||
from jedi import debug
|
||||
from jedi import parser_utils
|
||||
|
||||
|
||||
def _evaluate_for_annotation(context, annotation, index=None):
|
||||
"""
|
||||
Evaluates a string-node, looking for an annotation
|
||||
If index is not None, the annotation is expected to be a tuple
|
||||
and we're interested in that index
|
||||
"""
|
||||
context_set = context.eval_node(_fix_forward_reference(context, annotation))
|
||||
return context_set.execute_evaluated()
|
||||
|
||||
|
||||
def _evaluate_annotation_string(context, string, index=None):
|
||||
node = _get_forward_reference_node(context, string)
|
||||
if node is None:
|
||||
return NO_CONTEXTS
|
||||
|
||||
context_set = context.eval_node(node)
|
||||
if index is not None:
|
||||
context_set = context_set.filter(
|
||||
lambda context: context.array_type == u'tuple'
|
||||
and len(list(context.py__iter__())) >= index
|
||||
).py__getitem__(index)
|
||||
return context_set.execute_evaluated()
|
||||
|
||||
|
||||
def _fix_forward_reference(context, node):
|
||||
evaled_nodes = context.eval_node(node)
|
||||
if len(evaled_nodes) != 1:
|
||||
debug.warning("Eval'ed typing index %s should lead to 1 object, "
|
||||
" not %s" % (node, evaled_nodes))
|
||||
return node
|
||||
|
||||
evaled_context = list(evaled_nodes)[0]
|
||||
if is_string(evaled_context):
|
||||
result = _get_forward_reference_node(context, evaled_context.get_safe_value())
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return node
|
||||
|
||||
|
||||
def _get_forward_reference_node(context, string):
|
||||
try:
|
||||
new_node = context.evaluator.grammar.parse(
|
||||
force_unicode(string),
|
||||
start_symbol='eval_input',
|
||||
error_recovery=False
|
||||
)
|
||||
except ParserSyntaxError:
|
||||
debug.warning('Annotation not parsed: %s' % string)
|
||||
return None
|
||||
else:
|
||||
module = context.tree_node.get_root_node()
|
||||
parser_utils.move(new_node, module.end_pos[0])
|
||||
new_node.parent = context.tree_node
|
||||
return new_node
|
||||
|
||||
|
||||
def _split_comment_param_declaration(decl_text):
|
||||
"""
|
||||
Split decl_text on commas, but group generic expressions
|
||||
together.
|
||||
|
||||
For example, given "foo, Bar[baz, biz]" we return
|
||||
['foo', 'Bar[baz, biz]'].
|
||||
|
||||
"""
|
||||
try:
|
||||
node = parse(decl_text, error_recovery=False).children[0]
|
||||
except ParserSyntaxError:
|
||||
debug.warning('Comment annotation is not valid Python: %s' % decl_text)
|
||||
return []
|
||||
|
||||
if node.type == 'name':
|
||||
return [node.get_code().strip()]
|
||||
|
||||
params = []
|
||||
try:
|
||||
children = node.children
|
||||
except AttributeError:
|
||||
return []
|
||||
else:
|
||||
for child in children:
|
||||
if child.type in ['name', 'atom_expr', 'power']:
|
||||
params.append(child.get_code().strip())
|
||||
|
||||
return params
|
||||
|
||||
|
||||
@evaluator_method_cache()
|
||||
def infer_param(execution_context, param):
|
||||
"""
|
||||
Infers the type of a function parameter, using type annotations.
|
||||
"""
|
||||
annotation = param.annotation
|
||||
if annotation is None:
|
||||
# If no Python 3-style annotation, look for a Python 2-style comment
|
||||
# annotation.
|
||||
# Identify parameters to function in the same sequence as they would
|
||||
# appear in a type comment.
|
||||
all_params = [child for child in param.parent.children
|
||||
if child.type == 'param']
|
||||
|
||||
node = param.parent.parent
|
||||
comment = parser_utils.get_following_comment_same_line(node)
|
||||
if comment is None:
|
||||
return NO_CONTEXTS
|
||||
|
||||
match = re.match(r"^#\s*type:\s*\(([^#]*)\)\s*->", comment)
|
||||
if not match:
|
||||
return NO_CONTEXTS
|
||||
params_comments = _split_comment_param_declaration(match.group(1))
|
||||
|
||||
# Find the specific param being investigated
|
||||
index = all_params.index(param)
|
||||
# If the number of parameters doesn't match length of type comment,
|
||||
# ignore first parameter (assume it's self).
|
||||
if len(params_comments) != len(all_params):
|
||||
debug.warning(
|
||||
"Comments length != Params length %s %s",
|
||||
params_comments, all_params
|
||||
)
|
||||
from jedi.evaluate.context.instance import InstanceArguments
|
||||
if isinstance(execution_context.var_args, InstanceArguments):
|
||||
if index == 0:
|
||||
# Assume it's self, which is already handled
|
||||
return NO_CONTEXTS
|
||||
index -= 1
|
||||
if index >= len(params_comments):
|
||||
return NO_CONTEXTS
|
||||
|
||||
param_comment = params_comments[index]
|
||||
return _evaluate_annotation_string(
|
||||
execution_context.get_root_context(),
|
||||
param_comment
|
||||
)
|
||||
module_context = execution_context.get_root_context()
|
||||
return _evaluate_for_annotation(module_context, annotation)
|
||||
|
||||
|
||||
def py__annotations__(funcdef):
|
||||
return_annotation = funcdef.annotation
|
||||
if return_annotation:
|
||||
dct = {'return': return_annotation}
|
||||
else:
|
||||
dct = {}
|
||||
for function_param in funcdef.get_params():
|
||||
param_annotation = function_param.annotation
|
||||
if param_annotation is not None:
|
||||
dct[function_param.name.value] = param_annotation
|
||||
return dct
|
||||
|
||||
|
||||
@evaluator_method_cache()
|
||||
def infer_return_types(function_context):
|
||||
"""
|
||||
Infers the type of a function's return value,
|
||||
according to type annotations.
|
||||
"""
|
||||
annotation = py__annotations__(function_context.tree_node).get("return", None)
|
||||
if annotation is None:
|
||||
# If there is no Python 3-type annotation, look for a Python 2-type annotation
|
||||
node = function_context.tree_node
|
||||
comment = parser_utils.get_following_comment_same_line(node)
|
||||
if comment is None:
|
||||
return NO_CONTEXTS
|
||||
|
||||
match = re.match(r"^#\s*type:\s*\([^#]*\)\s*->\s*([^#]*)", comment)
|
||||
if not match:
|
||||
return NO_CONTEXTS
|
||||
|
||||
return _evaluate_annotation_string(
|
||||
function_context.get_root_context(),
|
||||
match.group(1).strip()
|
||||
)
|
||||
|
||||
module_context = function_context.get_root_context()
|
||||
return _evaluate_for_annotation(module_context, annotation)
|
||||
|
||||
|
||||
_typing_module = None
|
||||
_typing_module_code_lines = None
|
||||
|
||||
|
||||
def _get_typing_replacement_module(grammar):
|
||||
"""
|
||||
The idea is to return our jedi replacement for the PEP-0484 typing module
|
||||
as discussed at https://github.com/davidhalter/jedi/issues/663
|
||||
"""
|
||||
global _typing_module, _typing_module_code_lines
|
||||
if _typing_module is None:
|
||||
typing_path = \
|
||||
os.path.abspath(os.path.join(__file__, "../jedi_typing.py"))
|
||||
with open(typing_path) as f:
|
||||
code = unicode(f.read())
|
||||
_typing_module = grammar.parse(code)
|
||||
_typing_module_code_lines = split_lines(code, keepends=True)
|
||||
return _typing_module, _typing_module_code_lines
|
||||
|
||||
|
||||
def py__getitem__(context, typ, node):
|
||||
if not typ.get_root_context().name.string_name == "typing":
|
||||
return None
|
||||
# we assume that any class using [] in a module called
|
||||
# "typing" with a name for which we have a replacement
|
||||
# should be replaced by that class. This is not 100%
|
||||
# airtight but I don't have a better idea to check that it's
|
||||
# actually the PEP-0484 typing module and not some other
|
||||
if node.type == "subscriptlist":
|
||||
nodes = node.children[::2] # skip the commas
|
||||
else:
|
||||
nodes = [node]
|
||||
del node
|
||||
|
||||
nodes = [_fix_forward_reference(context, node) for node in nodes]
|
||||
type_name = typ.name.string_name
|
||||
|
||||
# hacked in Union and Optional, since it's hard to do nicely in parsed code
|
||||
if type_name in ("Union", '_Union'):
|
||||
# In Python 3.6 it's still called typing.Union but it's an instance
|
||||
# called _Union.
|
||||
return ContextSet.from_sets(context.eval_node(node) for node in nodes)
|
||||
if type_name in ("Optional", '_Optional'):
|
||||
# Here we have the same issue like in Union. Therefore we also need to
|
||||
# check for the instance typing._Optional (Python 3.6).
|
||||
return context.eval_node(nodes[0])
|
||||
|
||||
module_node, code_lines = _get_typing_replacement_module(context.evaluator.latest_grammar)
|
||||
typing = ModuleContext(
|
||||
context.evaluator,
|
||||
module_node=module_node,
|
||||
path=None,
|
||||
code_lines=code_lines,
|
||||
)
|
||||
factories = typing.py__getattribute__("factory")
|
||||
assert len(factories) == 1
|
||||
factory = list(factories)[0]
|
||||
assert factory
|
||||
function_body_nodes = factory.tree_node.children[4].children
|
||||
valid_classnames = set(child.name.value
|
||||
for child in function_body_nodes
|
||||
if isinstance(child, tree.Class))
|
||||
if type_name not in valid_classnames:
|
||||
return None
|
||||
compiled_classname = compiled.create_simple_object(context.evaluator, type_name)
|
||||
|
||||
from jedi.evaluate.context.iterable import FakeSequence
|
||||
args = FakeSequence(
|
||||
context.evaluator,
|
||||
u'tuple',
|
||||
[LazyTreeContext(context, n) for n in nodes]
|
||||
)
|
||||
|
||||
result = factory.execute_evaluated(compiled_classname, args)
|
||||
return result
|
||||
|
||||
|
||||
def find_type_from_comment_hint_for(context, node, name):
|
||||
return _find_type_from_comment_hint(context, node, node.children[1], name)
|
||||
|
||||
|
||||
def find_type_from_comment_hint_with(context, node, name):
|
||||
assert len(node.children[1].children) == 3, \
|
||||
"Can only be here when children[1] is 'foo() as f'"
|
||||
varlist = node.children[1].children[2]
|
||||
return _find_type_from_comment_hint(context, node, varlist, name)
|
||||
|
||||
|
||||
def find_type_from_comment_hint_assign(context, node, name):
|
||||
return _find_type_from_comment_hint(context, node, node.children[0], name)
|
||||
|
||||
|
||||
def _find_type_from_comment_hint(context, node, varlist, name):
|
||||
index = None
|
||||
if varlist.type in ("testlist_star_expr", "exprlist", "testlist"):
|
||||
# something like "a, b = 1, 2"
|
||||
index = 0
|
||||
for child in varlist.children:
|
||||
if child == name:
|
||||
break
|
||||
if child.type == "operator":
|
||||
continue
|
||||
index += 1
|
||||
else:
|
||||
return []
|
||||
|
||||
comment = parser_utils.get_following_comment_same_line(node)
|
||||
if comment is None:
|
||||
return []
|
||||
match = re.match(r"^#\s*type:\s*([^#]*)", comment)
|
||||
if match is None:
|
||||
return []
|
||||
return _evaluate_annotation_string(context, match.group(1).strip(), index)
|
||||
@@ -1,321 +0,0 @@
|
||||
"""
|
||||
Implementations of standard library functions, because it's not possible to
|
||||
understand them with Jedi.
|
||||
|
||||
To add a new implementation, create a function and add it to the
|
||||
``_implemented`` dict at the bottom of this module.
|
||||
|
||||
Note that this module exists only to implement very specific functionality in
|
||||
the standard library. The usual way to understand the standard library is the
|
||||
compiled module that returns the types for C-builtins.
|
||||
"""
|
||||
import parso
|
||||
|
||||
from jedi._compatibility import force_unicode
|
||||
from jedi import debug
|
||||
from jedi.evaluate.arguments import ValuesArguments, repack_with_argument_clinic
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate.context.instance import \
|
||||
AbstractInstanceContext, CompiledInstance, BoundMethod, InstanceArguments
|
||||
from jedi.evaluate.base_context import ContextualizedNode, \
|
||||
NO_CONTEXTS, ContextSet
|
||||
from jedi.evaluate.context import ClassContext, ModuleContext, FunctionExecutionContext
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.lazy_context import LazyTreeContext
|
||||
from jedi.evaluate.syntax_tree import is_string
|
||||
|
||||
# Now this is all part of fake tuples in Jedi. However super doesn't work on
|
||||
# __init__ and __new__ doesn't work at all. So adding this to nametuples is
|
||||
# just the easiest way.
|
||||
_NAMEDTUPLE_INIT = """
|
||||
def __init__(_cls, {arg_list}):
|
||||
'A helper function for namedtuple.'
|
||||
self.__iterable = ({arg_list})
|
||||
|
||||
def __iter__(self):
|
||||
for i in self.__iterable:
|
||||
yield i
|
||||
|
||||
def __getitem__(self, y):
|
||||
return self.__iterable[y]
|
||||
|
||||
"""
|
||||
|
||||
|
||||
class NotInStdLib(LookupError):
|
||||
pass
|
||||
|
||||
|
||||
def execute(evaluator, obj, arguments):
|
||||
if isinstance(obj, BoundMethod):
|
||||
raise NotInStdLib()
|
||||
|
||||
try:
|
||||
obj_name = obj.name.string_name
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if obj.parent_context == evaluator.builtins_module:
|
||||
module_name = 'builtins'
|
||||
elif isinstance(obj.parent_context, ModuleContext):
|
||||
module_name = obj.parent_context.name.string_name
|
||||
else:
|
||||
module_name = ''
|
||||
|
||||
# for now we just support builtin functions.
|
||||
try:
|
||||
func = _implemented[module_name][obj_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
return func(evaluator, obj, arguments=arguments)
|
||||
raise NotInStdLib()
|
||||
|
||||
|
||||
def _follow_param(evaluator, arguments, index):
|
||||
try:
|
||||
key, lazy_context = list(arguments.unpack())[index]
|
||||
except IndexError:
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
return lazy_context.infer()
|
||||
|
||||
|
||||
def argument_clinic(string, want_obj=False, want_context=False, want_arguments=False):
|
||||
"""
|
||||
Works like Argument Clinic (PEP 436), to validate function params.
|
||||
"""
|
||||
|
||||
def f(func):
|
||||
@repack_with_argument_clinic(string, keep_arguments_param=True)
|
||||
def wrapper(evaluator, obj, *args, **kwargs):
|
||||
arguments = kwargs.pop('arguments')
|
||||
assert not kwargs # Python 2...
|
||||
debug.dbg('builtin start %s' % obj, color='MAGENTA')
|
||||
result = NO_CONTEXTS
|
||||
if want_context:
|
||||
kwargs['context'] = arguments.context
|
||||
if want_obj:
|
||||
kwargs['obj'] = obj
|
||||
if want_arguments:
|
||||
kwargs['arguments'] = arguments
|
||||
result = func(evaluator, *args, **kwargs)
|
||||
debug.dbg('builtin end: %s', result, color='MAGENTA')
|
||||
return result
|
||||
|
||||
return wrapper
|
||||
return f
|
||||
|
||||
|
||||
@argument_clinic('iterator[, default], /')
|
||||
def builtins_next(evaluator, iterators, defaults):
|
||||
"""
|
||||
TODO this function is currently not used. It's a stab at implementing next
|
||||
in a different way than fake objects. This would be a bit more flexible.
|
||||
"""
|
||||
if evaluator.environment.version_info.major == 2:
|
||||
name = 'next'
|
||||
else:
|
||||
name = '__next__'
|
||||
|
||||
context_set = NO_CONTEXTS
|
||||
for iterator in iterators:
|
||||
if isinstance(iterator, AbstractInstanceContext):
|
||||
context_set = ContextSet.from_sets(
|
||||
n.infer()
|
||||
for filter in iterator.get_filters(include_self_names=True)
|
||||
for n in filter.get(name)
|
||||
).execute_evaluated()
|
||||
if context_set:
|
||||
return context_set
|
||||
return defaults
|
||||
|
||||
|
||||
@argument_clinic('object, name[, default], /')
|
||||
def builtins_getattr(evaluator, objects, names, defaults=None):
|
||||
# follow the first param
|
||||
for obj in objects:
|
||||
for name in names:
|
||||
if is_string(name):
|
||||
return obj.py__getattribute__(force_unicode(name.get_safe_value()))
|
||||
else:
|
||||
debug.warning('getattr called without str')
|
||||
continue
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
@argument_clinic('object[, bases, dict], /')
|
||||
def builtins_type(evaluator, objects, bases, dicts):
|
||||
if bases or dicts:
|
||||
# It's a type creation... maybe someday...
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
return objects.py__class__()
|
||||
|
||||
|
||||
class SuperInstance(AbstractInstanceContext):
|
||||
"""To be used like the object ``super`` returns."""
|
||||
def __init__(self, evaluator, cls):
|
||||
su = cls.py_mro()[1]
|
||||
super().__init__(evaluator, su and su[0] or self)
|
||||
|
||||
|
||||
@argument_clinic('[type[, obj]], /', want_context=True)
|
||||
def builtins_super(evaluator, types, objects, context):
|
||||
# TODO make this able to detect multiple inheritance super
|
||||
if isinstance(context, FunctionExecutionContext):
|
||||
if isinstance(context.var_args, InstanceArguments):
|
||||
su = context.var_args.instance.py__class__().py__bases__()
|
||||
return su[0].infer().execute_evaluated()
|
||||
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
@argument_clinic('sequence, /', want_obj=True, want_arguments=True)
|
||||
def builtins_reversed(evaluator, sequences, obj, arguments):
|
||||
# While we could do without this variable (just by using sequences), we
|
||||
# want static analysis to work well. Therefore we need to generated the
|
||||
# values again.
|
||||
key, lazy_context = next(arguments.unpack())
|
||||
cn = None
|
||||
if isinstance(lazy_context, LazyTreeContext):
|
||||
# TODO access private
|
||||
cn = ContextualizedNode(lazy_context._context, lazy_context.data)
|
||||
ordered = list(sequences.iterate(cn))
|
||||
|
||||
rev = list(reversed(ordered))
|
||||
# Repack iterator values and then run it the normal way. This is
|
||||
# necessary, because `reversed` is a function and autocompletion
|
||||
# would fail in certain cases like `reversed(x).__iter__` if we
|
||||
# just returned the result directly.
|
||||
seq = iterable.FakeSequence(evaluator, u'list', rev)
|
||||
arguments = ValuesArguments([ContextSet(seq)])
|
||||
return ContextSet(CompiledInstance(evaluator, evaluator.builtins_module, obj, arguments))
|
||||
|
||||
|
||||
@argument_clinic('obj, type, /', want_arguments=True)
|
||||
def builtins_isinstance(evaluator, objects, types, arguments):
|
||||
bool_results = set()
|
||||
for o in objects:
|
||||
cls = o.py__class__()
|
||||
try:
|
||||
mro_func = cls.py__mro__
|
||||
except AttributeError:
|
||||
# This is temporary. Everything should have a class attribute in
|
||||
# Python?! Maybe we'll leave it here, because some numpy objects or
|
||||
# whatever might not.
|
||||
bool_results = set([True, False])
|
||||
break
|
||||
|
||||
mro = mro_func()
|
||||
|
||||
for cls_or_tup in types:
|
||||
if cls_or_tup.is_class():
|
||||
bool_results.add(cls_or_tup in mro)
|
||||
elif cls_or_tup.name.string_name == 'tuple' \
|
||||
and cls_or_tup.get_root_context() == evaluator.builtins_module:
|
||||
# Check for tuples.
|
||||
classes = ContextSet.from_sets(
|
||||
lazy_context.infer()
|
||||
for lazy_context in cls_or_tup.iterate()
|
||||
)
|
||||
bool_results.add(any(cls in mro for cls in classes))
|
||||
else:
|
||||
_, lazy_context = list(arguments.unpack())[1]
|
||||
if isinstance(lazy_context, LazyTreeContext):
|
||||
node = lazy_context.data
|
||||
message = 'TypeError: isinstance() arg 2 must be a ' \
|
||||
'class, type, or tuple of classes and types, ' \
|
||||
'not %s.' % cls_or_tup
|
||||
analysis.add(lazy_context._context, 'type-error-isinstance', node, message)
|
||||
|
||||
return ContextSet.from_iterable(
|
||||
compiled.builtin_from_name(evaluator, force_unicode(str(b)))
|
||||
for b in bool_results
|
||||
)
|
||||
|
||||
|
||||
def collections_namedtuple(evaluator, obj, arguments):
|
||||
"""
|
||||
Implementation of the namedtuple function.
|
||||
|
||||
This has to be done by processing the namedtuple class template and
|
||||
evaluating the result.
|
||||
|
||||
"""
|
||||
collections_context = obj.parent_context
|
||||
_class_template_set = collections_context.py__getattribute__(u'_class_template')
|
||||
if not _class_template_set:
|
||||
# Namedtuples are not supported on Python 2.6, early 2.7, because the
|
||||
# _class_template variable is not defined, there.
|
||||
return NO_CONTEXTS
|
||||
|
||||
# Process arguments
|
||||
# TODO here we only use one of the types, we should use all.
|
||||
# TODO this is buggy, doesn't need to be a string
|
||||
name = list(_follow_param(evaluator, arguments, 0))[0].get_safe_value()
|
||||
_fields = list(_follow_param(evaluator, arguments, 1))[0]
|
||||
if isinstance(_fields, compiled.CompiledObject):
|
||||
fields = _fields.get_safe_value().replace(',', ' ').split()
|
||||
elif isinstance(_fields, iterable.Sequence):
|
||||
fields = [
|
||||
v.get_safe_value()
|
||||
for lazy_context in _fields.py__iter__()
|
||||
for v in lazy_context.infer() if is_string(v)
|
||||
]
|
||||
else:
|
||||
return NO_CONTEXTS
|
||||
|
||||
def get_var(name):
|
||||
x, = collections_context.py__getattribute__(name)
|
||||
return x.get_safe_value()
|
||||
|
||||
base = next(iter(_class_template_set)).get_safe_value()
|
||||
base += _NAMEDTUPLE_INIT
|
||||
# Build source code
|
||||
code = base.format(
|
||||
typename=name,
|
||||
field_names=tuple(fields),
|
||||
num_fields=len(fields),
|
||||
arg_list=repr(tuple(fields)).replace("u'", "").replace("'", "")[1:-1],
|
||||
repr_fmt=', '.join(get_var(u'_repr_template').format(name=name) for name in fields),
|
||||
field_defs='\n'.join(get_var(u'_field_template').format(index=index, name=name)
|
||||
for index, name in enumerate(fields))
|
||||
)
|
||||
|
||||
# Parse source code
|
||||
module = evaluator.grammar.parse(code)
|
||||
generated_class = next(module.iter_classdefs())
|
||||
parent_context = ModuleContext(
|
||||
evaluator, module, None,
|
||||
code_lines=parso.split_lines(code, keepends=True),
|
||||
)
|
||||
return ContextSet(ClassContext(evaluator, parent_context, generated_class))
|
||||
|
||||
|
||||
@argument_clinic('first, /')
|
||||
def _return_first_param(evaluator, firsts):
|
||||
return firsts
|
||||
|
||||
|
||||
_implemented = {
|
||||
'builtins': {
|
||||
'getattr': builtins_getattr,
|
||||
'type': builtins_type,
|
||||
'super': builtins_super,
|
||||
'reversed': builtins_reversed,
|
||||
'isinstance': builtins_isinstance,
|
||||
},
|
||||
'copy': {
|
||||
'copy': _return_first_param,
|
||||
'deepcopy': _return_first_param,
|
||||
},
|
||||
'json': {
|
||||
'load': lambda evaluator, obj, arguments: NO_CONTEXTS,
|
||||
'loads': lambda evaluator, obj, arguments: NO_CONTEXTS,
|
||||
},
|
||||
'collections': {
|
||||
'namedtuple': collections_namedtuple,
|
||||
},
|
||||
}
|
||||
@@ -1,662 +0,0 @@
|
||||
"""
|
||||
Functions evaluating the syntax tree.
|
||||
"""
|
||||
import copy
|
||||
|
||||
from parso.python import tree
|
||||
|
||||
from jedi._compatibility import force_unicode, unicode
|
||||
from jedi import debug
|
||||
from jedi import parser_utils
|
||||
from jedi.evaluate.base_context import ContextSet, NO_CONTEXTS, ContextualizedNode, \
|
||||
ContextualizedName, iterator_to_context_set, iterate_contexts
|
||||
from jedi.evaluate import compiled
|
||||
from jedi.evaluate import pep0484
|
||||
from jedi.evaluate import recursion
|
||||
from jedi.evaluate import helpers
|
||||
from jedi.evaluate import analysis
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate import arguments
|
||||
from jedi.evaluate.pep0484 import _evaluate_for_annotation
|
||||
from jedi.evaluate.context import ClassContext, FunctionContext
|
||||
from jedi.evaluate.context import iterable
|
||||
from jedi.evaluate.context import TreeInstance, CompiledInstance
|
||||
from jedi.evaluate.finder import NameFinder
|
||||
from jedi.evaluate.helpers import is_string, is_literal, is_number, is_compiled
|
||||
from jedi.evaluate.compiled.access import COMPARISON_OPERATORS
|
||||
|
||||
|
||||
def _limit_context_infers(func):
|
||||
"""
|
||||
This is for now the way how we limit type inference going wild. There are
|
||||
other ways to ensure recursion limits as well. This is mostly necessary
|
||||
because of instance (self) access that can be quite tricky to limit.
|
||||
|
||||
I'm still not sure this is the way to go, but it looks okay for now and we
|
||||
can still go anther way in the future. Tests are there. ~ dave
|
||||
"""
|
||||
def wrapper(context, *args, **kwargs):
|
||||
n = context.tree_node
|
||||
evaluator = context.evaluator
|
||||
try:
|
||||
evaluator.inferred_element_counts[n] += 1
|
||||
if evaluator.inferred_element_counts[n] > 300:
|
||||
debug.warning('In context %s there were too many inferences.', n)
|
||||
return NO_CONTEXTS
|
||||
except KeyError:
|
||||
evaluator.inferred_element_counts[n] = 1
|
||||
return func(context, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _py__stop_iteration_returns(generators):
|
||||
results = ContextSet()
|
||||
for generator in generators:
|
||||
try:
|
||||
method = generator.py__stop_iteration_returns
|
||||
except AttributeError:
|
||||
debug.warning('%s is not actually a generator', generator)
|
||||
else:
|
||||
results |= method()
|
||||
return results
|
||||
|
||||
|
||||
@debug.increase_indent
|
||||
@_limit_context_infers
|
||||
def eval_node(context, element):
|
||||
debug.dbg('eval_node %s@%s', element, element.start_pos)
|
||||
evaluator = context.evaluator
|
||||
typ = element.type
|
||||
if typ in ('name', 'number', 'string', 'atom', 'strings', 'keyword'):
|
||||
return eval_atom(context, element)
|
||||
elif typ == 'lambdef':
|
||||
return ContextSet(FunctionContext.from_context(context, element))
|
||||
elif typ == 'expr_stmt':
|
||||
return eval_expr_stmt(context, element)
|
||||
elif typ in ('power', 'atom_expr'):
|
||||
first_child = element.children[0]
|
||||
children = element.children[1:]
|
||||
had_await = False
|
||||
if first_child.type == 'keyword' and first_child.value == 'await':
|
||||
had_await = True
|
||||
first_child = children.pop(0)
|
||||
|
||||
context_set = eval_atom(context, first_child)
|
||||
for trailer in children:
|
||||
if trailer == '**': # has a power operation.
|
||||
right = context.eval_node(children[1])
|
||||
context_set = _eval_comparison(
|
||||
evaluator,
|
||||
context,
|
||||
context_set,
|
||||
trailer,
|
||||
right
|
||||
)
|
||||
break
|
||||
context_set = eval_trailer(context, context_set, trailer)
|
||||
|
||||
if had_await:
|
||||
await_context_set = context_set.py__getattribute__(u"__await__")
|
||||
if not await_context_set:
|
||||
debug.warning('Tried to run py__await__ on context %s', context)
|
||||
context_set = ContextSet()
|
||||
return _py__stop_iteration_returns(await_context_set.execute_evaluated())
|
||||
return context_set
|
||||
elif typ in ('testlist_star_expr', 'testlist',):
|
||||
# The implicit tuple in statements.
|
||||
return ContextSet(iterable.SequenceLiteralContext(evaluator, context, element))
|
||||
elif typ in ('not_test', 'factor'):
|
||||
context_set = context.eval_node(element.children[-1])
|
||||
for operator in element.children[:-1]:
|
||||
context_set = eval_factor(context_set, operator)
|
||||
return context_set
|
||||
elif typ == 'test':
|
||||
# `x if foo else y` case.
|
||||
return (context.eval_node(element.children[0]) |
|
||||
context.eval_node(element.children[-1]))
|
||||
elif typ == 'operator':
|
||||
# Must be an ellipsis, other operators are not evaluated.
|
||||
# In Python 2 ellipsis is coded as three single dot tokens, not
|
||||
# as one token 3 dot token.
|
||||
if element.value not in ('.', '...'):
|
||||
origin = element.parent
|
||||
raise AssertionError("unhandled operator %s in %s " % (repr(element.value), origin))
|
||||
return ContextSet(compiled.builtin_from_name(evaluator, u'Ellipsis'))
|
||||
elif typ == 'dotted_name':
|
||||
context_set = eval_atom(context, element.children[0])
|
||||
for next_name in element.children[2::2]:
|
||||
# TODO add search_global=True?
|
||||
context_set = context_set.py__getattribute__(next_name, name_context=context)
|
||||
return context_set
|
||||
elif typ == 'eval_input':
|
||||
return eval_node(context, element.children[0])
|
||||
elif typ == 'annassign':
|
||||
return pep0484._evaluate_for_annotation(context, element.children[1])
|
||||
elif typ == 'yield_expr':
|
||||
if len(element.children) and element.children[1].type == 'yield_arg':
|
||||
# Implies that it's a yield from.
|
||||
element = element.children[1].children[1]
|
||||
generators = context.eval_node(element)
|
||||
return _py__stop_iteration_returns(generators)
|
||||
|
||||
# Generator.send() is not implemented.
|
||||
return NO_CONTEXTS
|
||||
else:
|
||||
return eval_or_test(context, element)
|
||||
|
||||
|
||||
def eval_trailer(context, base_contexts, trailer):
|
||||
trailer_op, node = trailer.children[:2]
|
||||
if node == ')': # `arglist` is optional.
|
||||
node = None
|
||||
|
||||
if trailer_op == '[':
|
||||
trailer_op, node, _ = trailer.children
|
||||
|
||||
# TODO It's kind of stupid to cast this from a context set to a set.
|
||||
foo = set(base_contexts)
|
||||
# special case: PEP0484 typing module, see
|
||||
# https://github.com/davidhalter/jedi/issues/663
|
||||
result = ContextSet()
|
||||
for typ in list(foo):
|
||||
if isinstance(typ, (ClassContext, TreeInstance)):
|
||||
typing_module_types = pep0484.py__getitem__(context, typ, node)
|
||||
if typing_module_types is not None:
|
||||
foo.remove(typ)
|
||||
result |= typing_module_types
|
||||
|
||||
return result | base_contexts.get_item(
|
||||
eval_subscript_list(context.evaluator, context, node),
|
||||
ContextualizedNode(context, trailer)
|
||||
)
|
||||
else:
|
||||
debug.dbg('eval_trailer: %s in %s', trailer, base_contexts)
|
||||
if trailer_op == '.':
|
||||
return base_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)
|
||||
|
||||
|
||||
def eval_atom(context, atom):
|
||||
"""
|
||||
Basically to process ``atom`` nodes. The parser sometimes doesn't
|
||||
generate the node (because it has just one child). In that case an atom
|
||||
might be a name or a literal as well.
|
||||
"""
|
||||
if atom.type == 'name':
|
||||
# This is the first global lookup.
|
||||
stmt = tree.search_ancestor(
|
||||
atom, 'expr_stmt', 'lambdef'
|
||||
) or atom
|
||||
if stmt.type == 'lambdef':
|
||||
stmt = atom
|
||||
return context.py__getattribute__(
|
||||
name_or_str=atom,
|
||||
position=stmt.start_pos,
|
||||
search_global=True
|
||||
)
|
||||
elif atom.type == 'keyword':
|
||||
# For False/True/None
|
||||
if atom.value in ('False', 'True', 'None'):
|
||||
return ContextSet(compiled.builtin_from_name(context.evaluator, atom.value))
|
||||
elif atom.value == 'print':
|
||||
# print e.g. could be evaluated like this in Python 2.7
|
||||
return NO_CONTEXTS
|
||||
elif atom.value == 'yield':
|
||||
# Contrary to yield from, yield can just appear alone to return a
|
||||
# value when used with `.send()`.
|
||||
return NO_CONTEXTS
|
||||
assert False, 'Cannot evaluate the keyword %s' % atom
|
||||
|
||||
elif isinstance(atom, tree.Literal):
|
||||
string = context.evaluator.compiled_subprocess.safe_literal_eval(atom.value)
|
||||
return ContextSet(compiled.create_simple_object(context.evaluator, string))
|
||||
elif atom.type == 'strings':
|
||||
# Will be multiple string.
|
||||
context_set = eval_atom(context, atom.children[0])
|
||||
for string in atom.children[1:]:
|
||||
right = eval_atom(context, string)
|
||||
context_set = _eval_comparison(context.evaluator, context, context_set, u'+', right)
|
||||
return context_set
|
||||
else:
|
||||
c = atom.children
|
||||
# Parentheses without commas are not tuples.
|
||||
if c[0] == '(' and not len(c) == 2 \
|
||||
and not(c[1].type == 'testlist_comp' and
|
||||
len(c[1].children) > 1):
|
||||
return context.eval_node(c[1])
|
||||
|
||||
try:
|
||||
comp_for = c[1].children[1]
|
||||
except (IndexError, AttributeError):
|
||||
pass
|
||||
else:
|
||||
if comp_for == ':':
|
||||
# Dict comprehensions have a colon at the 3rd index.
|
||||
try:
|
||||
comp_for = c[1].children[3]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if comp_for.type == 'comp_for':
|
||||
return ContextSet(iterable.comprehension_from_atom(
|
||||
context.evaluator, context, atom
|
||||
))
|
||||
|
||||
# It's a dict/list/tuple literal.
|
||||
array_node = c[1]
|
||||
try:
|
||||
array_node_c = array_node.children
|
||||
except AttributeError:
|
||||
array_node_c = []
|
||||
if c[0] == '{' and (array_node == '}' or ':' in array_node_c or
|
||||
'**' in array_node_c):
|
||||
context = iterable.DictLiteralContext(context.evaluator, context, atom)
|
||||
else:
|
||||
context = iterable.SequenceLiteralContext(context.evaluator, context, atom)
|
||||
return ContextSet(context)
|
||||
|
||||
|
||||
@_limit_context_infers
|
||||
def eval_expr_stmt(context, stmt, seek_name=None):
|
||||
with recursion.execution_allowed(context.evaluator, stmt) as allowed:
|
||||
# Here we allow list/set to recurse under certain conditions. To make
|
||||
# it possible to resolve stuff like list(set(list(x))), this is
|
||||
# necessary.
|
||||
if not allowed and context.get_root_context() == context.evaluator.builtins_module:
|
||||
try:
|
||||
instance = context.var_args.instance
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
if instance.name.string_name in ('list', 'set'):
|
||||
c = instance.get_first_non_keyword_argument_contexts()
|
||||
if instance not in c:
|
||||
allowed = True
|
||||
|
||||
if allowed:
|
||||
return _eval_expr_stmt(context, stmt, seek_name)
|
||||
return NO_CONTEXTS
|
||||
|
||||
|
||||
@debug.increase_indent
|
||||
def _eval_expr_stmt(context, stmt, seek_name=None):
|
||||
"""
|
||||
The starting point of the completion. A statement always owns a call
|
||||
list, which are the calls, that a statement does. In case multiple
|
||||
names are defined in the statement, `seek_name` returns the result for
|
||||
this name.
|
||||
|
||||
:param stmt: A `tree.ExprStmt`.
|
||||
"""
|
||||
debug.dbg('eval_expr_stmt %s (%s)', stmt, seek_name)
|
||||
rhs = stmt.get_rhs()
|
||||
context_set = context.eval_node(rhs)
|
||||
|
||||
if seek_name:
|
||||
c_node = ContextualizedName(context, seek_name)
|
||||
context_set = check_tuple_assignments(context.evaluator, c_node, context_set)
|
||||
|
||||
first_operator = next(stmt.yield_operators(), None)
|
||||
if first_operator not in ('=', None) and first_operator.type == 'operator':
|
||||
# `=` is always the last character in aug assignments -> -1
|
||||
operator = copy.copy(first_operator)
|
||||
operator.value = operator.value[:-1]
|
||||
name = stmt.get_defined_names()[0].value
|
||||
left = context.py__getattribute__(
|
||||
name, position=stmt.start_pos, search_global=True)
|
||||
|
||||
for_stmt = tree.search_ancestor(stmt, 'for_stmt')
|
||||
if for_stmt is not None and for_stmt.type == 'for_stmt' and context_set \
|
||||
and parser_utils.for_stmt_defines_one_name(for_stmt):
|
||||
# Iterate through result and add the values, that's possible
|
||||
# only in for loops without clutter, because they are
|
||||
# predictable. Also only do it, if the variable is not a tuple.
|
||||
node = for_stmt.get_testlist()
|
||||
cn = ContextualizedNode(context, node)
|
||||
ordered = list(cn.infer().iterate(cn))
|
||||
|
||||
for lazy_context in ordered:
|
||||
dct = {for_stmt.children[1].value: lazy_context.infer()}
|
||||
with helpers.predefine_names(context, for_stmt, dct):
|
||||
t = context.eval_node(rhs)
|
||||
left = _eval_comparison(context.evaluator, context, left, operator, t)
|
||||
context_set = left
|
||||
else:
|
||||
context_set = _eval_comparison(context.evaluator, context, left, operator, context_set)
|
||||
debug.dbg('eval_expr_stmt result %s', context_set)
|
||||
return context_set
|
||||
|
||||
|
||||
def eval_or_test(context, or_test):
|
||||
iterator = iter(or_test.children)
|
||||
types = context.eval_node(next(iterator))
|
||||
for operator in iterator:
|
||||
right = next(iterator)
|
||||
if operator.type == 'comp_op': # not in / is not
|
||||
operator = ' '.join(c.value for c in operator.children)
|
||||
|
||||
# handle lazy evaluation of and/or here.
|
||||
if operator in ('and', 'or'):
|
||||
left_bools = set(left.py__bool__() for left in types)
|
||||
if left_bools == {True}:
|
||||
if operator == 'and':
|
||||
types = context.eval_node(right)
|
||||
elif left_bools == {False}:
|
||||
if operator != 'and':
|
||||
types = context.eval_node(right)
|
||||
# Otherwise continue, because of uncertainty.
|
||||
else:
|
||||
types = _eval_comparison(context.evaluator, context, types, operator,
|
||||
context.eval_node(right))
|
||||
debug.dbg('eval_or_test types %s', types)
|
||||
return types
|
||||
|
||||
|
||||
@iterator_to_context_set
|
||||
def eval_factor(context_set, operator):
|
||||
"""
|
||||
Calculates `+`, `-`, `~` and `not` prefixes.
|
||||
"""
|
||||
for context in context_set:
|
||||
if operator == '-':
|
||||
if is_number(context):
|
||||
yield context.negate()
|
||||
elif operator == 'not':
|
||||
value = context.py__bool__()
|
||||
if value is None: # Uncertainty.
|
||||
return
|
||||
yield compiled.create_simple_object(context.evaluator, not value)
|
||||
else:
|
||||
yield context
|
||||
|
||||
|
||||
def _literals_to_types(evaluator, result):
|
||||
# Changes literals ('a', 1, 1.0, etc) to its type instances (str(),
|
||||
# int(), float(), etc).
|
||||
new_result = NO_CONTEXTS
|
||||
for typ in result:
|
||||
if is_literal(typ):
|
||||
# Literals are only valid as long as the operations are
|
||||
# correct. Otherwise add a value-free instance.
|
||||
cls = compiled.builtin_from_name(evaluator, typ.name.string_name)
|
||||
new_result |= cls.execute_evaluated()
|
||||
else:
|
||||
new_result |= ContextSet(typ)
|
||||
return new_result
|
||||
|
||||
|
||||
def _eval_comparison(evaluator, context, left_contexts, operator, right_contexts):
|
||||
if not left_contexts or not right_contexts:
|
||||
# illegal slices e.g. cause left/right_result to be None
|
||||
result = (left_contexts or NO_CONTEXTS) | (right_contexts or NO_CONTEXTS)
|
||||
return _literals_to_types(evaluator, result)
|
||||
else:
|
||||
# I don't think there's a reasonable chance that a string
|
||||
# operation is still correct, once we pass something like six
|
||||
# objects.
|
||||
if len(left_contexts) * len(right_contexts) > 6:
|
||||
return _literals_to_types(evaluator, left_contexts | right_contexts)
|
||||
else:
|
||||
return ContextSet.from_sets(
|
||||
_eval_comparison_part(evaluator, context, left, operator, right)
|
||||
for left in left_contexts
|
||||
for right in right_contexts
|
||||
)
|
||||
|
||||
|
||||
def _is_tuple(context):
|
||||
return isinstance(context, iterable.Sequence) and context.array_type == 'tuple'
|
||||
|
||||
|
||||
def _is_list(context):
|
||||
return isinstance(context, iterable.Sequence) and context.array_type == 'list'
|
||||
|
||||
|
||||
def _bool_to_context(evaluator, bool_):
|
||||
return compiled.builtin_from_name(evaluator, force_unicode(str(bool_)))
|
||||
|
||||
|
||||
def _eval_comparison_part(evaluator, context, left, operator, right):
|
||||
l_is_num = is_number(left)
|
||||
r_is_num = is_number(right)
|
||||
if isinstance(operator, unicode):
|
||||
str_operator = operator
|
||||
else:
|
||||
str_operator = force_unicode(str(operator.value))
|
||||
|
||||
if str_operator == '*':
|
||||
# for iterables, ignore * operations
|
||||
if isinstance(left, iterable.Sequence) or is_string(left):
|
||||
return ContextSet(left)
|
||||
elif isinstance(right, iterable.Sequence) or is_string(right):
|
||||
return ContextSet(right)
|
||||
elif str_operator == '+':
|
||||
if l_is_num and r_is_num or is_string(left) and is_string(right):
|
||||
return ContextSet(left.execute_operation(right, str_operator))
|
||||
elif _is_tuple(left) and _is_tuple(right) or _is_list(left) and _is_list(right):
|
||||
return ContextSet(iterable.MergedArray(evaluator, (left, right)))
|
||||
elif str_operator == '-':
|
||||
if l_is_num and r_is_num:
|
||||
return ContextSet(left.execute_operation(right, str_operator))
|
||||
elif str_operator == '%':
|
||||
# With strings and numbers the left type typically remains. Except for
|
||||
# `int() % float()`.
|
||||
return ContextSet(left)
|
||||
elif str_operator in COMPARISON_OPERATORS:
|
||||
if is_compiled(left) and is_compiled(right):
|
||||
# Possible, because the return is not an option. Just compare.
|
||||
try:
|
||||
return ContextSet(left.execute_operation(right, str_operator))
|
||||
except TypeError:
|
||||
# Could be True or False.
|
||||
pass
|
||||
else:
|
||||
if str_operator in ('is', '!=', '==', 'is not'):
|
||||
operation = COMPARISON_OPERATORS[str_operator]
|
||||
bool_ = operation(left, right)
|
||||
return ContextSet(_bool_to_context(evaluator, bool_))
|
||||
|
||||
return ContextSet(_bool_to_context(evaluator, True), _bool_to_context(evaluator, False))
|
||||
elif str_operator == 'in':
|
||||
return NO_CONTEXTS
|
||||
|
||||
def check(obj):
|
||||
"""Checks if a Jedi object is either a float or an int."""
|
||||
return isinstance(obj, CompiledInstance) and \
|
||||
obj.name.string_name in ('int', 'float')
|
||||
|
||||
# Static analysis, one is a number, the other one is not.
|
||||
if str_operator in ('+', '-') and l_is_num != r_is_num \
|
||||
and not (check(left) or check(right)):
|
||||
message = "TypeError: unsupported operand type(s) for +: %s and %s"
|
||||
analysis.add(context, 'type-error-operation', operator,
|
||||
message % (left, right))
|
||||
|
||||
return ContextSet(left, right)
|
||||
|
||||
|
||||
def _remove_statements(evaluator, context, stmt, name):
|
||||
"""
|
||||
This is the part where statements are being stripped.
|
||||
|
||||
Due to lazy evaluation, statements like a = func; b = a; b() have to be
|
||||
evaluated.
|
||||
"""
|
||||
pep0484_contexts = \
|
||||
pep0484.find_type_from_comment_hint_assign(context, stmt, name)
|
||||
if pep0484_contexts:
|
||||
return pep0484_contexts
|
||||
|
||||
return eval_expr_stmt(context, stmt, seek_name=name)
|
||||
|
||||
|
||||
def tree_name_to_contexts(evaluator, context, tree_name):
|
||||
|
||||
context_set = ContextSet()
|
||||
module_node = context.get_root_context().tree_node
|
||||
if module_node is not None:
|
||||
names = module_node.get_used_names().get(tree_name.value, [])
|
||||
for name in names:
|
||||
expr_stmt = name.parent
|
||||
|
||||
correct_scope = parser_utils.get_parent_scope(name) == context.tree_node
|
||||
|
||||
if expr_stmt.type == "expr_stmt" and expr_stmt.children[1].type == "annassign" and correct_scope:
|
||||
context_set |= _evaluate_for_annotation(context, expr_stmt.children[1].children[1])
|
||||
|
||||
if context_set:
|
||||
return context_set
|
||||
|
||||
types = []
|
||||
node = tree_name.get_definition(import_name_always=True)
|
||||
if node is None:
|
||||
node = tree_name.parent
|
||||
if node.type == 'global_stmt':
|
||||
context = evaluator.create_context(context, tree_name)
|
||||
finder = NameFinder(evaluator, context, context, tree_name.value)
|
||||
filters = finder.get_filters(search_global=True)
|
||||
# For global_stmt lookups, we only need the first possible scope,
|
||||
# which means the function itself.
|
||||
filters = [next(filters)]
|
||||
return finder.find(filters, attribute_lookup=False)
|
||||
elif node.type not in ('import_from', 'import_name'):
|
||||
raise ValueError("Should not happen. type: %s", node.type)
|
||||
|
||||
typ = node.type
|
||||
if typ == 'for_stmt':
|
||||
types = pep0484.find_type_from_comment_hint_for(context, node, tree_name)
|
||||
if types:
|
||||
return types
|
||||
if typ == 'with_stmt':
|
||||
types = pep0484.find_type_from_comment_hint_with(context, node, tree_name)
|
||||
if types:
|
||||
return types
|
||||
|
||||
if typ in ('for_stmt', 'comp_for'):
|
||||
try:
|
||||
types = context.predefined_names[node][tree_name.value]
|
||||
except KeyError:
|
||||
cn = ContextualizedNode(context, node.children[3])
|
||||
for_types = iterate_contexts(
|
||||
cn.infer(),
|
||||
contextualized_node=cn,
|
||||
is_async=node.parent.type == 'async_stmt',
|
||||
)
|
||||
c_node = ContextualizedName(context, tree_name)
|
||||
types = check_tuple_assignments(evaluator, c_node, for_types)
|
||||
elif typ == 'expr_stmt':
|
||||
types = _remove_statements(evaluator, context, node, tree_name)
|
||||
elif typ == 'with_stmt':
|
||||
context_managers = context.eval_node(node.get_test_node_from_name(tree_name))
|
||||
enter_methods = context_managers.py__getattribute__(u'__enter__')
|
||||
return enter_methods.execute_evaluated()
|
||||
elif typ in ('import_from', 'import_name'):
|
||||
types = imports.infer_import(context, tree_name)
|
||||
elif typ in ('funcdef', 'classdef'):
|
||||
types = _apply_decorators(context, node)
|
||||
elif typ == 'try_stmt':
|
||||
# TODO an exception can also be a tuple. Check for those.
|
||||
# TODO check for types that are not classes and add it to
|
||||
# the static analysis report.
|
||||
exceptions = context.eval_node(tree_name.get_previous_sibling().get_previous_sibling())
|
||||
types = exceptions.execute_evaluated()
|
||||
else:
|
||||
raise ValueError("Should not happen. type: %s" % typ)
|
||||
return types
|
||||
|
||||
|
||||
def _apply_decorators(context, node):
|
||||
"""
|
||||
Returns the function, that should to be executed in the end.
|
||||
This is also the places where the decorators are processed.
|
||||
"""
|
||||
if node.type == 'classdef':
|
||||
decoratee_context = ClassContext(
|
||||
context.evaluator,
|
||||
parent_context=context,
|
||||
tree_node=node
|
||||
)
|
||||
else:
|
||||
decoratee_context = FunctionContext.from_context(context, node)
|
||||
initial = values = ContextSet(decoratee_context)
|
||||
for dec in reversed(node.get_decorators()):
|
||||
debug.dbg('decorator: %s %s', dec, values)
|
||||
dec_values = context.eval_node(dec.children[1])
|
||||
trailer_nodes = dec.children[2:-1]
|
||||
if trailer_nodes:
|
||||
# Create a trailer and evaluate it.
|
||||
trailer = tree.PythonNode('trailer', trailer_nodes)
|
||||
trailer.parent = dec
|
||||
dec_values = eval_trailer(context, dec_values, trailer)
|
||||
|
||||
if not len(dec_values):
|
||||
debug.warning('decorator not found: %s on %s', dec, node)
|
||||
return initial
|
||||
|
||||
values = dec_values.execute(arguments.ValuesArguments([values]))
|
||||
if not len(values):
|
||||
debug.warning('not possible to resolve wrappers found %s', node)
|
||||
return initial
|
||||
|
||||
debug.dbg('decorator end %s', values)
|
||||
return values
|
||||
|
||||
|
||||
def check_tuple_assignments(evaluator, contextualized_name, context_set):
|
||||
"""
|
||||
Checks if tuples are assigned.
|
||||
"""
|
||||
lazy_context = None
|
||||
for index, node in contextualized_name.assignment_indexes():
|
||||
cn = ContextualizedNode(contextualized_name.context, node)
|
||||
iterated = context_set.iterate(cn)
|
||||
for _ in range(index + 1):
|
||||
try:
|
||||
lazy_context = next(iterated)
|
||||
except StopIteration:
|
||||
# We could do this with the default param in next. But this
|
||||
# would allow this loop to run for a very long time if the
|
||||
# index number is high. Therefore break if the loop is
|
||||
# finished.
|
||||
return ContextSet()
|
||||
context_set = lazy_context.infer()
|
||||
return context_set
|
||||
|
||||
|
||||
def eval_subscript_list(evaluator, context, index):
|
||||
"""
|
||||
Handles slices in subscript nodes.
|
||||
"""
|
||||
if index == ':':
|
||||
# Like array[:]
|
||||
return ContextSet(iterable.Slice(context, None, None, None))
|
||||
|
||||
elif index.type == 'subscript' and not index.children[0] == '.':
|
||||
# subscript basically implies a slice operation, except for Python 2's
|
||||
# Ellipsis.
|
||||
# e.g. array[:3]
|
||||
result = []
|
||||
for el in index.children:
|
||||
if el == ':':
|
||||
if not result:
|
||||
result.append(None)
|
||||
elif el.type == 'sliceop':
|
||||
if len(el.children) == 2:
|
||||
result.append(el.children[1])
|
||||
else:
|
||||
result.append(el)
|
||||
result += [None] * (3 - len(result))
|
||||
|
||||
return ContextSet(iterable.Slice(context, *result))
|
||||
elif index.type == 'subscriptlist':
|
||||
return NO_CONTEXTS
|
||||
|
||||
# No slices
|
||||
return context.eval_node(index)
|
||||
@@ -1,62 +0,0 @@
|
||||
from jedi.evaluate import imports
|
||||
from jedi.evaluate.filters import TreeNameDefinition
|
||||
from jedi.evaluate.context import ModuleContext
|
||||
|
||||
|
||||
def _resolve_names(definition_names, avoid_names=()):
|
||||
for name in definition_names:
|
||||
if name in avoid_names:
|
||||
# Avoiding recursions here, because goto on a module name lands
|
||||
# on the same module.
|
||||
continue
|
||||
|
||||
if not isinstance(name, imports.SubModuleName):
|
||||
# SubModuleNames are not actually existing names but created
|
||||
# names when importing something like `import foo.bar.baz`.
|
||||
yield name
|
||||
|
||||
if name.api_type == 'module':
|
||||
for name in _resolve_names(name.goto(), definition_names):
|
||||
yield name
|
||||
|
||||
|
||||
def _dictionarize(names):
|
||||
return dict(
|
||||
(n if n.tree_name is None else n.tree_name, n)
|
||||
for n in names
|
||||
)
|
||||
|
||||
|
||||
def _find_names(module_context, tree_name):
|
||||
context = module_context.create_context(tree_name)
|
||||
name = TreeNameDefinition(context, tree_name)
|
||||
found_names = set(name.goto())
|
||||
found_names.add(name)
|
||||
return _dictionarize(_resolve_names(found_names))
|
||||
|
||||
|
||||
def usages(module_context, tree_name):
|
||||
search_name = tree_name.value
|
||||
found_names = _find_names(module_context, tree_name)
|
||||
modules = set(d.get_root_context() for d in found_names.values())
|
||||
modules = set(m for m in modules if isinstance(m, ModuleContext))
|
||||
|
||||
non_matching_usage_maps = {}
|
||||
for m in imports.get_modules_containing_name(module_context.evaluator, modules, search_name):
|
||||
for name_leaf in m.tree_node.get_used_names().get(search_name, []):
|
||||
new = _find_names(m, name_leaf)
|
||||
if any(tree_name in found_names for tree_name in new):
|
||||
found_names.update(new)
|
||||
for tree_name in new:
|
||||
for dct in non_matching_usage_maps.get(tree_name, []):
|
||||
# A usage that was previously searched for matches with
|
||||
# a now found name. Merge.
|
||||
found_names.update(dct)
|
||||
try:
|
||||
del non_matching_usage_maps[tree_name]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
for name in new:
|
||||
non_matching_usage_maps.setdefault(name, []).append(new)
|
||||
return found_names.values()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user