Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/mrdocs/Support/Handlebars.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1313,6 +1313,18 @@ MRDOCS_DECL
bool
ne_fn(dom::Array const& args);

/** "gt" helper function

The "gt" helper returns true if the first argument compares
greater than the second, via @ref dom::Value's `operator<=>`.

@param args The two values to compare.
@return True if the first value is greater than the second.
*/
MRDOCS_DECL
bool
gt_fn(dom::Array const& args);

/** "not" helper function

The "not" helper returns true if not all of the values are truthy.
Expand Down
1 change: 1 addition & 0 deletions share/mrdocs/addons/generator/common/partials/symbol.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
{{> symbol/section/non-member-functions}}
{{> symbol/section/derived-classes}}
{{> symbol/section/introduced-symbols}}
{{> symbol/section/noexcept-specification}}
{{> symbol/section/exceptions}}
{{> symbol/section/return-value}}
{{> symbol/section/template-parameters}}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{! Exception specification: a dedicated section shown only when the noexcept
specifier is long enough that the signature elides it with the see-below
placeholder (see symbol/signature/function). Shorter specifiers are shown
inline and need no section here. The threshold is the noexcept-see-below-limit
option (the active generator's generator-options.<id>, exposed as
generatorConfig; default 50). symbol.noexcept is the whole "noexcept(...)"
string; its operand is that without the "noexcept(" prefix (9 chars) and
trailing ")". }}
{{#if symbol.noexcept}}
{{#if (gt (len symbol.noexcept) (or @root.generatorConfig.[noexcept-see-below-limit] 50))}}
{{#> markup/section name="noexcept-specification"}}
{{#> markup/dynamic-level-h }}Exception specification{{/markup/dynamic-level-h~}}
{{#> markup/p}}This function is potentially-throwing unless {{#>markup/code}}{{slice symbol.noexcept 9 (sub (len symbol.noexcept) 1)}}{{/markup/code}}.{{/markup/p}}
{{/markup/section}}
{{/if}}
{{/if}}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,14 @@
{{~#if isConst}} const{{/if~}}
{{#if isVolatile}} volatile{{/if~}}
{{#if refQualifier}} {{refQualifier}}{{/if~}}
{{#if noexcept}} {{noexcept}}{{/if~}}
{{~#if noexcept~}}
{{~! Specs whose full "noexcept(...)" string is longer than the
noexcept-see-below-limit option (the active generator's
generator-options.<id>, exposed as generatorConfig; default 50) are
rendered with the see-below placeholder plus a dedicated specification
section; shorter ones are shown inline. ~}}
{{~#if (gt (len noexcept) (or @root.generatorConfig.[noexcept-see-below-limit] 50))}} noexcept({{> symbol/signature/see-below}}){{else}} {{noexcept}}{{/if~}}
{{~/if~}}
{{#if (eq funcClass "normal")}}{{>type/declarator-suffix returnType}}{{/if~}}
{{#if requires}}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ str '/*' }} implementation-defined {{ str '*/' }}
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@
final;` is ill-formed (`final` can't be used on a non-defining
declaration). Similarly, don't show a semicolon if there are base
classes. --}}
{{~#isSeeBelow}} { /* see-below */ }{{/isSeeBelow~}}
{{~#isSeeBelow}} { {{> symbol/signature/see-below}} }{{/isSeeBelow~}}
{{#if (or isSeeBelow (and (not isFinal) (eq (len bases) 0)))}};{{/if}}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{ str '/*' }} see-below {{ str '*/' }}
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@
{{~#if cv-qualifiers}} {{cv-qualifiers}}{{/if~}}
{{! Refqualifiers as "&" or "&&" ~}}
{{#if (eq refQualifier "lvalue")}} &{{else if (eq refQualifier "rvalue")}} &&{{/if~}}
{{! Exception spec as literal string ~}}
{{#if exceptionSpec}} {{exceptionSpec}}{{/if~}}
{{! noexcept specification with "see-below" placeholder for long operands ~}}
{{~#if exceptionSpec~}}
{{~#if (gt (len exceptionSpec) (or @root.generatorConfig.[noexcept-see-below-limit] 50))}} noexcept({{> symbol/signature/see-below}}){{else}} {{exceptionSpec}}{{/if~}}
{{~/if~}}
{{! Declarator suffix of the return type ~}}
{{~>type/declarator-suffix returnType link-components-impl=link-components-impl~}}
{{/if}}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
--}}
{{#if id.isImplementationDefined~}}
{{! The name refers to a symbol that's implementation defined. These are special names that should not be linked. ~}}
{{ str '/* '}}implementation-defined{{ str ' */'~}}
{{~> symbol/signature/implementation-defined ~}}
{{else~}}
{{#if prefix~}}
{{> type/name-info prefix link-components-impl=link-components-impl~}}::
Expand Down
26 changes: 26 additions & 0 deletions src/lib/Gen/hbs/Builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,24 @@ namespace hbs {

namespace {

/** The active generator's `generator-options.<id>` settings.

Exposed to templates as `generatorConfig` so shared partials can read a
per-generator option directly as `@root.generatorConfig.<key>`, avoiding a
costly generator-options lookup by generator name in the template and
letting common partials reach generator configuration without hardcoding
the generator. Returns an empty object when the generator has no entry.
*/
dom::Value
generatorConfig(HandlebarsCorpus const& domCorpus)
{
auto const& genOpts = domCorpus->config->generatorOptions;
auto const it = genOpts.find(domCorpus.fileExtension);
return it != genOpts.end()
? dom::Value(it->second)
: dom::Value(dom::Object());
}

/** Loads Handlebars partial templates from a directory.

Recursively scans the specified directory for `.hbs` files and
Expand Down Expand Up @@ -540,6 +558,10 @@ createContext(Symbol const& I)
ctx.set("page", page);
ctx.set("symbol", domCorpus.get(I.id));
ctx.set("config", domCorpus->config.object());
// The active generator's id, so templates can index per-generator
// settings, e.g. lookup config.generator-options.<generatorId>.
ctx.set("generatorId", domCorpus.fileExtension);
ctx.set("generatorConfig", generatorConfig(domCorpus));
return ctx;
}

Expand Down Expand Up @@ -594,6 +616,10 @@ renderWrapped(
page.set("hasDefaultStyles", domCorpus.hasDefaultStyles);
ctx.set("page", page);
ctx.set("config", domCorpus->config.object());
// The active generator's id, so templates can index per-generator
// settings, e.g. lookup config.generator-options.<generatorId>.
ctx.set("generatorId", domCorpus.fileExtension);
ctx.set("generatorConfig", generatorConfig(domCorpus));
ctx.set("contents",
dom::makeInvocable([&](dom::Value const &) -> Expected<dom::Value> {
MRDOCS_TRY(contentsCb());
Expand Down
14 changes: 14 additions & 0 deletions src/lib/Support/Handlebars.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3828,6 +3828,7 @@ registerAntoraHelpers(Handlebars& hbs)
hbs.registerHelper("and", dom::makeVariadicInvocable(and_fn));
hbs.registerHelper("detag", dom::makeInvocable(detag_fn));
hbs.registerHelper("eq", dom::makeVariadicInvocable(eq_fn));
hbs.registerHelper("gt", dom::makeVariadicInvocable(gt_fn));
hbs.registerHelper("increment", dom::makeInvocable(increment_fn));
hbs.registerHelper("ne", dom::makeVariadicInvocable(ne_fn));
hbs.registerHelper("not", dom::makeVariadicInvocable(not_fn));
Expand All @@ -3840,6 +3841,7 @@ registerLogicalHelpers(Handlebars& hbs)
{
hbs.registerHelper("and", dom::makeVariadicInvocable(and_fn));
hbs.registerHelper("eq", dom::makeVariadicInvocable(eq_fn));
hbs.registerHelper("gt", dom::makeVariadicInvocable(gt_fn));
hbs.registerHelper("ne", dom::makeVariadicInvocable(ne_fn));
hbs.registerHelper("not", dom::makeVariadicInvocable(not_fn));
hbs.registerHelper("or", dom::makeVariadicInvocable(or_fn));
Expand Down Expand Up @@ -3902,6 +3904,18 @@ ne_fn(dom::Array const& args) {
return !eq_fn(args);
}

// Greater-than comparison via `operator<=>` on `dom::Value`.
bool
gt_fn(dom::Array const& args) {
// args carries trailing options, so we need at least two real
// arguments plus that one.
if (args.size() < 3)
{
return false;
}
return args.get(0) > args.get(1);
}

bool
not_fn(dom::Array const& args) {
std::size_t const n = args.size();
Expand Down
118 changes: 118 additions & 0 deletions src/test/lib/Metadata/NoexceptInfo.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com)
//
// Official repository: https://github.com/cppalliance/mrdocs
//

#include <mrdocs/Dom/String.hpp>
#include <mrdocs/Metadata/Specifiers/NoexceptInfo.hpp>
#include <test_suite/test_suite.hpp>

namespace mrdocs {
namespace {

// Helpers to build the specs in one line per test.

NoexceptInfo
makeNoexcept(
bool const implicit,
NoexceptKind const kind,
std::string operand = {})
{
NoexceptInfo info;
info.Implicit = implicit;
info.Kind = kind;
info.Operand = std::move(operand);
return info;
}

// ------------------------------------------------------------------

struct NoexceptInfoTest
{
// -- toString(NoexceptInfo) ----------------------------------

void test_noexcept_implicit_is_skipped()
{
NoexceptInfo const info = makeNoexcept(true, NoexceptKind::True);
BOOST_TEST(toString(info) == "");
}

void test_noexcept_implicit_shown_when_requested()
{
NoexceptInfo const info = makeNoexcept(true, NoexceptKind::True);
BOOST_TEST(toString(info, false, true) == "noexcept");
}

void test_noexcept_dependent_empty_operand()
{
NoexceptInfo const info = makeNoexcept(false, NoexceptKind::Dependent);
BOOST_TEST(toString(info) == "");
}

void test_noexcept_dependent_with_operand()
{
NoexceptInfo const info = makeNoexcept(
false, NoexceptKind::Dependent, "sizeof(T) > 4");
BOOST_TEST(toString(info) == "noexcept(sizeof(T) > 4)");
}

void test_noexcept_false_resolved_drops_operand()
{
NoexceptInfo const info = makeNoexcept(
false, NoexceptKind::False, "false");
BOOST_TEST(toString(info, true) == "");
}

void test_noexcept_false_with_operand()
{
NoexceptInfo const info = makeNoexcept(
false, NoexceptKind::False, "false");
BOOST_TEST(toString(info) == "noexcept(false)");
}

void test_noexcept_true_resolved_drops_operand()
{
NoexceptInfo const info = makeNoexcept(
false, NoexceptKind::True, "true");
BOOST_TEST(toString(info, true) == "noexcept");
}

void test_noexcept_true_empty_operand()
{
NoexceptInfo const info = makeNoexcept(false, NoexceptKind::True);
BOOST_TEST(toString(info) == "noexcept");
}

void test_noexcept_true_with_operand()
{
NoexceptInfo const info = makeNoexcept(
false, NoexceptKind::True, "true");
BOOST_TEST(toString(info) == "noexcept(true)");
}

// -- runner --------------------------------------------------

void run()
{
test_noexcept_implicit_is_skipped();
test_noexcept_implicit_shown_when_requested();
test_noexcept_dependent_empty_operand();
test_noexcept_dependent_with_operand();
test_noexcept_false_resolved_drops_operand();
test_noexcept_false_with_operand();
test_noexcept_true_resolved_drops_operand();
test_noexcept_true_empty_operand();
test_noexcept_true_with_operand();
}
};

} // (unnamed)

TEST_SUITE(NoexceptInfoTest, "clang.mrdocs.Metadata.NoexceptInfo");

} // mrdocs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
generator: [adoc, html]
multipage: false
no-default-styles: true
warn-if-undocumented: false
source-root: .
# noexcept-see-below-limit set for each generator to a small value, so the
# specifier (longer than 20, but shorter than the default 50) is elided to
# noexcept(/* see-below */) with its own section. Both generators set it, so
# the elision is driven by the option and its rendering is checked in adoc and
# html. The inline case (specifier at or below the limit) is the default and is
# already exercised throughout the suite, so it is not repeated here.
generator-options:
adoc:
noexcept-see-below-limit: 20
html:
noexcept-see-below-limit: 20
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
= Reference
:mrdocs:

[#index]
== Global namespace

=== Functions

[cols="1,4"]
|===
| Name| Description
| link:#swap[`swap`]
| Swaps two values, conditionally noexcept&period;
|===


[#swap]
== swap

Swaps two values, conditionally noexcept&period;

=== Synopsis

Declared in `&lt;noexcept&hyphen;limit&period;cpp&gt;`

[source,cpp,subs="verbatim,replacements,macros,-callouts"]
----
template&lt;class T&gt;
void
swap(
T& a,
T& b) noexcept(&sol;&ast; see-below &ast;&sol;);
----

=== Exception specification

This function is potentially-throwing unless `sizeof(T) &gt; sizeof(int)`.

=== Parameters

[cols="1,4"]
|===
| Name| Description
| *a*
| first value
| *b*
| second value
|===


[.small]#Created with https://www.mrdocs.com[MrDocs]#
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Swaps two values, conditionally noexcept.

@param a first value
@param b second value
*/
template <class T>
void swap(T& a, T& b) noexcept(sizeof(T) > sizeof(int));
Loading
Loading