diff --git a/conftest.py b/conftest.py index 1eec4221..f82b7c67 100644 --- a/conftest.py +++ b/conftest.py @@ -110,6 +110,11 @@ def names(Script): return lambda code, **kwargs: Script(code).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') def has_typing(environment): if environment.version_info >= (3, 5, 0): diff --git a/jedi/api/__init__.py b/jedi/api/__init__.py index ea0ffe7b..2fac065d 100644 --- a/jedi/api/__init__.py +++ b/jedi/api/__init__.py @@ -12,6 +12,7 @@ arguments. import os import sys import warnings +import keyword import parso from parso.python import tree @@ -27,6 +28,7 @@ from jedi.api import interpreter from jedi.api import helpers from jedi.api.helpers import validate_line_column from jedi.api.completion import Completion +from jedi.api.keywords import KeywordName from jedi.api.environment import InterpreterEnvironment from jedi.api.project import get_default_project, Project from jedi.inference import InferenceState @@ -272,9 +274,6 @@ class Script(object): dynamic language, which means depending on an option you can have two different versions of a function. - .. note:: It is deprecated to use follow_imports and follow_builtin_imports as - positional arguments. Will be a keyword argument in 0.16.0. - :param follow_imports: The goto call will follow imports. :param follow_builtin_imports: If follow_imports is True will decide if it follow builtin imports. @@ -324,6 +323,29 @@ class Script(object): defs = [classes.Definition(self._inference_state, d) for d in set(names)] return helpers.sorted_definitions(defs) + @validate_line_column + def help(self, line=None, column=None): + """ + Works like goto and returns a list of Definition objects. Returns + additional definitions for keywords and operators. + + The additional definitions are of ``Definition(...).type == 'keyword'``. + These definitions do not have a lot of value apart from their docstring + attribute, which contains the output of Python's ``help()`` function. + + :rtype: list of :class:`classes.Definition` + """ + definitions = self.goto(line, column) + if definitions: + return definitions + leaf = self._module_node.get_leaf_for_position((line, column)) + if leaf.type in ('keyword', 'operator', 'error_leaf'): + reserved = self._grammar._pgen_grammar.reserved_syntax_strings.keys() + if leaf.value in reserved: + name = KeywordName(self._inference_state, leaf.value) + return [classes.Definition(self._inference_state, name)] + return [] + def usages(self, **kwargs): # Deprecated, will be removed. return self.find_references(*self._pos, **kwargs) diff --git a/test/blabla_test_documentation.py b/test/blabla_test_documentation.py deleted file mode 100644 index c0625339..00000000 --- a/test/blabla_test_documentation.py +++ /dev/null @@ -1,29 +0,0 @@ -def test_keyword_doc(Script): - r = list(Script("or").infer(1, 1)) - assert len(r) == 1 - assert len(r[0].doc) > 100 - - r = list(Script("asfdasfd").infer(1, 1)) - assert len(r) == 0 - - k = Script("fro").complete()[0] - imp_start = '\nThe ``import' - assert k.raw_doc.startswith(imp_start) - assert k.doc.startswith(imp_start) - - -def test_blablabla(Script): - defs = Script("import").infer() - assert len(defs) == 1 and [1 for d in defs if d.doc] - # unrelated to #44 - - -def test_operator_doc(Script): - r = list(Script("a == b").infer(1, 3)) - assert len(r) == 1 - assert len(r[0].doc) > 100 - - -def test_lambda(Script): - defs = Script('lambda x: x').infer(column=0) - assert [d.type for d in defs] == ['keyword'] diff --git a/test/test_api/test_documentation.py b/test/test_api/test_documentation.py new file mode 100644 index 00000000..645ad58f --- /dev/null +++ b/test/test_api/test_documentation.py @@ -0,0 +1,52 @@ +import pytest + + +def test_error_leaf_keyword_doc(Script): + d, = Script("or").help(1, 1) + assert len(d.docstring()) > 100 + assert d.name == 'or' + + +def test_error_leaf_operator_doc(Script): + d, = Script("==").help() + assert len(d.docstring()) > 100 + assert d.name == '==' + + +def test_keyword_completion(Script): + k = Script("fro").complete()[0] + imp_start = 'The "import' + assert k.docstring(raw=True).startswith(imp_start) + assert k.docstring().startswith(imp_start) + + +def test_import_keyword(Script): + d, = Script("import x").help(column=0) + assert d.docstring().startswith('The "import" statement') + # unrelated to #44 + + +def test_import_keyword_with_gotos(goto_or_infer): + assert not goto_or_infer("import x", column=0) + + +def test_operator_doc(Script): + d, = Script("a == b").help(1, 3) + assert len(d.docstring()) > 100 + + +def test_lambda(Script): + d, = Script('lambda x: x').help(column=0) + assert d.type == 'keyword' + assert d.docstring().startswith('Lambdas\n*******') + + +@pytest.mark.parametrize( + 'code, kwargs', [ + ('?', {}), + ('""', {}), + ('"', {}), + ] +) +def test_help_no_returns(Script, code, kwargs): + assert not Script(code).help(**kwargs)