From fcede44c2adb2fdd3d410fec7497299fade100a0 Mon Sep 17 00:00:00 2001 From: Dave Halter Date: Sat, 21 Dec 2019 20:06:37 +0100 Subject: [PATCH] Move the docstring checking code to the names --- conftest.py | 5 ++ jedi/api/classes.py | 32 +------- jedi/api/keywords.py | 34 +------- jedi/inference/compiled/value.py | 4 + jedi/inference/gradual/conversion.py | 5 +- jedi/inference/gradual/stub_value.py | 15 +--- jedi/inference/names.py | 80 +++++++++++++++++++ test/test_api/test_documentation.py | 2 + .../test_gradual/test_typeshed.py | 5 +- 9 files changed, 105 insertions(+), 77 deletions(-) diff --git a/conftest.py b/conftest.py index f82b7c67..92595b66 100644 --- a/conftest.py +++ b/conftest.py @@ -115,6 +115,11 @@ 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') def has_typing(environment): if environment.version_info >= (3, 5, 0): diff --git a/jedi/api/classes.py b/jedi/api/classes.py index 6786589b..b9e459eb 100644 --- a/jedi/api/classes.py +++ b/jedi/api/classes.py @@ -742,9 +742,6 @@ class _Help(object): @memoize_method def _get_values(self, fast): - if isinstance(self._name, ImportName) and fast: - return {} - if self._name.api_type == 'statement': return {} @@ -756,29 +753,6 @@ class _Help(object): See :attr:`doc` for example. """ - full_doc = '' - # Using the first docstring that we see. - for value in self._get_values(fast=fast): - if full_doc: - # In case we have multiple values, just return all of them - # separated by a few dashes. - full_doc += '\n' + '-' * 30 + '\n' - - doc = value.py__doc__() - - signature_text = '' - if self._name.is_value_name: - if not raw: - signature_text = _format_signatures(value) - if not doc and value.is_stub(): - for c in convert_values(ValueSet({value}), ignore_compiled=False): - doc = c.py__doc__() - if doc: - break - - if signature_text and doc: - full_doc += signature_text + '\n\n' + doc - else: - full_doc += signature_text + doc - - return full_doc + if isinstance(self._name, ImportName) and fast: + return '' + return self._name.py__doc__(include_signatures=not raw) diff --git a/jedi/api/keywords.py b/jedi/api/keywords.py index 822f80ff..3d3d317d 100644 --- a/jedi/api/keywords.py +++ b/jedi/api/keywords.py @@ -15,41 +15,11 @@ except ImportError: pydoc_topics = None -def get_operator(inference_state, string, pos): - return Keyword(inference_state, string, pos) - - class KeywordName(AbstractArbitraryName): api_type = u'keyword' - def infer(self): - return [Keyword(self.inference_state, self.string_name, (0, 0))] - - -class Keyword(object): - api_type = u'keyword' - - def __init__(self, inference_state, name, pos): - self.name = KeywordName(inference_state, name) - self.start_pos = pos - self.parent = inference_state.builtins_module - - @property - def names(self): - """ For a `parsing.Name` like comparision """ - return [self.name] - - def py__doc__(self): - return imitate_pydoc(self.name.string_name) - - def get_signatures(self): - # TODO this makes no sense, I think Keyword should somehow merge with - # Value to make it easier for the api/classes.py to deal with all - # of it. - return [] - - def __repr__(self): - return '<%s: %s>' % (type(self).__name__, self.name) + def py__doc__(self, include_signatures=False): + return imitate_pydoc(self.string_name) def imitate_pydoc(string): diff --git a/jedi/inference/compiled/value.py b/jedi/inference/compiled/value.py index 2580073b..9a7dffa3 100644 --- a/jedi/inference/compiled/value.py +++ b/jedi/inference/compiled/value.py @@ -288,6 +288,10 @@ class CompiledName(AbstractNameDefinition): self.parent_context = parent_context self.string_name = name + def py__doc__(self, include_signatures=False): + value, = self.infer() + return value.py__doc__() + def _get_qualified_names(self): parent_qualified_names = self.parent_context.get_qualified_names() if parent_qualified_names is None: diff --git a/jedi/inference/gradual/conversion.py b/jedi/inference/gradual/conversion.py index 35271b8a..8f6917ef 100644 --- a/jedi/inference/gradual/conversion.py +++ b/jedi/inference/gradual/conversion.py @@ -136,13 +136,14 @@ def _python_to_stub_names(names, fallback_to_python=False): yield name -def convert_names(names, only_stubs=False, prefer_stubs=False): +def convert_names(names, only_stubs=False, prefer_stubs=False, prefer_stub_to_compiled=True): assert not (only_stubs and prefer_stubs) with debug.increase_indent_cm('convert names'): if only_stubs or prefer_stubs: return _python_to_stub_names(names, fallback_to_python=prefer_stubs) else: - return _try_stub_to_python_names(names, prefer_stub_to_compiled=True) + return _try_stub_to_python_names( + names, prefer_stub_to_compiled=prefer_stub_to_compiled) def convert_values(values, only_stubs=False, prefer_stubs=False, ignore_compiled=True): diff --git a/jedi/inference/gradual/stub_value.py b/jedi/inference/gradual/stub_value.py index 1f6376fa..34637305 100644 --- a/jedi/inference/gradual/stub_value.py +++ b/jedi/inference/gradual/stub_value.py @@ -1,7 +1,7 @@ from jedi.inference.base_value import ValueWrapper from jedi.inference.value.module import ModuleValue -from jedi.inference.filters import ParserTreeFilter, \ - TreeNameDefinition +from jedi.inference.filters import ParserTreeFilter +from jedi.inference.names import StubName from jedi.inference.gradual.typing import TypingModuleFilterWrapper from jedi.inference.context import ModuleContext @@ -81,17 +81,8 @@ class TypingModuleContext(ModuleContext): yield f -# From here on down we make looking up the sys.version_info fast. -class _StubName(TreeNameDefinition): - def infer(self): - inferred = super(_StubName, self).infer() - if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys': - return [VersionInfo(c) for c in inferred] - return inferred - - class StubFilter(ParserTreeFilter): - name_class = _StubName + name_class = StubName def _is_name_reachable(self, name): if not super(StubFilter, self)._is_name_reachable(name): diff --git a/jedi/inference/names.py b/jedi/inference/names.py index 69d466e7..3a4919e6 100644 --- a/jedi/inference/names.py +++ b/jedi/inference/names.py @@ -3,6 +3,7 @@ from abc import abstractmethod from parso.tree import search_ancestor from jedi._compatibility import Parameter +from jedi.parser_utils import clean_scope_docstring from jedi.inference.utils import unite from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference import docstrings @@ -10,6 +11,30 @@ from jedi.cache import memoize_method from jedi.inference.helpers import deep_ast_copy, infer_call_of_leaf +def _merge_name_docs(names): + doc = '' + for name in names: + if doc: + # In case we have multiple values, just return all of them + # separated by a few dashes. + doc += '\n' + '-' * 30 + '\n' + doc += name.py__doc__() + return doc + + +def _merge_docs_and_signature(values, doc): + signature_text = '\n'.join( + signature.to_string() + for value in values + for signature in value.get_signatures() + ) + + if signature_text and doc: + return signature_text + '\n\n' + doc + else: + return signature_text + doc + + class AbstractNameDefinition(object): start_pos = None string_name = None @@ -59,6 +84,9 @@ class AbstractNameDefinition(object): def is_import(self): return False + def py__doc__(self, include_signatures=False): + return '' + @property def api_type(self): return self.parent_context.api_type @@ -197,6 +225,20 @@ class ValueNameMixin(object): def infer(self): return ValueSet([self._value]) + def py__doc__(self, include_signatures=False): + from jedi.inference.gradual.conversion import convert_names + doc = '' + if self._value.is_stub(): + names = convert_names([self], prefer_stub_to_compiled=False) + if self not in names: + doc = _merge_name_docs(names) + if not doc: + doc = self._value.py__doc__() + + if include_signatures: + doc = _merge_docs_and_signature([self._value], doc) + return doc + def _get_qualified_names(self): return self._value.get_qualified_names() @@ -285,6 +327,17 @@ class TreeNameDefinition(AbstractTreeName): node = node.parent return indexes + def py__doc__(self, include_signatures=False): + if self.api_type in ('function', 'class'): + return clean_scope_docstring(self.tree_name.get_definition()) + + if self.api_type == 'module': + names = self.goto() + if self not in names: + print('la', _merge_name_docs(names)) + return _merge_name_docs(names) + return super(TreeNameDefinition, self).py__doc__(include_signatures) + class _ParamMixin(object): def maybe_positional_argument(self, include_star=True): @@ -531,6 +584,10 @@ class ImportName(AbstractNameDefinition): def api_type(self): return 'module' + def py__doc__(self, include_signatures=False): + print('la', (self.goto())) + return _merge_name_docs(self.goto()) + class SubModuleName(ImportName): _level = 1 @@ -549,3 +606,26 @@ class NameWrapper(object): def __repr__(self): return '%s(%s)' % (self.__class__.__name__, self._wrapped_name) + + +# From here on down we make looking up the sys.version_info fast. +class StubName(TreeNameDefinition): + def infer(self): + inferred = super(StubName, self).infer() + if self.string_name == 'version_info' and self.get_root_context().py__name__() == 'sys': + from jedi.inference.gradual.stub_value import VersionInfo + return [VersionInfo(c) for c in inferred] + return inferred + + def py__doc__(self, include_signatures=False): + from jedi.inference.gradual.conversion import convert_names + names = convert_names([self], prefer_stub_to_compiled=False) + if self in names: + doc = super(StubName, self).py__doc__(include_signatures) + else: + doc = _merge_name_docs(names) + if include_signatures: + parent = self.tree_name.parent + if parent.type in ('funcdef', 'classdef') and parent.name is self.tree_name: + doc = _merge_docs_and_signature(self.infer(), doc) + return doc diff --git a/test/test_api/test_documentation.py b/test/test_api/test_documentation.py index 645ad58f..7e8ac5c9 100644 --- a/test/test_api/test_documentation.py +++ b/test/test_api/test_documentation.py @@ -1,3 +1,5 @@ +from textwrap import dedent + import pytest diff --git a/test/test_inference/test_gradual/test_typeshed.py b/test/test_inference/test_gradual/test_typeshed.py index a3076bdc..adf7a114 100644 --- a/test/test_inference/test_gradual/test_typeshed.py +++ b/test/test_inference/test_gradual/test_typeshed.py @@ -3,9 +3,10 @@ import os import pytest from parso.utils import PythonVersionInfo -from jedi.inference.gradual import typeshed, stub_value +from jedi.inference.gradual import typeshed from jedi.inference.value import TreeInstance, BoundMethod, FunctionValue, \ MethodValue, ClassValue +from jedi.inference.names import StubName TYPESHED_PYTHON3 = os.path.join(typeshed.TYPESHED_PATH, 'stdlib', '3') @@ -125,7 +126,7 @@ def test_sys_getwindowsversion(Script, environment): def test_sys_hexversion(Script): script = Script('import sys; sys.hexversion') def_, = script.complete() - assert isinstance(def_._name, stub_value._StubName), def_._name + assert isinstance(def_._name, StubName), def_._name assert typeshed.TYPESHED_PATH in def_.module_path def_, = script.infer() assert def_.name == 'int'