diff --git a/.travis.yml b/.travis.yml index 25c3a21e..e51e2c9d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,25 @@ language: python -python: 3.5 sudo: false -env: - - TOXENV=py26 - - TOXENV=py27 - - TOXENV=py33 - - TOXENV=py34 - - TOXENV=py35 - - TOXENV=pypy - - TOXENV=cov - - TOXENV=sith +python: + - 2.6 + - 2.7 + - 3.3 + - 3.4 + - 3.5 + - 3.6 + - pypy matrix: - allow_failures: - - env: TOXENV=cov - - env: TOXENV=sith - - env: TOXENV=pypy -python: 3.5 + allow_failures: + - python: pypy + - env: TOXENV=cov + - env: TOXENV=sith + include: + - python: 3.5 + env: TOXENV=cov + - python: 3.5 + env: TOXENV=sith install: - - pip install --quiet tox + - pip install --quiet tox-travis script: - tox after_script: diff --git a/docs/docs/usage.rst b/docs/docs/usage.rst index 015c3e2a..82e1040c 100644 --- a/docs/docs/usage.rst +++ b/docs/docs/usage.rst @@ -48,6 +48,10 @@ Kate: `__, you have to enable it, though. +Visual Studio Code: + +- `Python Extension`_ + Atom: - autocomplete-python_ @@ -112,3 +116,4 @@ Using a custom ``$HOME/.pythonrc.py`` .. _GNOME Builder: https://wiki.gnome.org/Apps/Builder/ .. _gedi: https://github.com/isamert/gedi .. _Eric IDE: http://eric-ide.python-projects.org +.. _Python Extension: https://marketplace.visualstudio.com/items?itemName=donjayamanne.python diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index a1cb9ac0..b326072c 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -105,7 +105,7 @@ class Script(object): if source is None: # TODO add a better warning than the traceback! - with open(path) as f: + with open(path, 'rb') as f: source = f.read() self._source = common.source_to_unicode(source, encoding) @@ -206,8 +206,8 @@ class Script(object): def goto_assignments(self, follow_imports=False): """ - Return the first definition found. Imports and statements aren't - followed. Multiple objects may be returned, because Python itself is a + Return the first definition found, while optionally following imports. + Multiple objects may be returned, because Python itself is a dynamic language, which means depending on an option you can have two different versions of a function. diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index 461cc088..96109e34 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -8,8 +8,13 @@ from jedi.parser.tree import Leaf try: from pydoc_data import topics as pydoc_topics except ImportError: - # Python 2.6 - import pydoc_topics + # Python 2 + try: + import pydoc_topics + except ImportError: + # This is for Python 3 embeddable version, which dont have + # pydoc_data module in its file python3x.zip. + pydoc_topics = None if is_py3: if is_py35: @@ -108,6 +113,9 @@ def imitate_pydoc(string): It's not possible to get the pydoc's without starting the annoying pager stuff. """ + if pydoc_topics is None: + return '' + # str needed because of possible unicode stuff in py2k (pydoc doesn't work # with unicode strings) string = str(string) diff --git a/jedi/evaluate/__init__.py b/jedi/evaluate/__init__.py index d5900868..2679c635 100644 --- a/jedi/evaluate/__init__.py +++ b/jedi/evaluate/__init__.py @@ -430,7 +430,7 @@ class Evaluator(object): def_ = name.get_definition() is_simple_name = name.parent.type not in ('power', 'trailer') if is_simple_name: - if name.parent.type == 'classdef': + if name.parent.type == 'classdef' and name.parent.name == name: return [er.ClassContext(self, name.parent, context)] elif name.parent.type == 'funcdef': return [er.FunctionContext(self, context, name.parent)] diff --git a/jedi/evaluate/sys_path.py b/jedi/evaluate/sys_path.py index b16a53c5..071049d0 100644 --- a/jedi/evaluate/sys_path.py +++ b/jedi/evaluate/sys_path.py @@ -28,14 +28,13 @@ def get_venv_path(venv): def _get_sys_path_with_egglinks(sys_path): """Find all paths including those referenced by egg-links. - Egg-link-referenced directories are inserted into path immediately after + Egg-link-referenced directories are inserted into path immediately before the directory on which their links were found. Such directories are not taken into consideration by normal import mechanism, but they are traversed when doing pkg_resources.require. """ result = [] for p in sys_path: - result.append(p) # pkg_resources does not define a specific order for egg-link files # using os.listdir to enumerate them, we're sorting them to have # reproducible tests. @@ -48,6 +47,7 @@ def _get_sys_path_with_egglinks(sys_path): # pkg_resources package only interprets the first # non-empty line in egg-link files. break + result.append(p) return result diff --git a/jedi/parser/grammar3.6.txt b/jedi/parser/grammar3.6.txt new file mode 100644 index 00000000..b44a5698 --- /dev/null +++ b/jedi/parser/grammar3.6.txt @@ -0,0 +1,161 @@ +# Grammar for Python + +# Note: Changing the grammar specified in this file will most likely +# require corresponding changes in the parser module +# (../Modules/parsermodule.c). If you can't make the changes to +# that module yourself, please co-ordinate the required changes +# with someone who can; ask around on python-dev for help. Fred +# Drake will probably be listening there. + +# NOTE WELL: You should also follow all the steps listed at +# https://docs.python.org/devguide/grammar.html + +# Start symbols for the grammar: +# file_input is a module or sequence of commands read from an input file; +# single_input is a single interactive statement; +# eval_input is the input for the eval() functions. +# NB: compound_stmt in single_input is followed by extra NEWLINE! +file_input: (NEWLINE | stmt)* ENDMARKER +single_input: NEWLINE | simple_stmt | compound_stmt NEWLINE +eval_input: testlist NEWLINE* ENDMARKER + +decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE +decorators: decorator+ +decorated: decorators (classdef | funcdef | async_funcdef) + +# NOTE: Francisco Souza/Reinoud Elhorst, using ASYNC/'await' keywords instead of +# skipping python3.5+ compatibility, in favour of 3.7 solution +async_funcdef: 'async' funcdef +funcdef: 'def' NAME parameters ['->' test] ':' suite + +parameters: '(' [typedargslist] ')' +typedargslist: (tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [ + '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]] + | '**' tfpdef [',']]] + | '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]] + | '**' tfpdef [',']) +tfpdef: NAME [':' test] +varargslist: (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [ + '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]] + | '**' vfpdef [',']]] + | '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]] + | '**' vfpdef [','] +) +vfpdef: NAME + +stmt: simple_stmt | compound_stmt +simple_stmt: small_stmt (';' small_stmt)* [';'] NEWLINE +small_stmt: (expr_stmt | del_stmt | pass_stmt | flow_stmt | + import_stmt | global_stmt | nonlocal_stmt | assert_stmt) +expr_stmt: testlist_star_expr (annassign | augassign (yield_expr|testlist) | + ('=' (yield_expr|testlist_star_expr))*) +annassign: ':' test ['=' test] +testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [','] +augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' | + '<<=' | '>>=' | '**=' | '//=') +# For normal and annotated assignments, additional restrictions enforced by the interpreter +del_stmt: 'del' exprlist +pass_stmt: 'pass' +flow_stmt: break_stmt | continue_stmt | return_stmt | raise_stmt | yield_stmt +break_stmt: 'break' +continue_stmt: 'continue' +return_stmt: 'return' [testlist] +yield_stmt: yield_expr +raise_stmt: 'raise' [test ['from' test]] +import_stmt: import_name | import_from +import_name: 'import' dotted_as_names +# note below: the ('.' | '...') is necessary because '...' is tokenized as ELLIPSIS +import_from: ('from' (('.' | '...')* dotted_name | ('.' | '...')+) + 'import' ('*' | '(' import_as_names ')' | import_as_names)) +import_as_name: NAME ['as' NAME] +dotted_as_name: dotted_name ['as' NAME] +import_as_names: import_as_name (',' import_as_name)* [','] +dotted_as_names: dotted_as_name (',' dotted_as_name)* +dotted_name: NAME ('.' NAME)* +global_stmt: 'global' NAME (',' NAME)* +nonlocal_stmt: 'nonlocal' NAME (',' NAME)* +assert_stmt: 'assert' test [',' test] + +compound_stmt: if_stmt | while_stmt | for_stmt | try_stmt | with_stmt | funcdef | classdef | decorated | async_stmt +async_stmt: 'async' (funcdef | with_stmt | for_stmt) +if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':' suite] +while_stmt: 'while' test ':' suite ['else' ':' suite] +for_stmt: 'for' exprlist 'in' testlist ':' suite ['else' ':' suite] +try_stmt: ('try' ':' suite + ((except_clause ':' suite)+ + ['else' ':' suite] + ['finally' ':' suite] | + 'finally' ':' suite)) +with_stmt: 'with' with_item (',' with_item)* ':' suite +with_item: test ['as' expr] +# NB compile.c makes sure that the default except clause is last +except_clause: 'except' [test ['as' NAME]] +# Edit by Francisco Souza/David Halter: The stmt is now optional. This reflects +# how Jedi allows classes and functions to be empty, which is beneficial for +# autocompletion. +suite: simple_stmt | NEWLINE INDENT stmt* DEDENT + +test: or_test ['if' or_test 'else' test] | lambdef +test_nocond: or_test | lambdef_nocond +lambdef: 'lambda' [varargslist] ':' test +lambdef_nocond: 'lambda' [varargslist] ':' test_nocond +or_test: and_test ('or' and_test)* +and_test: not_test ('and' not_test)* +not_test: 'not' not_test | comparison +comparison: expr (comp_op expr)* +# <> isn't actually a valid comparison operator in Python. It's here for the +# sake of a __future__ import described in PEP 401 (which really works :-) +comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is' 'not' +star_expr: '*' expr +expr: xor_expr ('|' xor_expr)* +xor_expr: and_expr ('^' and_expr)* +and_expr: shift_expr ('&' shift_expr)* +shift_expr: arith_expr (('<<'|'>>') arith_expr)* +arith_expr: term (('+'|'-') term)* +term: factor (('*'|'@'|'/'|'%'|'//') factor)* +factor: ('+'|'-'|'~') factor | power +power: atom_expr ['**' factor] +atom_expr: ['await'] atom trailer* +atom: ('(' [yield_expr|testlist_comp] ')' | + '[' [testlist_comp] ']' | + '{' [dictorsetmaker] '}' | + NAME | NUMBER | STRING+ | '...' | 'None' | 'True' | 'False') +testlist_comp: (test|star_expr) ( comp_for | (',' (test|star_expr))* [','] ) +trailer: '(' [arglist] ')' | '[' subscriptlist ']' | '.' NAME +subscriptlist: subscript (',' subscript)* [','] +subscript: test | [test] ':' [test] [sliceop] +sliceop: ':' [test] +exprlist: (expr|star_expr) (',' (expr|star_expr))* [','] +testlist: test (',' test)* [','] +dictorsetmaker: ( ((test ':' test | '**' expr) + (comp_for | (',' (test ':' test | '**' expr))* [','])) | + ((test | star_expr) + (comp_for | (',' (test | star_expr))* [','])) ) + +classdef: 'class' NAME ['(' [arglist] ')'] ':' suite + +arglist: argument (',' argument)* [','] + +# The reason that keywords are test nodes instead of NAME is that using NAME +# results in an ambiguity. ast.c makes sure it's a NAME. +# "test '=' test" is really "keyword '=' test", but we have no such token. +# These need to be in a single rule to avoid grammar that is ambiguous +# to our LL(1) parser. Even though 'test' includes '*expr' in star_expr, +# we explicitly match '*' here, too, to give it proper precedence. +# Illegal combinations and orderings are blocked in ast.c: +# multiple (test comp_for) arguments are blocked; keyword unpackings +# that precede iterable unpackings are blocked; etc. +argument: ( test [comp_for] | + test '=' test | + '**' test | + '*' test ) + +comp_iter: comp_for | comp_if +comp_for: ['async'] 'for' exprlist 'in' or_test [comp_iter] +comp_if: 'if' test_nocond [comp_iter] + +# not used in grammar, but may appear in "node" passed from Parser to Compiler +encoding_decl: NAME + +yield_expr: 'yield' [yield_arg] +yield_arg: 'from' test | testlist diff --git a/jedi/parser/utils.py b/jedi/parser/utils.py index 77287eaa..d929a633 100644 --- a/jedi/parser/utils.py +++ b/jedi/parser/utils.py @@ -159,7 +159,6 @@ class ParserPickling(object): # 0 means version is not defined (= always delete cache): if data.get('version', 0) != self.version: self.clear_cache() - self.__index = {} else: self.__index = data['index'] return self.__index @@ -179,6 +178,7 @@ class ParserPickling(object): def clear_cache(self): shutil.rmtree(self._cache_directory()) + self.__index = {} def _get_hashed_path(self, path): return self._get_path('%s.pkl' % hashlib.md5(path.encode("utf-8")).hexdigest()) diff --git a/test/completion/classes.py b/test/completion/classes.py index 3ea9b2d1..c3062e13 100644 --- a/test/completion/classes.py +++ b/test/completion/classes.py @@ -162,6 +162,7 @@ class Mixin(SuperClass): def method_mixin(self): return int +#? 20 SuperClass class SubClass(SuperClass): class_sub = 3 def __init__(self): diff --git a/test/run.py b/test/run.py index 4e5d6d6e..220fa1de 100755 --- a/test/run.py +++ b/test/run.py @@ -25,10 +25,10 @@ multiple Python versions. .. _tox: http://testrun.org/tox Integration test cases are located in ``test/completion`` directory -and each test cases are indicated by the comment ``#?`` (completions / -definitions), ``#!`` (assignments) and ``#<`` (usages). There is also -support for third party libraries. In a normal test run they are not -being executed, you have to provide a ``--thirdparty`` option. +and each test case is indicated by either the comment ``#?`` (completions / +definitions), ``#!`` (assignments), or ``#<`` (usages). +There is also support for third party libraries. In a normal test run they are +not being executed, you have to provide a ``--thirdparty`` option. In addition to standard `-k` and `-m` options in py.test, you can use `-T` (`--test-files`) option to specify integration test cases to run. @@ -64,7 +64,7 @@ happening. Auto-Completion +++++++++++++++ -Uses comments to specify a test in the next line. The comment says, which +Uses comments to specify a test in the next line. The comment says which results are expected. The comment always begins with `#?`. The last row symbolizes the cursor. @@ -94,8 +94,8 @@ Tests look like this:: #! ['abc=1'] abc -Additionally it is possible to add a number which describes to position of -the test (otherwise it's just end of line):: +Additionally it is possible to specify the column by adding a number, which +describes the position of the test (otherwise it's just the end of line):: #! 2 ['abc=1'] abc diff --git a/test/test_evaluate/test_sys_path.py b/test/test_evaluate/test_sys_path.py index 98e60b63..58fa5718 100644 --- a/test/test_evaluate/test_sys_path.py +++ b/test/test_evaluate/test_sys_path.py @@ -41,9 +41,9 @@ def test_get_venv_path(venv): site_pkgs = (glob(pjoin(venv, 'lib', 'python*', 'site-packages')) + glob(pjoin(venv, 'lib', 'site-packages')))[0] ETALON = [ - site_pkgs, pjoin('/path', 'from', 'egg-link'), pjoin(site_pkgs, '.', 'relative', 'egg-link', 'path'), + site_pkgs, pjoin(site_pkgs, 'dir-from-foo-pth'), ] diff --git a/tox.ini b/tox.ini index dadbb2fe..97b77a14 100644 --- a/tox.ini +++ b/tox.ini @@ -13,7 +13,7 @@ setenv = # tox corrupts __pycache__, solution from here: PYTHONDONTWRITEBYTECODE=1 commands = - py.test [] + py.test {posargs:jedi test} [testenv:py26] deps = unittest2