Move the docstring checking code to the names

This commit is contained in:
Dave Halter
2019-12-21 20:06:37 +01:00
parent 536fd8c7c0
commit fcede44c2a
9 changed files with 105 additions and 77 deletions

View File

@@ -115,6 +115,11 @@ def goto_or_infer(request, Script):
return lambda code, *args, **kwargs: getattr(Script(code), request.param)(*args, **kwargs) 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') @pytest.fixture(scope='session')
def has_typing(environment): def has_typing(environment):
if environment.version_info >= (3, 5, 0): if environment.version_info >= (3, 5, 0):

View File

@@ -742,9 +742,6 @@ class _Help(object):
@memoize_method @memoize_method
def _get_values(self, fast): def _get_values(self, fast):
if isinstance(self._name, ImportName) and fast:
return {}
if self._name.api_type == 'statement': if self._name.api_type == 'statement':
return {} return {}
@@ -756,29 +753,6 @@ class _Help(object):
See :attr:`doc` for example. See :attr:`doc` for example.
""" """
full_doc = '' if isinstance(self._name, ImportName) and fast:
# Using the first docstring that we see. return ''
for value in self._get_values(fast=fast): return self._name.py__doc__(include_signatures=not raw)
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

View File

@@ -15,41 +15,11 @@ except ImportError:
pydoc_topics = None pydoc_topics = None
def get_operator(inference_state, string, pos):
return Keyword(inference_state, string, pos)
class KeywordName(AbstractArbitraryName): class KeywordName(AbstractArbitraryName):
api_type = u'keyword' api_type = u'keyword'
def infer(self): def py__doc__(self, include_signatures=False):
return [Keyword(self.inference_state, self.string_name, (0, 0))] return imitate_pydoc(self.string_name)
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 imitate_pydoc(string): def imitate_pydoc(string):

View File

@@ -288,6 +288,10 @@ class CompiledName(AbstractNameDefinition):
self.parent_context = parent_context self.parent_context = parent_context
self.string_name = name self.string_name = name
def py__doc__(self, include_signatures=False):
value, = self.infer()
return value.py__doc__()
def _get_qualified_names(self): def _get_qualified_names(self):
parent_qualified_names = self.parent_context.get_qualified_names() parent_qualified_names = self.parent_context.get_qualified_names()
if parent_qualified_names is None: if parent_qualified_names is None:

View File

@@ -136,13 +136,14 @@ def _python_to_stub_names(names, fallback_to_python=False):
yield name 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) assert not (only_stubs and prefer_stubs)
with debug.increase_indent_cm('convert names'): with debug.increase_indent_cm('convert names'):
if only_stubs or prefer_stubs: if only_stubs or prefer_stubs:
return _python_to_stub_names(names, fallback_to_python=prefer_stubs) return _python_to_stub_names(names, fallback_to_python=prefer_stubs)
else: 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): def convert_values(values, only_stubs=False, prefer_stubs=False, ignore_compiled=True):

View File

@@ -1,7 +1,7 @@
from jedi.inference.base_value import ValueWrapper from jedi.inference.base_value import ValueWrapper
from jedi.inference.value.module import ModuleValue from jedi.inference.value.module import ModuleValue
from jedi.inference.filters import ParserTreeFilter, \ from jedi.inference.filters import ParserTreeFilter
TreeNameDefinition from jedi.inference.names import StubName
from jedi.inference.gradual.typing import TypingModuleFilterWrapper from jedi.inference.gradual.typing import TypingModuleFilterWrapper
from jedi.inference.context import ModuleContext from jedi.inference.context import ModuleContext
@@ -81,17 +81,8 @@ class TypingModuleContext(ModuleContext):
yield f 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): class StubFilter(ParserTreeFilter):
name_class = _StubName name_class = StubName
def _is_name_reachable(self, name): def _is_name_reachable(self, name):
if not super(StubFilter, self)._is_name_reachable(name): if not super(StubFilter, self)._is_name_reachable(name):

View File

@@ -3,6 +3,7 @@ from abc import abstractmethod
from parso.tree import search_ancestor from parso.tree import search_ancestor
from jedi._compatibility import Parameter from jedi._compatibility import Parameter
from jedi.parser_utils import clean_scope_docstring
from jedi.inference.utils import unite from jedi.inference.utils import unite
from jedi.inference.base_value import ValueSet, NO_VALUES from jedi.inference.base_value import ValueSet, NO_VALUES
from jedi.inference import docstrings 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 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): class AbstractNameDefinition(object):
start_pos = None start_pos = None
string_name = None string_name = None
@@ -59,6 +84,9 @@ class AbstractNameDefinition(object):
def is_import(self): def is_import(self):
return False return False
def py__doc__(self, include_signatures=False):
return ''
@property @property
def api_type(self): def api_type(self):
return self.parent_context.api_type return self.parent_context.api_type
@@ -197,6 +225,20 @@ class ValueNameMixin(object):
def infer(self): def infer(self):
return ValueSet([self._value]) 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): def _get_qualified_names(self):
return self._value.get_qualified_names() return self._value.get_qualified_names()
@@ -285,6 +327,17 @@ class TreeNameDefinition(AbstractTreeName):
node = node.parent node = node.parent
return indexes 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): class _ParamMixin(object):
def maybe_positional_argument(self, include_star=True): def maybe_positional_argument(self, include_star=True):
@@ -531,6 +584,10 @@ class ImportName(AbstractNameDefinition):
def api_type(self): def api_type(self):
return 'module' return 'module'
def py__doc__(self, include_signatures=False):
print('la', (self.goto()))
return _merge_name_docs(self.goto())
class SubModuleName(ImportName): class SubModuleName(ImportName):
_level = 1 _level = 1
@@ -549,3 +606,26 @@ class NameWrapper(object):
def __repr__(self): def __repr__(self):
return '%s(%s)' % (self.__class__.__name__, self._wrapped_name) 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

View File

@@ -1,3 +1,5 @@
from textwrap import dedent
import pytest import pytest

View File

@@ -3,9 +3,10 @@ import os
import pytest import pytest
from parso.utils import PythonVersionInfo 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, \ from jedi.inference.value import TreeInstance, BoundMethod, FunctionValue, \
MethodValue, ClassValue MethodValue, ClassValue
from jedi.inference.names import StubName
TYPESHED_PYTHON3 = os.path.join(typeshed.TYPESHED_PATH, 'stdlib', '3') 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): def test_sys_hexversion(Script):
script = Script('import sys; sys.hexversion') script = Script('import sys; sys.hexversion')
def_, = script.complete() 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 assert typeshed.TYPESHED_PATH in def_.module_path
def_, = script.infer() def_, = script.infer()
assert def_.name == 'int' assert def_.name == 'int'