mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 12:15:10 +00:00
Follow parameter annotations in REPL type documentation; support enums
Referenced types are now also discovered through the parameter annotations of type members and facade methods (not only through return types), such that e.g. SymbolKind (a parameter type of LanguageServerSymbol.iter_ancestors) is documented along with LanguageServerSymbol. Enums are documented with their members and values. The lsp facade declares SymbolKind as a referenced type.
This commit is contained in:
1 parent
24b41dcb88
commit
8d1fb7424c
3 files changed
+44
-13
No files matched your search
@@ -331,6 +331,7 @@ class LspApi(FacadeApi):
|
||||
ReferencedType(LspReferenceCollection),
|
||||
ReferencedType(ReferenceInLanguageServerSymbol),
|
||||
ReferencedType(LspDiagnostics),
|
||||
ReferencedType(SymbolKind),
|
||||
],
|
||||
)
|
||||
|
||||
@@ -453,8 +454,8 @@ class LspApi(FacadeApi):
|
||||
:param include_info: whether to include additional info (hover-like, typically including docstring and signature),
|
||||
about the symbol (ignored if include_body is True). Info is never included for child symbols.
|
||||
Note: Depending on the language, this can be slow (e.g., C/C++).
|
||||
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers)
|
||||
:param exclude_kinds: (optional) list of LSP symbol kinds (integers) to exclude.
|
||||
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers, i.e. values of `SymbolKind`)
|
||||
:param exclude_kinds: (optional) list of LSP symbol kinds (integers, i.e. values of `SymbolKind`) to exclude.
|
||||
:param substring_matching: If True, use substring matching for the last segment of `name_path_pattern`
|
||||
(i.e. the name of the symbol, e.g. "foo" in "Class/foo" or "my_method" in "my_method").
|
||||
:param max_matches: Maximum number of permitted matches. If exceeded, an error containing a shortened result is raised,
|
||||
@@ -517,8 +518,8 @@ class LspApi(FacadeApi):
|
||||
|
||||
:param name_path: name path of the symbol
|
||||
:param relative_path: the relative path to the file containing the symbol for which to find references.
|
||||
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers)
|
||||
:param exclude_kinds: optional list of LSP symbol kinds (integers) to exclude.
|
||||
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers, i.e. values of `SymbolKind`)
|
||||
:param exclude_kinds: optional list of LSP symbol kinds (integers, i.e. values of `SymbolKind`) to exclude.
|
||||
:param max_answer_chars: max result length; -1 for default
|
||||
:return: the references to the symbol
|
||||
"""
|
||||
@@ -554,8 +555,8 @@ class LspApi(FacadeApi):
|
||||
Note that here you can't pass a directory but must pass a file.
|
||||
:param include_info: whether to include additional info (hover-like, typically including docstring and signature),
|
||||
about the implementing symbols.
|
||||
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers)
|
||||
:param exclude_kinds: (optional) list of LSP symbol kinds (integers) to exclude.
|
||||
:param include_kinds: (optional) limits results to the given LSP symbol kinds (integers, i.e. values of `SymbolKind`)
|
||||
:param exclude_kinds: (optional) list of LSP symbol kinds (integers, i.e. values of `SymbolKind`) to exclude.
|
||||
:param max_answer_chars: max result length; -1 for default
|
||||
:return: the symbols implementing the given symbol
|
||||
"""
|
||||
|
||||
@@ -11,6 +11,7 @@ import typing
|
||||
from abc import ABC
|
||||
from collections.abc import Callable, Sequence
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any, TypeVar
|
||||
|
||||
from serena.config.serena_config import ApiInclusionDefinition
|
||||
@@ -84,8 +85,10 @@ class ReferencedType:
|
||||
def get_referenced_type_names(self) -> list[str]:
|
||||
"""
|
||||
:return: the names of the types appearing in the annotations of the described members (attributes, properties,
|
||||
method return types), in order of appearance (each name at most once, excluding the type itself)
|
||||
method parameters and return types), in order of appearance (each name at most once, excluding the type itself)
|
||||
"""
|
||||
if self.is_enum():
|
||||
return []
|
||||
annotations: list[str] = []
|
||||
type_hints = typing.get_type_hints(self.cls)
|
||||
for member_name in self._get_member_names():
|
||||
@@ -93,7 +96,7 @@ class ReferencedType:
|
||||
if isinstance(member, property) and member.fget is not None:
|
||||
annotations.append(format_annotation(inspect.signature(member.fget).return_annotation))
|
||||
elif inspect.isfunction(member):
|
||||
annotations.append(format_annotation(inspect.signature(member).return_annotation))
|
||||
annotations.append(format_signature(member))
|
||||
elif member_name in type_hints:
|
||||
annotations.append(format_annotation(type_hints[member_name]))
|
||||
names: list[str] = []
|
||||
@@ -103,6 +106,16 @@ class ReferencedType:
|
||||
names.append(name)
|
||||
return names
|
||||
|
||||
def is_enum(self) -> bool:
|
||||
return isinstance(self.cls, type) and issubclass(self.cls, Enum)
|
||||
|
||||
def _describe_enum(self) -> str:
|
||||
parts = [f"enum {self.name}"]
|
||||
if self.cls.__doc__:
|
||||
parts.append(f" {inspect.cleandoc(self.cls.__doc__).replace(chr(10), chr(10) + ' ')}")
|
||||
parts.append("members:\n" + "\n".join(f" {self.name}.{member.name} = {member.value!r}" for member in self.cls)) # type: ignore[attr-defined]
|
||||
return "\n".join(parts) + "\n"
|
||||
|
||||
@staticmethod
|
||||
def _first_doc_line(obj: Any) -> str:
|
||||
doc = inspect.getdoc(obj) or ""
|
||||
@@ -112,8 +125,10 @@ class ReferencedType:
|
||||
def describe(self) -> str:
|
||||
"""
|
||||
:return: the type's documentation: its docstring, attributes/properties with their types and methods with their
|
||||
signatures and documentation
|
||||
signatures and documentation; for enums, the members with their values
|
||||
"""
|
||||
if self.is_enum():
|
||||
return self._describe_enum()
|
||||
attributes: list[str] = []
|
||||
methods: list[str] = []
|
||||
type_hints = typing.get_type_hints(self.cls)
|
||||
@@ -325,7 +340,17 @@ class FacadeMethod:
|
||||
:return: the types referenced by the facade which appear in the method's return type annotation
|
||||
"""
|
||||
return_annotation = format_annotation(inspect.signature(self._implementation).return_annotation)
|
||||
return [t for t in self.parent.get_types() if re.search(rf"\b{re.escape(t.name)}\b", return_annotation)]
|
||||
return self._find_referenced_types(return_annotation)
|
||||
|
||||
def get_referenced_parameter_types(self) -> list[ReferencedType]:
|
||||
"""
|
||||
:return: the types referenced by the facade which appear in the annotations of the method's parameters
|
||||
"""
|
||||
parameters = inspect.signature(self._implementation).parameters.values()
|
||||
return self._find_referenced_types(" ".join(format_annotation(p.annotation) for p in parameters))
|
||||
|
||||
def _find_referenced_types(self, annotation_text: str) -> list[ReferencedType]:
|
||||
return [t for t in self.parent.get_types() if re.search(rf"\b{re.escape(t.name)}\b", annotation_text)]
|
||||
|
||||
def get_summary(self) -> str:
|
||||
"""
|
||||
@@ -336,16 +361,17 @@ class FacadeMethod:
|
||||
|
||||
def describe(self) -> str:
|
||||
"""
|
||||
:return: the method's signature and documentation, with a pointer to the documentation of its return type
|
||||
if it is a type referenced by the facade
|
||||
:return: the method's signature and documentation, with pointers to the documentation of the referenced types
|
||||
appearing in its return type and parameter annotations
|
||||
"""
|
||||
signature = format_signature(self._implementation)
|
||||
doc = inspect.getdoc(self._implementation) or "(no documentation)"
|
||||
text = f"{self.qualified_name}{signature}\n{doc}\n"
|
||||
referenced = self.get_referenced_return_types()
|
||||
referenced += [t for t in self.get_referenced_parameter_types() if t not in referenced]
|
||||
if referenced:
|
||||
pointers = ", ".join(f'`s.info("{t.name}")`' for t in referenced)
|
||||
text += f"Return type documentation: {pointers}\n"
|
||||
text += f"Type documentation: {pointers}\n"
|
||||
return text
|
||||
|
||||
def describe_summary(self) -> str:
|
||||
|
||||
@@ -72,6 +72,10 @@ class TestReplExecution:
|
||||
assert type_info == repl.execute('s.info("LanguageServerSymbol")')
|
||||
assert "get_name_path() -> str" in type_info and "iter_children()" in type_info
|
||||
assert "to_dict" not in type_info # not among the curated members
|
||||
|
||||
# types referenced by parameters are documented as well; enums are documented with their members
|
||||
assert "enum SymbolKind" in type_info and "SymbolKind.Class = 5" in type_info # via iter_ancestors(up_to_symbol_kind)
|
||||
assert "enum SymbolKind" in repl.execute('s.info("SymbolKind")')
|
||||
assert "represent()" not in repl.execute('s.info("LspSymbolCollection")') # the representation mechanism is not exposed
|
||||
|
||||
def test_contained_types_are_documented_once_per_session(self, repl: SerenaRepl) -> None:
|
||||
|
||||
Reference in new issue
Block a user