Skip to content

Commit

Permalink
Add hook to decide whether to destructure argument (#75)
Browse files Browse the repository at this point in the history
This might seem a bit excessive but it's useful for Pyodide not to have to add
@destructure tags for all options arguments.
  • Loading branch information
hoodmane authored Sep 26, 2023
1 parent 0139964 commit f4e83fe
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 16 deletions.
1 change: 1 addition & 0 deletions sphinx_js/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ def setup(app: Sphinx) -> None:
)
app.add_config_value("jsdoc_config_path", default=None, rebuild="env")
app.add_config_value("ts_xref_formatter", None, "env")
app.add_config_value("ts_should_destructure_arg", 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
50 changes: 35 additions & 15 deletions sphinx_js/typedoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pathlib
import re
import subprocess
import typing
from collections import defaultdict
from collections.abc import Iterable, Iterator, Sequence
from errno import ENOENT
Expand Down Expand Up @@ -104,9 +105,21 @@ def parse(json: dict[str, Any]) -> "Project":


class Converter:
def __init__(self, base_dir: str):
base_dir: str
index: dict[int, "IndexType"]
_should_destructure_arg: typing.Callable[["Signature", "Param"], bool]

def __init__(
self,
base_dir: str,
should_destructure_arg: typing.Callable[["Signature", "Param"], bool]
| None = None,
):
self.base_dir: str = base_dir
self.index: dict[int, IndexType] = {}
if not should_destructure_arg:
should_destructure_arg = lambda sig, param: False
self._should_destructure_arg = should_destructure_arg

def populate_index(self, root: "IndexType") -> "Converter":
"""Create an ID-to-node mapping for all the TypeDoc output nodes.
Expand Down Expand Up @@ -232,14 +245,20 @@ def convert_all_nodes(self, root: "Project") -> list[ir.TopLevel]:


class Analyzer:
def __init__(self, json: "Project", base_dir: str):
def __init__(
self,
json: "Project",
base_dir: str,
should_destructure_arg: typing.Callable[["Signature", "Param"], bool]
| None = None,
):
"""
:arg json: The loaded JSON output from typedoc
:arg base_dir: The absolute path of the dir relative to which to
construct file-path segments of object paths
"""
converter = Converter(base_dir).populate_index(json)
converter = Converter(base_dir, should_destructure_arg).populate_index(json)
ir_objects = converter.convert_all_nodes(json)

self._base_dir = base_dir
Expand All @@ -253,7 +272,7 @@ def from_disk(
json = typedoc_output(
abs_source_paths, app.confdir, app.config.jsdoc_config_path, base_dir
)
return cls(json, base_dir)
return cls(json, base_dir, app.config.ts_should_destructure_arg)

def get_object(
self,
Expand Down Expand Up @@ -816,20 +835,21 @@ def _destructure_param(self, param: Param) -> list[Param]:
)
return result

def _destructure_params(self) -> list[Param]:
destructure_targets = []
for tag in self.comment.blockTags:
if tag.tag == "@destructure":
destructure_targets = tag.content[0].text.split(" ")
break

if not destructure_targets:
return self.parameters
def _destructure_params(self, converter: Converter) -> list[Param]:
destructure_targets: list[str] = []
for tag_content in self.comment.get_tag_list("destructure"):
tag = tag_content[0]
assert isinstance(tag, ir.DescriptionText)
destructure_targets.extend(tag.text.split(" "))

params = []
for p in self.parameters:
if p.name in destructure_targets:
if p.name in destructure_targets or converter._should_destructure_arg(
self, p
):
params.extend(self._destructure_param(p))
else:
params.append(p)
return params

def render(self, converter: Converter) -> Iterator[str | ir.TypeXRef]:
Expand Down Expand Up @@ -866,7 +886,7 @@ def to_ir(
# This isn't ideal, but otherwise the coloring is weird.
self.name = "[Symbol\u2024" + self.name[1:]
self._fix_type_suffix()
params = self._destructure_params()
params = self._destructure_params(converter)
# Would be nice if we could statically determine that the function was
# defined with `async` keyword but this is probably good enough
is_async = isinstance(self.type, ReferenceType) and self.type.name == "Promise"
Expand Down
9 changes: 9 additions & 0 deletions tests/test_typedoc_analysis/source/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,15 @@ export function destructureTest2({
*/
export function destructureTest3({ a, b }: { a: string; b: { c: string } }) {}


/**
* A test for should_destructure_arg
*/
export function destructureTest4(destructureThisPlease: {
/** The 'a' string. */
a: string;
}) {}

/**
* An example with a function as argument
*
Expand Down
4 changes: 4 additions & 0 deletions tests/test_typedoc_analysis/test_typedoc_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,10 @@ def test_destructured(self):
obj = self.analyzer.get_object(["destructureTest3"])
assert obj.params[0].name == "options"
assert join_type(obj.params[0].type) == "{ a: string; b: { c: string; }; }"
obj = self.analyzer.get_object(["destructureTest4"])
assert obj.params[0].name == "destructureThisPlease.a"
assert join_type(obj.params[0].type) == "string"
assert obj.params[0].description == [DescriptionText(text="The 'a' string.")]

def test_funcarg(self):
obj = self.analyzer.get_object(["funcArg"])
Expand Down
6 changes: 5 additions & 1 deletion tests/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ class TypeDocAnalyzerTestCase(TypeDocTestCase):
def setup_class(cls):
"""Run the TS analyzer over the TypeDoc output."""
super().setup_class()
cls.analyzer = TsAnalyzer(cls.json, cls._source_dir)

def should_destructure(sig, p):
return p.name == "destructureThisPlease"

cls.analyzer = TsAnalyzer(cls.json, cls._source_dir, should_destructure)


NO_MATCH = object()
Expand Down

0 comments on commit f4e83fe

Please sign in to comment.