diff --git a/sphinx_js/ir.py b/sphinx_js/ir.py index 47a81aff..bd4ac126 100644 --- a/sphinx_js/ir.py +++ b/sphinx_js/ir.py @@ -248,6 +248,7 @@ class TopLevel: line: int | None #: Explanation of the deprecation (which implies True) or True or False deprecated: Description | bool + experimental: Description | bool = field(kw_only=True, default=False) #: List of preformatted textual examples examples: Sequence[Description] #: List of paths to also refer the reader to diff --git a/sphinx_js/js/call_typedoc.ts b/sphinx_js/js/call_typedoc.ts index d87bacdb..4f0c8ea1 100644 --- a/sphinx_js/js/call_typedoc.ts +++ b/sphinx_js/js/call_typedoc.ts @@ -56,7 +56,15 @@ async function main() { return ExitCodes.Ok; } app.extraData = {}; - app.options.getValue("modifierTags").push("@hidetype"); + const modifierTags = app.options.getValue("modifierTags"); + modifierTags.push("@hidetype"); + // We want to use @experimental as a block tag so take it out of modifierTags + // and stick it in blockTags + const experimentalIndex = modifierTags.indexOf("@experimental"); + if (experimentalIndex !== -1) { + modifierTags.splice(experimentalIndex, 1); + } + app.options.getValue("blockTags").push("@experimental"); const userConfigPath = app.options.getValue("sphinxJsConfig"); const config = await loadConfig(userConfigPath); app.logger.info(`Loaded user config from ${userConfigPath}`); diff --git a/sphinx_js/js/convertTopLevel.ts b/sphinx_js/js/convertTopLevel.ts index 28f8e84c..3fe6ce4c 100644 --- a/sphinx_js/js/convertTopLevel.ts +++ b/sphinx_js/js/convertTopLevel.ts @@ -673,6 +673,11 @@ export class Converter { if (deprecated && deprecated.length === 0) { deprecated = true; } + let experimental: Description | boolean = + block_tags["experimental"]?.[0] || false; + if (experimental && experimental.length === 0) { + experimental = true; + } return { name: refl.name, path, @@ -682,6 +687,7 @@ export class Converter { modifier_tags: Array.from(refl.comment?.modifierTags || []), block_tags, deprecated, + experimental, examples: block_tags["example"] || [], properties: [], see_alsos: [], diff --git a/sphinx_js/js/ir.ts b/sphinx_js/js/ir.ts index d726e95f..003af099 100644 --- a/sphinx_js/js/ir.ts +++ b/sphinx_js/js/ir.ts @@ -103,6 +103,7 @@ export type TopLevel = { block_tags: { [key: string]: Description[] }; line: number | null; deprecated: Description | boolean; + experimental: Description | boolean; examples: Description[]; see_alsos: string[]; properties: Attribute[]; diff --git a/sphinx_js/renderers.py b/sphinx_js/renderers.py index e8de493d..734a4b88 100644 --- a/sphinx_js/renderers.py +++ b/sphinx_js/renderers.py @@ -556,6 +556,9 @@ def _template_vars(self, name: str, obj: Function) -> dict[str, Any]: # type: i deprecated = obj.deprecated if not isinstance(deprecated, bool): deprecated = render_description(deprecated) + experimental = obj.experimental + if not isinstance(experimental, bool): + experimental = render_description(experimental) return dict( name=name, type_params=self._type_params(obj), @@ -564,6 +567,7 @@ def _template_vars(self, name: str, obj: Function) -> dict[str, Any]: # type: i description=render_description(obj.description), examples=[render_description(x) for x in obj.examples], deprecated=deprecated, + experimental=experimental, is_optional=obj.is_optional, is_static=obj.is_static, is_async=obj.is_async, @@ -592,6 +596,7 @@ def _template_vars(self, name: str, obj: Class | Interface) -> dict[str, Any]: description="", line=0, deprecated=False, + experimental=False, examples=[], see_alsos=[], properties=[], @@ -615,6 +620,7 @@ def _template_vars(self, name: str, obj: Class | Interface) -> dict[str, Any]: fields=self._fields(constructor), examples=[render_description(ex) for ex in constructor.examples], deprecated=constructor.deprecated, + experimental=constructor.experimental, see_also=constructor.see_alsos, exported_from=obj.exported_from, class_comment=render_description(obj.description), @@ -672,6 +678,12 @@ def _template_vars(self, name: str, obj: Attribute | TypeAlias) -> dict[str, Any if obj.readonly: ty = "readonly " + ty type_params = "" + deprecated = obj.deprecated + if not isinstance(deprecated, bool): + deprecated = render_description(deprecated) + experimental = obj.experimental + if not isinstance(experimental, bool): + experimental = render_description(experimental) is_type_alias = isinstance(obj, TypeAlias) fields: Iterator[tuple[list[str], str]] = iter([]) if isinstance(obj, TypeAlias): @@ -683,7 +695,8 @@ def _template_vars(self, name: str, obj: Attribute | TypeAlias) -> dict[str, Any type_params=type_params, fields=fields, description=render_description(obj.description), - deprecated=obj.deprecated, + deprecated=deprecated, + experimental=experimental, is_optional=is_optional, see_also=obj.see_alsos, examples=[render_description(ex) for ex in obj.examples], diff --git a/sphinx_js/templates/attribute.rst b/sphinx_js/templates/attribute.rst index 6408fa85..4cd2683f 100644 --- a/sphinx_js/templates/attribute.rst +++ b/sphinx_js/templates/attribute.rst @@ -8,6 +8,7 @@ {{ common.deprecated(deprecated)|indent(3) }} + {{ common.experimental(experimental)|indent(3) }} {% if type -%} .. rst-class:: js attribute type diff --git a/sphinx_js/templates/class.rst b/sphinx_js/templates/class.rst index f1165de5..a9ba8fdd 100644 --- a/sphinx_js/templates/class.rst +++ b/sphinx_js/templates/class.rst @@ -7,6 +7,7 @@ {%- endif %} {{ common.deprecated(deprecated)|indent(3) }} + {{ common.experimental(experimental)|indent(3) }} {% if class_comment -%} {{ class_comment|indent(3) }} diff --git a/sphinx_js/templates/common.rst b/sphinx_js/templates/common.rst index e1c47b55..6d0d9c42 100644 --- a/sphinx_js/templates/common.rst +++ b/sphinx_js/templates/common.rst @@ -1,9 +1,18 @@ {% macro deprecated(message) %} {% if message -%} -.. note:: +.. admonition:: Deprecated + :class: warning - Deprecated - {%- if message is string -%}: {{ message }}{% else %}.{% endif -%} + {% if message is string -%} {{ message }} {%- else -%} .. Not empty {%- endif -%} +{%- endif %} +{% endmacro %} + +{% macro experimental(message) %} +{% if message -%} +.. admonition:: Experimental + :class: warning + + {% if message is string -%} {{ message }} {%- else -%} .. Not empty {%- endif -%} {%- endif %} {% endmacro %} diff --git a/sphinx_js/templates/function.rst b/sphinx_js/templates/function.rst index 2930a9db..f3fbeade 100644 --- a/sphinx_js/templates/function.rst +++ b/sphinx_js/templates/function.rst @@ -9,6 +9,7 @@ {% endif %} {{ common.deprecated(deprecated)|indent(3) }} + {{ common.experimental(experimental)|indent(3) }} {% if description -%} {{ description|indent(3) }} diff --git a/tests/test_build_js/test_build_js.py b/tests/test_build_js/test_build_js.py index 537f240d..3c85f789 100644 --- a/tests/test_build_js/test_build_js.py +++ b/tests/test_build_js/test_build_js.py @@ -109,11 +109,9 @@ def test_autofunction_deprecated(self): self._file_contents_eq( "autofunction_deprecated", "deprecatedFunction()\n\n" - " Note:\n\n" - " Deprecated.\n\n" + " Deprecated:\n\n" "deprecatedExplanatoryFunction()\n\n" - " Note:\n\n" - " Deprecated: don't use anymore\n", + " Deprecated: don't use anymore\n", ) def test_autofunction_see(self): @@ -278,11 +276,9 @@ def test_autoclass_deprecated(self): self._file_contents_eq( "autoclass_deprecated", "class DeprecatedClass()\n\n" - " Note:\n\n" - " Deprecated.\n\n" + " Deprecated:\n\n" "class DeprecatedExplanatoryClass()\n\n" - " Note:\n\n" - " Deprecated: don't use anymore\n", + " Deprecated: don't use anymore\n", ) def test_autoclass_see(self): @@ -316,11 +312,9 @@ def test_autoattribute_deprecated(self): self._file_contents_eq( "autoattribute_deprecated", "DeprecatedAttribute\n\n" - " Note:\n\n" - " Deprecated.\n\n" + " Deprecated:\n\n" "DeprecatedExplanatoryAttribute\n\n" - " Note:\n\n" - " Deprecated: don't use anymore\n", + " Deprecated: don't use anymore\n", ) def test_autoattribute_see(self): diff --git a/tests/test_build_ts/source/class.ts b/tests/test_build_ts/source/class.ts index f94613cb..3971b52a 100644 --- a/tests/test_build_ts/source/class.ts +++ b/tests/test_build_ts/source/class.ts @@ -74,6 +74,16 @@ export function deprecatedFunction1() {} */ export function deprecatedFunction2() {} +/** + * @experimental Not ready yet. + */ +export function experimentalFunction1() {} + +/** + * @experimental + */ +export function experimentalFunction2() {} + /** * @example This is an example. * @example This is another example. diff --git a/tests/test_build_ts/test_build_ts.py b/tests/test_build_ts/test_build_ts.py index f46f962d..ab97f22e 100644 --- a/tests/test_build_ts/test_build_ts.py +++ b/tests/test_build_ts/test_build_ts.py @@ -111,15 +111,27 @@ def test_deprecated(self): """\ deprecatedFunction1() - Note: - - Deprecated: since v20! + Deprecated: since v20! deprecatedFunction2() - Note: + Deprecated: + """ + ), + ) + + def test_experimental(self): + self._file_contents_eq( + "experimental", + dedent( + """\ + experimentalFunction1() + + Experimental: Not ready yet. + + experimentalFunction2() - Deprecated. + Experimental: """ ), ) diff --git a/tests/test_renderers.py b/tests/test_renderers.py index e9fe7830..c6b5e167 100644 --- a/tests/test_renderers.py +++ b/tests/test_renderers.py @@ -426,16 +426,18 @@ def test_func_render_param_exceptions(function_render): def test_func_render_callouts(function_render): assert function_render(deprecated=True) == DEFAULT_RESULT + setindent( """ - .. note:: + .. admonition:: Deprecated + :class: warning - Deprecated. + .. Not empty """, ) assert function_render(deprecated="v0.24") == DEFAULT_RESULT + setindent( """ - .. note:: + .. admonition:: Deprecated + :class: warning - Deprecated: v0.24 + v0.24 """, ) assert function_render(see_alsos=["see", "this too"]) == DEFAULT_RESULT + setindent( @@ -460,9 +462,10 @@ def test_all(function_render): """\ .. js:function:: blah(a) - .. note:: + .. admonition:: Deprecated + :class: warning - Deprecated. + .. Not empty description @@ -562,3 +565,69 @@ def test_type_alias(type_alias_render): :typeparam T: ABC (extends **number**) """ ) + + +def admonition_test_content(first_line, admonition, extra=""): + return ( + dedent( + f"""\ + {first_line} + + .. admonition:: {admonition} + :class: warning + + {extra} + """ + ).strip() + + "\n" + ) + + +@pytest.mark.parametrize( + "deprecated,expected", + [ + (True, ".. Not empty"), + ("Blah", "Blah"), + ( + [DescriptionText("This is "), DescriptionCode("`some code`")], + "This is ``some code``", + ), + ], +) +def test_deprecated( + function_render, attribute_render, type_alias_render, deprecated, expected +): + assert function_render(deprecated=deprecated) == admonition_test_content( + ".. js:function:: blah()", "Deprecated", expected + ) + assert attribute_render(deprecated=deprecated) == admonition_test_content( + ".. js:attribute:: blah", "Deprecated", expected + ) + assert type_alias_render(deprecated=deprecated) == admonition_test_content( + ".. js:typealias:: blah", "Deprecated", expected + ) + + +@pytest.mark.parametrize( + "experimental,expected", + [ + (True, ".. Not empty"), + ("Blah", "Blah"), + ( + [DescriptionText("This is "), DescriptionCode("`some code`")], + "This is ``some code``", + ), + ], +) +def test_experimental( + function_render, attribute_render, type_alias_render, experimental, expected +): + assert function_render(experimental=experimental) == admonition_test_content( + ".. js:function:: blah()", "Experimental", expected + ) + assert attribute_render(experimental=experimental) == admonition_test_content( + ".. js:attribute:: blah", "Experimental", expected + ) + assert type_alias_render(experimental=experimental) == admonition_test_content( + ".. js:typealias:: blah", "Experimental", expected + )