Add referenced types to facades, making result types discoverable in the REPL

Facades declare the types their methods return or contain as ReferencedTypes (with an optional
whitelist of members for curation, e.g. for LanguageServerSymbol, and a flag for inclusion in the
facade description). Type documentation is generated from class docstrings, attribute annotations,
properties and method signatures/docstrings, and is available via s.info("<facade>.<Type>") or by
bare type name; method documentation points to the documentation of a referenced return type.
Annotations are rendered without module paths, such that the names in signatures are the names by
which types can be looked up. s.info accepts several items at once, reporting unknown items inline.

All APIs declare their result types; result classes declare their attributes at class level.
This commit is contained in:
Dominik Jain authored and Dominik Jain committed 2026-09-15 12:50:44 +02:00
1 parent 05603b2706
commit 6cb3d37e09
11 files changed
+299 -34

No files matched your search

+25 -2
View File
@@ -52,6 +52,29 @@ class TestReplExecution:
method_info = repl.execute('s.info("lsp.find_symbol")')
assert "name_path_pattern" in method_info
def test_type_discovery(self, repl: SerenaRepl) -> None:
# signatures render type names without module paths, and point to the documentation of referenced return types
method_info = repl.execute('s.info("lsp.find_symbol")')
assert "-> LspSymbolCollection" in method_info and "lsp_api." not in method_info
assert 's.info("lsp.LspSymbolCollection")' in method_info
# the facade description includes types declared as important in full and lists the others by name
facade_info = repl.execute('s.info("lsp")')
assert "type LspSymbolCollection" in facade_info and "symbols: list[LanguageServerSymbol]" in facade_info
assert "type LanguageServerSymbol" not in facade_info and "LanguageServerSymbol" in facade_info
# types can be requested via the facade or by bare name, and their curated members are documented
type_info = repl.execute('s.info("lsp.LanguageServerSymbol")')
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
assert "represent" not in repl.execute('s.info("LspSymbolCollection")') # the representation mechanism is not exposed
def test_info_documents_several_items(self, repl: SerenaRepl) -> None:
info = repl.execute('s.info("lsp.find_symbol", "nope", "lsp.LspSymbolCollection")')
assert "lsp.find_symbol(" in info and "type LspSymbolCollection" in info
assert "Unknown item 'nope'" in info # an unknown item does not prevent the documentation of the others
class TestFacade:
"""Tests the indirection between facades and their implementations."""
@@ -131,7 +154,7 @@ class TestFacade:
facade = Facade.from_api(self.DummyApi(MagicMock()), ApiScope())
assert facade.add(1, 2) == 3
assert "dummy.add(a: int, b: int) -> int" in facade.describe()
assert "Adds two numbers." in facade.describe_method("add")
assert "Adds two numbers." in facade.describe_member("add")
def test_disabled_methods_are_inaccessible_and_undocumented(self) -> None:
facade = Facade.from_api(self.DummyApi(MagicMock()), self._scope(excluded_apis=["dummy.secret"]))
@@ -139,7 +162,7 @@ class TestFacade:
with pytest.raises(AttributeError):
facade.secret()
with pytest.raises(ValueError):
facade.describe_method("secret")
facade.describe_member("secret")
assert "secret" not in facade.describe()
assert "_internal" not in facade.describe()