Skip to content

Commit d4b4a4f

Browse files
committed
feat: render long noexcept specifications as noexcept(see-below)
MrDocs used to render the full `noexcept` operand inline, so a declaration like void swap(reference, reference) noexcept( std::is_nothrow_move_constructible<value_t>::value && std::is_nothrow_move_assignable<value_t>::value && std::is_nothrow_move_constructible<json_value>::value && std::is_nothrow_move_assignable<json_value>::value); buried the `noexcept` condition in a mostly-unreadable slop. This replaces operands longer than 40 characters with an italic "see-below" placeholder in the declaration, and moves the actual condition to a dedicated "noexcept Specification" section of the exposition: void swap(reference, reference) noexcept(see-below); === noexcept Specification noexcept when `...long condition...`. The section is intentionally separate from the existing "Exceptions" section, which continues to cover `@throws` documentation. Closes issue #1103.
1 parent ab5f412 commit d4b4a4f

11 files changed

Lines changed: 883 additions & 3 deletions

File tree

include/mrdocs/Support/Handlebars.hpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1300,6 +1300,18 @@ MRDOCS_DECL
13001300
bool
13011301
ne_fn(dom::Array const& args);
13021302

1303+
/** "gt" helper function
1304+
1305+
The "gt" helper returns true if the first argument compares
1306+
greater than the second, via @ref dom::Value's `operator<=>`.
1307+
1308+
@param args The two values to compare.
1309+
@return True if the first value is greater than the second.
1310+
*/
1311+
MRDOCS_DECL
1312+
bool
1313+
gt_fn(dom::Array const& args);
1314+
13031315
/** "not" helper function
13041316
13051317
The "not" helper returns true if not all of the values are truthy.

share/mrdocs/addons/generator/adoc/partials/symbol.adoc.hbs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,16 @@
167167
| {{> symbol/qualified-name . }}
168168
{{/each}}
169169
|===
170+
{{/if}}
171+
{{/if}}
172+
{{! noexcept specification - only when the operand is long enough to
173+
be rendered as noexcept(see-below) in the signature. Short
174+
operands are shown inline and need no separate section. }}
175+
{{#if symbol.noexcept}}
176+
{{#if (gt (len symbol.noexcept) 50)}}
177+
{{#> markup/dynamic-level-h }}`noexcept` Specification{{/markup/dynamic-level-h~}}
178+
`noexcept` when `{{slice symbol.noexcept 9 (sub (len symbol.noexcept) 1)}}`.
179+
170180
{{/if}}
171181
{{/if}}
172182
{{! Exceptions }}

share/mrdocs/addons/generator/common/partials/symbol/signature/function.hbs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,13 @@
3131
{{~#if isConst}} const{{/if~}}
3232
{{#if isVolatile}} volatile{{/if~}}
3333
{{#if refQualifier}} {{refQualifier}}{{/if~}}
34-
{{#if noexcept}} {{noexcept}}{{/if~}}
34+
{{~#if noexcept~}}
35+
{{~! Specs with operands longer than 40 characters (full string > 50,
36+
accounting for the "noexcept(" and ")" wrappers) are rendered as
37+
`noexcept(see-below)` plus a dedicated specification section;
38+
shorter ones are shown inline. ~}}
39+
{{~#if (gt (len noexcept) 50)}} noexcept({{#>markup/em}}see-below{{/markup/em}}){{else}} {{noexcept}}{{/if~}}
40+
{{~/if~}}
3541
{{#if (eq funcClass "normal")}}{{>type/declarator-suffix returnType}}{{/if~}}
3642
{{#if requires}}
3743

share/mrdocs/addons/generator/common/partials/type/declarator-suffix.hbs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
{{~#if cv-qualifiers}} {{cv-qualifiers}}{{/if~}}
3838
{{! Refqualifiers as "&" or "&&" ~}}
3939
{{#if (eq refQualifier "lvalue")}} &{{else if (eq refQualifier "rvalue")}} &&{{/if~}}
40-
{{! Exception spec as literal string ~}}
41-
{{#if exceptionSpec}} {{exceptionSpec}}{{/if~}}
40+
{{! noexcept specification with "see-below" placeholder for long operands ~}}
41+
{{~#if exceptionSpec~}}
42+
{{~#if (gt (len exceptionSpec) 50)}} noexcept({{#>markup/em}}see-below{{/markup/em}}){{else}} {{exceptionSpec}}{{/if~}}
43+
{{~/if~}}
4244
{{! Declarator suffix of the return type ~}}
4345
{{~>type/declarator-suffix returnType link-components-impl=link-components-impl~}}
4446
{{/if}}

share/mrdocs/addons/generator/html/partials/symbol.html.hbs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,17 @@
231231
{{/if}}
232232
</div>
233233

234+
{{/if}}
235+
{{! noexcept specification - only when the operand is long enough to
236+
be rendered as noexcept(see-below) in the signature. Short
237+
operands are shown inline and need no separate section. }}
238+
{{#if symbol.noexcept}}
239+
{{#if (gt (len symbol.noexcept) 50)}}
240+
<div>
241+
{{#> markup/dynamic-level-h level=2 }}<code>noexcept</code> Specification{{/markup/dynamic-level-h~}}
242+
<p><code>noexcept</code> when <code>{{slice symbol.noexcept 9 (sub (len symbol.noexcept) 1)}}</code>.</p>
243+
</div>
244+
{{/if}}
234245
{{/if}}
235246
{{! Exceptions }}
236247
{{#if symbol.doc.exceptions}}

src/lib/Support/Handlebars.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3835,6 +3835,7 @@ registerAntoraHelpers(Handlebars& hbs)
38353835
hbs.registerHelper("and", dom::makeVariadicInvocable(and_fn));
38363836
hbs.registerHelper("detag", dom::makeInvocable(detag_fn));
38373837
hbs.registerHelper("eq", dom::makeVariadicInvocable(eq_fn));
3838+
hbs.registerHelper("gt", dom::makeVariadicInvocable(gt_fn));
38383839
hbs.registerHelper("increment", dom::makeInvocable(increment_fn));
38393840
hbs.registerHelper("ne", dom::makeVariadicInvocable(ne_fn));
38403841
hbs.registerHelper("not", dom::makeVariadicInvocable(not_fn));
@@ -3847,6 +3848,7 @@ registerLogicalHelpers(Handlebars& hbs)
38473848
{
38483849
hbs.registerHelper("and", dom::makeVariadicInvocable(and_fn));
38493850
hbs.registerHelper("eq", dom::makeVariadicInvocable(eq_fn));
3851+
hbs.registerHelper("gt", dom::makeVariadicInvocable(gt_fn));
38503852
hbs.registerHelper("ne", dom::makeVariadicInvocable(ne_fn));
38513853
hbs.registerHelper("not", dom::makeVariadicInvocable(not_fn));
38523854
hbs.registerHelper("or", dom::makeVariadicInvocable(or_fn));
@@ -3909,6 +3911,18 @@ ne_fn(dom::Array const& args) {
39093911
return !eq_fn(args);
39103912
}
39113913

3914+
// Greater-than comparison via `operator<=>` on `dom::Value`.
3915+
bool
3916+
gt_fn(dom::Array const& args) {
3917+
// args carries trailing options, so we need at least two real
3918+
// arguments plus that one.
3919+
if (args.size() < 3)
3920+
{
3921+
return false;
3922+
}
3923+
return args.get(0) > args.get(1);
3924+
}
3925+
39123926
bool
39133927
not_fn(dom::Array const& args) {
39143928
std::size_t const n = args.size();
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//
2+
// Licensed under the Apache License v2.0 with LLVM Exceptions.
3+
// See https://llvm.org/LICENSE.txt for license information.
4+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
5+
//
6+
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
7+
//
8+
// Official repository: https://github.com/cppalliance/mrdocs
9+
//
10+
11+
#include <mrdocs/Dom/String.hpp>
12+
#include <mrdocs/Metadata/Specifiers/NoexceptInfo.hpp>
13+
#include <test_suite/test_suite.hpp>
14+
15+
namespace mrdocs {
16+
namespace {
17+
18+
// Helpers to build the specs in one line per test.
19+
20+
NoexceptInfo
21+
makeNoexcept(
22+
bool const implicit,
23+
NoexceptKind const kind,
24+
std::string operand = {})
25+
{
26+
NoexceptInfo info;
27+
info.Implicit = implicit;
28+
info.Kind = kind;
29+
info.Operand = std::move(operand);
30+
return info;
31+
}
32+
33+
// ------------------------------------------------------------------
34+
35+
struct NoexceptInfoTest
36+
{
37+
// -- toString(NoexceptInfo) ----------------------------------
38+
39+
void test_noexcept_implicit_is_skipped()
40+
{
41+
NoexceptInfo const info = makeNoexcept(true, NoexceptKind::True);
42+
BOOST_TEST(toString(info).get() == "");
43+
}
44+
45+
void test_noexcept_implicit_shown_when_requested()
46+
{
47+
NoexceptInfo const info = makeNoexcept(true, NoexceptKind::True);
48+
BOOST_TEST(toString(info, false, true).get() == "noexcept");
49+
}
50+
51+
void test_noexcept_dependent_empty_operand()
52+
{
53+
NoexceptInfo const info = makeNoexcept(false, NoexceptKind::Dependent);
54+
BOOST_TEST(toString(info).get() == "");
55+
}
56+
57+
void test_noexcept_dependent_with_operand()
58+
{
59+
NoexceptInfo const info = makeNoexcept(
60+
false, NoexceptKind::Dependent, "sizeof(T) > 4");
61+
BOOST_TEST(toString(info).get() == "noexcept(sizeof(T) > 4)");
62+
}
63+
64+
void test_noexcept_false_resolved_drops_operand()
65+
{
66+
NoexceptInfo const info = makeNoexcept(
67+
false, NoexceptKind::False, "false");
68+
BOOST_TEST(toString(info, true).get() == "");
69+
}
70+
71+
void test_noexcept_false_with_operand()
72+
{
73+
NoexceptInfo const info = makeNoexcept(
74+
false, NoexceptKind::False, "false");
75+
BOOST_TEST(toString(info).get() == "noexcept(false)");
76+
}
77+
78+
void test_noexcept_true_resolved_drops_operand()
79+
{
80+
NoexceptInfo const info = makeNoexcept(
81+
false, NoexceptKind::True, "true");
82+
BOOST_TEST(toString(info, true).get() == "noexcept");
83+
}
84+
85+
void test_noexcept_true_empty_operand()
86+
{
87+
NoexceptInfo const info = makeNoexcept(false, NoexceptKind::True);
88+
BOOST_TEST(toString(info).get() == "noexcept");
89+
}
90+
91+
void test_noexcept_true_with_operand()
92+
{
93+
NoexceptInfo const info = makeNoexcept(
94+
false, NoexceptKind::True, "true");
95+
BOOST_TEST(toString(info).get() == "noexcept(true)");
96+
}
97+
98+
// -- runner --------------------------------------------------
99+
100+
void run()
101+
{
102+
test_noexcept_implicit_is_skipped();
103+
test_noexcept_implicit_shown_when_requested();
104+
test_noexcept_dependent_empty_operand();
105+
test_noexcept_dependent_with_operand();
106+
test_noexcept_false_resolved_drops_operand();
107+
test_noexcept_false_with_operand();
108+
test_noexcept_true_resolved_drops_operand();
109+
test_noexcept_true_empty_operand();
110+
test_noexcept_true_with_operand();
111+
}
112+
};
113+
114+
} // (unnamed)
115+
116+
TEST_SUITE(NoexceptInfoTest, "clang.mrdocs.Metadata.NoexceptInfo");
117+
118+
} // mrdocs
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
= Reference
2+
:mrdocs:
3+
4+
[#index]
5+
== Global namespace
6+
7+
=== Functions
8+
9+
[cols=1]
10+
|===
11+
| Name
12+
| link:#f1[`f1`]
13+
| link:#f2[`f2`]
14+
| link:#f3[`f3`]
15+
| link:#f4[`f4`]
16+
| link:#f5[`f5`]
17+
| link:#f6[`f6`]
18+
|===
19+
20+
=== Variables
21+
22+
[cols=1]
23+
|===
24+
| Name
25+
| link:#is_nothrow_move_assignable_v[`is&lowbar;nothrow&lowbar;move&lowbar;assignable&lowbar;v`]
26+
| link:#is_nothrow_move_constructible_v[`is&lowbar;nothrow&lowbar;move&lowbar;constructible&lowbar;v`]
27+
| link:#is_nothrow_swappable_v[`is&lowbar;nothrow&lowbar;swappable&lowbar;v`]
28+
|===
29+
30+
[#f1]
31+
== f1
32+
33+
=== Synopsis
34+
35+
Declared in `&lt;noexcept&period;cpp&gt;`
36+
37+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
38+
----
39+
void
40+
f1() noexcept;
41+
----
42+
43+
[#f2]
44+
== f2
45+
46+
=== Synopsis
47+
48+
Declared in `&lt;noexcept&period;cpp&gt;`
49+
50+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
51+
----
52+
void
53+
f2() noexcept(true);
54+
----
55+
56+
[#f3]
57+
== f3
58+
59+
=== Synopsis
60+
61+
Declared in `&lt;noexcept&period;cpp&gt;`
62+
63+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
64+
----
65+
void
66+
f3() noexcept(false);
67+
----
68+
69+
[#f4]
70+
== f4
71+
72+
=== Synopsis
73+
74+
Declared in `&lt;noexcept&period;cpp&gt;`
75+
76+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
77+
----
78+
template&lt;typename T&gt;
79+
void
80+
f4(T&) noexcept(is&lowbar;nothrow&lowbar;swappable&lowbar;v&lt;T&gt;);
81+
----
82+
83+
[#f5]
84+
== f5
85+
86+
=== Synopsis
87+
88+
Declared in `&lt;noexcept&period;cpp&gt;`
89+
90+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
91+
----
92+
template&lt;typename T&gt;
93+
void
94+
f5(
95+
T a,
96+
T b) noexcept(noexcept(a &plus; b));
97+
----
98+
99+
[#f6]
100+
== f6
101+
102+
=== Synopsis
103+
104+
Declared in `&lt;noexcept&period;cpp&gt;`
105+
106+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
107+
----
108+
template&lt;typename T&gt;
109+
void
110+
f6(
111+
T&,
112+
T&) noexcept(_see-below_);
113+
----
114+
115+
=== `noexcept` Specification
116+
117+
`noexcept` when `is&lowbar;nothrow&lowbar;move&lowbar;constructible&lowbar;v&lt;T&gt; &amp;&amp; is&lowbar;nothrow&lowbar;move&lowbar;assignable&lowbar;v&lt;T&gt;`.
118+
119+
[#is_nothrow_move_assignable_v]
120+
== is&lowbar;nothrow&lowbar;move&lowbar;assignable&lowbar;v
121+
122+
=== Synopsis
123+
124+
Declared in `&lt;noexcept&period;cpp&gt;`
125+
126+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
127+
----
128+
template&lt;typename T&gt;
129+
inline constexpr bool is&lowbar;nothrow&lowbar;move&lowbar;assignable&lowbar;v = false;
130+
----
131+
132+
[#is_nothrow_move_constructible_v]
133+
== is&lowbar;nothrow&lowbar;move&lowbar;constructible&lowbar;v
134+
135+
=== Synopsis
136+
137+
Declared in `&lt;noexcept&period;cpp&gt;`
138+
139+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
140+
----
141+
template&lt;typename T&gt;
142+
inline constexpr bool is&lowbar;nothrow&lowbar;move&lowbar;constructible&lowbar;v = false;
143+
----
144+
145+
[#is_nothrow_swappable_v]
146+
== is&lowbar;nothrow&lowbar;swappable&lowbar;v
147+
148+
=== Synopsis
149+
150+
Declared in `&lt;noexcept&period;cpp&gt;`
151+
152+
[source,cpp,subs="verbatim,replacements,macros,-callouts"]
153+
----
154+
template&lt;typename T&gt;
155+
inline constexpr bool is&lowbar;nothrow&lowbar;swappable&lowbar;v = false;
156+
----
157+
158+
159+
[.small]#Created with https://www.mrdocs.com[MrDocs]#

0 commit comments

Comments
 (0)