Skip to content

Commit

Permalink
Add external xref formatting
Browse files Browse the repository at this point in the history
This depends on the project specifically, so we added a config field called
`ts_xref_formatter` which is a function that formats the external references.
  • Loading branch information
hoodmane committed Sep 25, 2023
1 parent 2b13282 commit b8b1a59
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 9 deletions.
1 change: 1 addition & 0 deletions sphinx_js/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ def setup(app: Sphinx) -> None:
"js_source_path", default=["../"], rebuild="env", types=[str, list]
)
app.add_config_value("jsdoc_config_path", default=None, rebuild="env")
app.add_config_value("ts_xref_formatter", None, "env")

# We could use a callable as the "default" param here, but then we would
# have had to duplicate or build framework around the logic that promotes
Expand Down
1 change: 1 addition & 0 deletions sphinx_js/ir.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class TypeXRefInternal(TypeXRef):

@define
class TypeXRefExternal(TypeXRef):
package: str
sourcefilename: str
qualifiedName: str

Expand Down
29 changes: 24 additions & 5 deletions sphinx_js/renderers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import textwrap
from collections.abc import Callable, Iterator
from functools import partial
from re import sub
from typing import Any, Literal

Expand All @@ -10,6 +11,7 @@
from docutils.utils import new_document
from jinja2 import Environment, PackageLoader
from sphinx.application import Sphinx
from sphinx.config import Config
from sphinx.errors import SphinxError
from sphinx.util import logging, rst

Expand All @@ -29,6 +31,7 @@
Type,
TypeParam,
TypeXRef,
TypeXRefExternal,
TypeXRefInternal,
)
from .jsdoc import Analyzer as JsAnalyzer
Expand All @@ -54,10 +57,27 @@ class JsRenderer:

_renderer_type: Literal["function", "class", "attribute"]
_template: str
_xref_formatter: Callable[[TypeXRefExternal], str]
_partial_path: list[str]
_explicit_formal_params: str
_content: list[str]
_options: dict[str, Any]

def _template_vars(self, name: str, obj: TopLevel) -> dict[str, Any]:
raise NotImplementedError

def _set_xref_formatter(
self, formatter: Callable[[Config, TypeXRefExternal], str] | None
) -> None:
if formatter:
self._xref_formatter = partial(formatter, self._app.config)
return

def default_xref_formatter(xref: TypeXRefExternal) -> str:
return xref.name

self._xref_formatter = default_xref_formatter

def __init__(
self,
directive: Directive,
Expand All @@ -71,15 +91,13 @@ def __init__(
directive.state.document.settings.tab_width = 8

self._directive = directive
self._app = app
self._set_xref_formatter(app.config.ts_xref_formatter)

# content, arguments, options, app: all need to be accessible to
# template_vars, so we bring them in on construction and stow them away
# on the instance so calls to template_vars don't need to concern
# themselves with what it needs.
self._app = app
self._partial_path: list[str]
self._explicit_formal_params: str

(
self._partial_path,
self._explicit_formal_params,
Expand Down Expand Up @@ -295,7 +313,8 @@ def render_xref(self, s: TypeXRef, escape: bool = False) -> str:
name = rst.escape(s.name)
result = f":js:class:`{name}`"
else:
result = s.name
assert isinstance(s, TypeXRefExternal)
result = self._xref_formatter(s)
if escape:
result = rst.escape(result)
return result
Expand Down
7 changes: 6 additions & 1 deletion sphinx_js/typedoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,7 @@ class ReferenceType(TypeBase):
type: Literal["reference"]
name: str
target: int | Target | None
package: str | None = None
refersToTypeParameter: bool = False

def _render_name_root(self, converter: Converter) -> Iterator[str | ir.TypeXRef]:
Expand All @@ -996,8 +997,12 @@ def _render_name_root(self, converter: Converter) -> Iterator[str | ir.TypeXRef]
yield ir.TypeXRefInternal(self.name, node.path)
return
assert isinstance(self.target, Target)
assert self.package
yield ir.TypeXRefExternal(
self.name, self.target.sourceFileName, self.target.qualifiedName
self.name,
self.package,
self.target.sourceFileName,
self.target.qualifiedName,
)


Expand Down
38 changes: 36 additions & 2 deletions tests/test_renderers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
Param,
Return,
TypeParam,
TypeXRefExternal,
TypeXRefInternal,
)
from sphinx_js.renderers import AutoFunctionRenderer, JsRenderer
Expand Down Expand Up @@ -50,15 +51,29 @@ def test_render_description():


@pytest.fixture()
def function_render() -> AutoFunctionRenderer:
def function_renderer():
class _config:
pass

class _app:
config = _config

renderer = AutoFunctionRenderer.__new__(AutoFunctionRenderer)
renderer._app = _app
renderer._explicit_formal_params = None
renderer._content = []
renderer._set_xref_formatter(None)
return renderer


@pytest.fixture()
def function_render(function_renderer) -> AutoFunctionRenderer:
def function_render(partial_path=None, use_short_name=False, **args):
if not partial_path:
partial_path = ["blah"]
return renderer.rst(partial_path, make_function(**args), use_short_name)
return function_renderer.rst(
partial_path, make_function(**args), use_short_name
)

return function_render

Expand Down Expand Up @@ -202,6 +217,25 @@ def test_func_render_type_params(function_render):
)


def test_render_xref(function_renderer: AutoFunctionRenderer):
assert (
function_renderer.render_type([TypeXRefInternal(name="A", path=["a.", "A"])])
== ":js:class:`A`"
)
xref_external = TypeXRefExternal("A", "blah", "a.ts", "a.A")
assert function_renderer.render_type([xref_external]) == "A"
res = []

def xref_render(config, val):
res.append([config, val])
return val.package + "::" + val.name

function_renderer._set_xref_formatter(xref_render)
assert function_renderer.render_type([xref_external]) == "blah::A"
assert res[0][0] == function_renderer._app.config
assert res[0][1] == xref_external


def test_func_render_param_type(function_render):
assert function_render(
description="this is a description",
Expand Down
7 changes: 6 additions & 1 deletion tests/test_typedoc_analysis/test_typedoc_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,12 @@ def test_utility_types(self):
obj = self.analyzer.get_object(["partial"])
t = deepcopy(obj.type)
t[0].sourcefilename = "xxx"
assert t == [TypeXRefExternal("Partial", "xxx", "Partial"), "<", "string", ">"]
assert t == [
TypeXRefExternal("Partial", "typescript", "xxx", "Partial"),
"<",
"string",
">",
]

def test_constrained_by_property(self):

Expand Down

0 comments on commit b8b1a59

Please sign in to comment.