diff --git a/docs/modules/ROOT/pages/configuration/filters.adoc b/docs/modules/ROOT/pages/configuration/filters.adoc index 9e8590c0a7..88a0913121 100644 --- a/docs/modules/ROOT/pages/configuration/filters.adoc +++ b/docs/modules/ROOT/pages/configuration/filters.adoc @@ -226,6 +226,84 @@ include::example$snippets/options/extract-private/extract-private.yml[tags=!test include::example$snippets/options/extract-private/extract-private.adoc[tags=!footer] ======== +== Macros + +MrDocs also documents macros. + +.Example +[source,cpp] +---- +include::example$snippets/options/extract-all-macros/extract-all-macros.cpp[] +---- + +[.adoc-preview] +======== +include::example$snippets/options/extract-all-macros/extract-all-macros.adoc[tags=!footer] +======== + +By default, only documented macros are included in the documentation. Here the public `MY_LIB_VERSION_STRING` is kept, while the intermediary `MY_LIB_DETAIL_STRINGIZE` helper, which carries an ordinary comment rather than a documentation comment, is dropped. This is because the xref:configuration/reference.adoc#extract-all-macros_option[`extract-all-macros`] option defaults to `false`. Set it to `true` to extract every macro that passes the filters, whether it is documented or not. + +Macros follow different rules from other symbols, so they get their own name filters, which otherwise work like their symbol counterparts: xref:configuration/reference.adoc#include-macros_option[`include-macros`] and xref:configuration/reference.adoc#exclude-macros_option[`exclude-macros`]. `include-macros` narrows the extracted macros by name: + +.Example +[source,cpp] +---- +include::example$snippets/options/include-macros/include-macros.cpp[] +---- + +.`mrdocs.yml` +[source,yaml] +---- +include::example$snippets/options/include-macros/include-macros.yml[tags=!test-only] +---- + +[.adoc-preview] +======== +include::example$snippets/options/include-macros/include-macros.adoc[tags=!footer] +======== + +`MY_LIB_VERSION` is kept; `OTHER_HELPER` falls outside the `MY_LIB_*` allowlist. + +=== Excluding macros + +`exclude-macros` drops macros by name, which suits the helpers a library defines only as an implementation detail: + +.Example +[source,cpp] +---- +include::example$snippets/options/exclude-macros/exclude-macros.cpp[] +---- + +.`mrdocs.yml` +[source,yaml] +---- +include::example$snippets/options/exclude-macros/exclude-macros.yml[tags=!test-only] +---- + +[.adoc-preview] +======== +include::example$snippets/options/exclude-macros/exclude-macros.adoc[tags=!footer] +======== + +`MY_LIB_ASSERT` is kept; the `MY_LIB_DETAIL_*` and `+*_IMPL+` helpers are dropped. + +NOTE: Macros have only include and exclude filters, with no xref:configuration/reference.adoc#implementation-defined_option[`implementation-defined`] or xref:configuration/reference.adoc#see-below_option[`see-below`] equivalents. Those modes keep a symbol in the corpus while hiding its scope or eliding its synopsis body, and neither fits a macro: a macro has no members to hide, and since the preprocessor replaces macros before the other symbols are extracted, a macro never appears in another symbol's synopsis. A macro is therefore only ever included or excluded. + +=== Feature testing macros + +A feature-test or configuration macro is often not defined in the build MrDocs sees, so the preprocessor never reports it. MrDocs always defines `+__MRDOCS__+`, so guard a definition with it to document the macro anyway: + +.Example +[source,cpp] +---- +include::example$snippets/options/macros-guard/macros-guard.cpp[] +---- + +[.adoc-preview] +======== +include::example$snippets/options/macros-guard/macros-guard.adoc[tags=!footer] +======== + == Glob syntax Symbol and file patterns share the same wildcards (the file form uses `/` as the path delimiter; the symbol form uses `::`): diff --git a/docs/mrdocs.schema.json b/docs/mrdocs.schema.json index d96dac10f8..27e2080fa4 100644 --- a/docs/mrdocs.schema.json +++ b/docs/mrdocs.schema.json @@ -135,6 +135,15 @@ "title": "Input directories to exclude", "type": "array" }, + "exclude-macros": { + "default": [], + "description": "A macro whose name matches one of these patterns, or one of `exclude-symbols`, is not extracted even if it would otherwise be included. See `include-symbols` for the pattern syntax.", + "items": { + "type": "string" + }, + "title": "Macro patterns to exclude", + "type": "array" + }, "exclude-patterns": { "default": [], "description": "Files matching these glob patterns are skipped, even when they sit inside an included directory. The patterns are relative to the configuration file. A single `*` matches everything in a directory; `**` matches everything in a directory and its subdirectories.", @@ -163,6 +172,16 @@ "title": "Extract all symbols", "type": "boolean" }, + "extract-all-macros": { + "default": false, + "description": "Like `extract-all`, but for preprocessor macros. When `false` (the default), MrDocs extracts only macros that carry a documentation comment, on the assumption that an undocumented macro is an implementation detail while a documented one is public API meant to be shown. When `true`, every macro that passes the macro filters is extracted whether documented or not. This is independent of `extract-all`, which does not apply to macros.", + "enum": [ + true, + false + ], + "title": "Extract all macros", + "type": "boolean" + }, "extract-anonymous-namespaces": { "default": true, "description": "Determine whether symbols in anonymous namespaces should be extracted.", @@ -339,6 +358,15 @@ "title": "Symbols rendered as \"implementation-defined\"", "type": "array" }, + "include-macros": { + "default": [], + "description": "If any patterns are defined here, only macros whose name matches one of them (or one of `include-symbols`) are extracted. When empty (the default), every macro is a candidate, so with `extract-all-macros` off the documented macros are shown and can then be narrowed with these patterns. Macro names are unqualified, so namespace-scoped `include-symbols` patterns never match a macro; use these patterns to scope macros by name (e.g. `MY_LIB_*`). A macro also listed in `include-symbols` is still included. See `include-symbols` for the pattern syntax.", + "items": { + "type": "string" + }, + "title": "Macro patterns to include", + "type": "array" + }, "include-symbols": { "default": [], "description": "If any patterns are defined here, only symbols that match one of these patterns are extracted. The patterns are applied to the fully qualified name of the symbol without any leading \"::\". A single \"*\" will match all symbols in the namespace. Double \"**\" will match all symbols in the namespace and its subnamespaces. The patterns also support \"?\" for any chars, \"[]\" for charsets, \"[^]\" for inverted charsets, and \"{,...}\" for alternatives.", diff --git a/docs/mrdocs.yml b/docs/mrdocs.yml index 0f025b3908..786e0572e7 100644 --- a/docs/mrdocs.yml +++ b/docs/mrdocs.yml @@ -9,6 +9,16 @@ file-patterns: - '*.hpp' include-symbols: - 'mrdocs::**' +# MrDocs's own macros are maintainer-facing implementation helpers, not +# public API: the MRDOCS_* support macros and the internal X-macros +# (INFO/LOG/F) and compat shims used across the headers. Keep them out of +# MrDocs's own reference. +exclude-macros: + - 'MRDOCS_*' + - 'INFO' + - 'LOG' + - 'F' + - 'FMT_CONSTEVAL' implementation-defined: - '**::detail' # Injected by the MRDOCS_DESCRIBE_* macros diff --git a/include/mrdocs/Metadata.hpp b/include/mrdocs/Metadata.hpp index 96f133c7bf..27420f4271 100644 --- a/include/mrdocs/Metadata.hpp +++ b/include/mrdocs/Metadata.hpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/include/mrdocs/Metadata/Symbol/Macro.hpp b/include/mrdocs/Metadata/Symbol/Macro.hpp new file mode 100644 index 0000000000..46072ed85a --- /dev/null +++ b/include/mrdocs/Metadata/Symbol/Macro.hpp @@ -0,0 +1,74 @@ +// +// 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) +// Copyright (c) 2026 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_API_METADATA_SYMBOL_MACRO_HPP +#define MRDOCS_API_METADATA_SYMBOL_MACRO_HPP + +#include +#include +#include +#include + +namespace mrdocs { + +/** Info for preprocessor macros. + + Covers both object-like and function-like macros. +*/ +struct MacroSymbol final + : SymbolCommonBase +{ + /** Whether this is a function-like macro. + + This is not derivable from @ref Parameters and + @ref IsVariadic: a function-like macro can have an + empty parameter list. `#define F` is object-like, + while `#define F()` is function-like with no + parameters, and only this flag tells them apart. + */ + bool IsFunctionLike = false; + + /** The names of the macro's parameters. + + Empty for object-like macros and for function-like + macros with no parameters. For variadic + function-like macros, this lists only the + named parameters; variadicness is indicated + by @ref IsVariadic. + */ + std::vector Parameters; + + /** Whether the macro takes a variadic argument list. + + True for macros declared with `...`, such as + `#define LOG(fmt, ...) ...`. + */ + bool IsVariadic = false; + + //-------------------------------------------- + + /** Create a macro symbol bound to an ID. + */ + explicit MacroSymbol(SymbolID const& ID) noexcept + : SymbolCommonBase(ID) + { + } +}; + +MRDOCS_DESCRIBE_STRUCT( + MacroSymbol, + (SymbolCommonBase), + (IsFunctionLike, Parameters, IsVariadic) +) + +} // mrdocs + +#endif // MRDOCS_API_METADATA_SYMBOL_MACRO_HPP diff --git a/include/mrdocs/Metadata/Symbol/Namespace.hpp b/include/mrdocs/Metadata/Symbol/Namespace.hpp index 1b179550bf..6725e9aa62 100644 --- a/include/mrdocs/Metadata/Symbol/Namespace.hpp +++ b/include/mrdocs/Metadata/Symbol/Namespace.hpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -55,13 +56,21 @@ struct NamespaceTranche { */ std::vector Usings; + /** Preprocessor macros. + + Only the global namespace's tranche carries these: + macros have no C++ scope, so they are collected at the + top level rather than under any named namespace. + */ + std::vector Macros; + }; MRDOCS_DESCRIBE_STRUCT( NamespaceTranche, (), (Namespaces, NamespaceAliases, Typedefs, Records, Enums, - Functions, Variables, Concepts, Guides, Usings) + Functions, Variables, Concepts, Guides, Usings, Macros) ) /** Join all tranche member lists into a single view. @@ -71,25 +80,16 @@ inline auto allMembers(NamespaceTranche const& T) { - // This is a trick to emulate views::concat in C++20 - return std::views::transform( - std::views::iota(0, 10), - [&T](int const i) -> auto const& { - switch (i) { - case 0: return T.Namespaces; - case 1: return T.NamespaceAliases; - case 2: return T.Typedefs; - case 3: return T.Records; - case 4: return T.Enums; - case 5: return T.Functions; - case 6: return T.Variables; - case 7: return T.Concepts; - case 8: return T.Guides; - case 9: return T.Usings; - default: throw std::out_of_range("Invalid index"); - } - } - ) | std::ranges::views::join; + // Concatenate every member list (emulating C++26 views::concat). + // The lists are discovered by reflection, so adding a member to + // NamespaceTranche extends this automatically, no switch to keep + // in sync. + static constexpr auto members = + describe::memberPointers(); + return members + | std::views::transform( + [&T](auto const p) -> auto const& { return T.*p; }) + | std::ranges::views::join; } /** Describes a namespace and its members. diff --git a/include/mrdocs/Metadata/Symbol/RecordInterface.hpp b/include/mrdocs/Metadata/Symbol/RecordInterface.hpp index d98a53992a..afe437b6dc 100644 --- a/include/mrdocs/Metadata/Symbol/RecordInterface.hpp +++ b/include/mrdocs/Metadata/Symbol/RecordInterface.hpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -75,19 +76,15 @@ inline auto allMembers(RecordInterface const& T) { - // This is a trick to emulate views::concat in C++20 - return - std::views::iota(0, 3) | - std::views::transform( - [&T](int i) -> auto { - switch (i) { - case 0: return allMembers(T.Public); - case 1: return allMembers(T.Protected); - case 2: return allMembers(T.Private); - default: throw std::out_of_range("Invalid index"); - } - }) | - std::ranges::views::join; + // Concatenate the access tranches (emulating C++26 views::concat). + // The tranches are discovered by reflection, so this needs no + // hand-maintained per-tranche switch. + static constexpr auto tranches = + describe::memberPointers(); + return tranches + | std::views::transform( + [&T](auto const p) { return allMembers(T.*p); }) + | std::ranges::views::join; } } // mrdocs diff --git a/include/mrdocs/Metadata/Symbol/RecordTranche.hpp b/include/mrdocs/Metadata/Symbol/RecordTranche.hpp index 4604e43d45..13d8758074 100644 --- a/include/mrdocs/Metadata/Symbol/RecordTranche.hpp +++ b/include/mrdocs/Metadata/Symbol/RecordTranche.hpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2025 Alan de Freitas (alandefreitas@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -81,27 +82,16 @@ inline auto allMembers(RecordTranche const& T) { - // This is a trick to emulate views::concat in C++20 - return std::views::transform( - std::views::iota(0, 11), - [&T](int const i) -> auto const& - { - switch (i) { - case 0: return T.NamespaceAliases; - case 1: return T.Typedefs; - case 2: return T.Records; - case 3: return T.Enums; - case 4: return T.Functions; - case 5: return T.StaticFunctions; - case 6: return T.Variables; - case 7: return T.StaticVariables; - case 8: return T.Concepts; - case 9: return T.Guides; - case 10: return T.Usings; - default: throw std::out_of_range("Invalid index"); - } - } - ) | std::ranges::views::join; + // Concatenate every member list (emulating C++26 views::concat). + // The lists are discovered by reflection, so adding a member to + // RecordTranche extends this automatically, no switch to keep in + // sync. + static constexpr auto members = + describe::memberPointers(); + return members + | std::views::transform( + [&T](auto const p) -> auto const& { return T.*p; }) + | std::ranges::views::join; } } // mrdocs diff --git a/include/mrdocs/Metadata/Symbol/SymbolNodes.inc b/include/mrdocs/Metadata/Symbol/SymbolNodes.inc index 64866eb9d8..31fae3ca9f 100644 --- a/include/mrdocs/Metadata/Symbol/SymbolNodes.inc +++ b/include/mrdocs/Metadata/Symbol/SymbolNodes.inc @@ -24,6 +24,7 @@ INFO(Guide) INFO(NamespaceAlias) INFO(Using) INFO(Concept) +INFO(Macro) #undef INFO diff --git a/include/mrdocs/Support/Describe.hpp b/include/mrdocs/Support/Describe.hpp index 5b67d8e10d..620c9bc894 100644 --- a/include/mrdocs/Support/Describe.hpp +++ b/include/mrdocs/Support/Describe.hpp @@ -277,6 +277,43 @@ describedMemberCount() return n; } +namespace detail { +template struct describe_front; +template +struct describe_front> { using type = D0; }; +} // namespace detail + +/** The member-pointer type shared by `T`'s reflected members. + + Well-formed only when `T` has at least one own reflected member + and all its members share a single type. That homogeneity is + what lets the members be addressed by a runtime index (see + @ref memberPointers). +*/ +template +using member_pointer_t = std::remove_cvref_t< + decltype(detail::describe_front>::type::pointer)>; + +/** Pointers to every reflected member of `T`, in reflection order. + + Complements @ref describedMemberCount by giving O(1) access to + the i-th described member: `t.*memberPointers()[i]`. Requires + the members to be homogeneous (a single type), so a plain array + indexable at runtime can hold them. Use it to iterate a struct's + members generically instead of hand-writing a per-member switch. + + @return An array with one pointer per reflected member of `T`. +*/ +template +consteval std::array, describedMemberCount()> +memberPointers() +{ + std::array, describedMemberCount()> ptrs{}; + std::size_t i = 0; + for_each_member([&](auto d) { ptrs[i++] = d.pointer; }); + return ptrs; +} + /** Whether every reflected member of `T` (inherited ones included) is an undescribed string, i.e. a value convertible to `std::string_view`. diff --git a/mrdocs.rnc b/mrdocs.rnc index 5ddcea2965..536a8cee99 100644 --- a/mrdocs.rnc +++ b/mrdocs.rnc @@ -246,7 +246,8 @@ grammar | element staticVariables { IdList } | element concepts { IdList } | element guides { IdList } - | element usings { IdList } ) + | element usings { IdList } + | element macros { IdList } ) Namespace = element namespace @@ -387,6 +388,15 @@ grammar ( SymbolField | Template | element constraint { NameKind* | text } )* } + Macro = + element macro + { + ( SymbolField + | element isFunctionLike { Bool } + | element parameters { Strings } + | element isVariadic { Bool } )* + } + Overloads = element overloads { @@ -399,7 +409,7 @@ grammar AnySymbol = ( Namespace | Record | Function | Typedef | Enum | EnumConstant - | Variable | Guide | NamespaceAlias | Using | Concept | Overloads ) + | Variable | Guide | NamespaceAlias | Using | Concept | Macro | Overloads ) #--------------------------------------------- # Tagfile diff --git a/share/mrdocs/addons/generator/common/partials/symbol/signature/macro.hbs b/share/mrdocs/addons/generator/common/partials/symbol/signature/macro.hbs new file mode 100644 index 0000000000..036913f213 --- /dev/null +++ b/share/mrdocs/addons/generator/common/partials/symbol/signature/macro.hbs @@ -0,0 +1,12 @@ +{{!-- + Render the synopsis for a preprocessor macro. + + The synopsis is the `#define` signature reconstructed from + the macro's name and, for function-like macros, its parameter + list. The macro body is deliberately not shown: like every + other symbol, the synopsis presents the interface (name and + parameters), not the implementation. + + Expected Context: {Symbol Object} (kind == "macro") +--}} +#define {{name}}{{#if isFunctionLike}}({{#each parameters}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}{{#if isVariadic}}{{#if parameters}}, {{/if}}...{{/if}}){{/if}} diff --git a/share/mrdocs/addons/generator/common/partials/symbol/tranche.hbs b/share/mrdocs/addons/generator/common/partials/symbol/tranche.hbs index 6536ebf5f7..9138a03965 100644 --- a/share/mrdocs/addons/generator/common/partials/symbol/tranche.hbs +++ b/share/mrdocs/addons/generator/common/partials/symbol/tranche.hbs @@ -28,6 +28,7 @@ {{>symbol/members-table members=tranche.variables title="Variables"}} {{>symbol/members-table members=tranche.concepts title="Concepts"}} {{>symbol/members-table members=tranche.usings title=(concat (select label (concat label " ") "") "Using Declarations")}} +{{>symbol/members-table members=tranche.macros title="Macros"}} {{else}} {{>symbol/members-table members=tranche.namespaceAliases title="Namespace Aliases"}} {{>symbol/members-table members=(reject_by tranche.records "isListedOnPrimary") title=(concat (select label (concat label " ") "") "Types")}} diff --git a/src/lib/AST/ASTAction.cpp b/src/lib/AST/ASTAction.cpp index 0dfa6a68a3..b50f22237f 100644 --- a/src/lib/AST/ASTAction.cpp +++ b/src/lib/AST/ASTAction.cpp @@ -7,16 +7,20 @@ // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) // Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // #include #include +#include #include #include +#include #include #include +#include namespace mrdocs { @@ -31,6 +35,10 @@ ExecuteAction() return; } + CI.getPreprocessor().addPPCallbacks( + std::make_unique( + macroDefs_, CI.getPreprocessor())); + // Ensure comments in system headers are retained. // We may want them if, e.g., a declaration was extracted // as a dependency @@ -96,7 +104,7 @@ CreateASTConsumer( llvm::StringRef) { return std::make_unique( - config_, ex_, Compiler); + config_, ex_, Compiler, macroDefs_); } } // mrdocs diff --git a/src/lib/AST/ASTAction.hpp b/src/lib/AST/ASTAction.hpp index 2063c4b24f..9060e24267 100644 --- a/src/lib/AST/ASTAction.hpp +++ b/src/lib/AST/ASTAction.hpp @@ -7,6 +7,7 @@ // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) // Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -15,10 +16,12 @@ #define MRDOCS_LIB_AST_ASTACTION_HPP #include +#include #include #include #include #include +#include namespace mrdocs { @@ -43,6 +46,7 @@ class ASTAction ExecutionContext& ex_; ConfigImpl const& config_; MissingSymbolSink* missingSink_ = nullptr; + std::vector macroDefs_; public: ASTAction( diff --git a/src/lib/AST/ASTVisitor.cpp b/src/lib/AST/ASTVisitor.cpp index c3990598b8..7e10594124 100644 --- a/src/lib/AST/ASTVisitor.cpp +++ b/src/lib/AST/ASTVisitor.cpp @@ -7,6 +7,7 @@ // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) // Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -33,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +43,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -54,13 +60,15 @@ ASTVisitor( Diagnostics const& diags, clang::CompilerInstance& compiler, clang::ASTContext& context, - clang::Sema& sema) noexcept + clang::Sema& sema, + std::vector& macroDefs) noexcept : config_(config) , diags_(diags) , compiler_(compiler) , context_(context) , source_(context.getSourceManager()) , sema_(sema) + , macroDefs_(macroDefs) { // Install handlers for our custom commands initCustomCommentCommands(context_); @@ -84,6 +92,177 @@ build() clang::TranslationUnitDecl const* TU = context_.getTranslationUnitDecl(); traverse(TU); MRDOCS_ASSERT(find(SymbolID::global)); + populateMacros(); +} + +namespace { + +std::vector +gatherMacroParameters(clang::MacroInfo const* MI) +{ + std::vector result; + // For variadic macros, Clang appends a synthetic `__VA_ARGS__` + // identifier as the last parameter. Drop it: variadicness is + // reported via `MacroSymbol::IsVariadic`, mirroring how + // `FunctionSymbol` excludes the trailing C-style `...`. + for (clang::IdentifierInfo const* P : MI->params()) + { + if (P && P->getName() != "__VA_ARGS__") + { + result.emplace_back(P->getName().str()); + } + } + return result; +} + +} // unnamed namespace + +void +ASTVisitor:: +populateMacros() +{ + for (CollectedMacro const& m : macroDefs_) + { + clang::MacroInfo const* const MI = m.Info; + + // Header guards are `#define`s too, but they are an + // implementation detail of inclusion, not part of the API. + MRDOCS_CHECK_OR_CONTINUE(!MI->isUsedForHeaderGuard()); + + std::string const name = m.Identifier->getName().str(); + clang::SourceLocation const defLoc = MI->getDefinitionLoc(); + + // `findFileInfo` returns null only for invalid `SourceLocation` + // values and a handful of exotic `FileID` kinds (predefined-macro + // buffers, unusual `#line` situations). `MacroCollector` already + // drops invalid locations, builtins, and system headers, so + // anything reaching here points at a real source file. + FileInfo* file = findFileInfo(defLoc); + MRDOCS_ASSERT(file); + + clang::PresumedLoc const presLoc = + source_.getPresumedLoc(defLoc, false); + MRDOCS_CHECK_OR_CONTINUE(presLoc.isValid()); + + // Stable `SymbolID` derived from the source-relative + // path, line number, and macro name. + std::string const usr = std::format( + "macro:{}@{}:{}", + name, + file->short_path, + presLoc.getLine()); + std::array const h = + llvm::SHA1::hash(llvm::arrayRefFromStringRef(usr)); + SymbolID const id(h.data()); + MRDOCS_CHECK_OR_CONTINUE(info_.find(id) == info_.end()); + + // Apply user-configured filters: by name (symbol + // patterns) and by source location (file patterns). + ExtractionMode const mode = macroNameMode(name); + MRDOCS_CHECK_OR_CONTINUE(mode != ExtractionMode::Dependency); + MRDOCS_CHECK_OR_CONTINUE(checkFileFilters(file->full_path)); + + auto sym = std::make_unique(id); + sym->Name = name; + // Macros have no C++ scope, so they live in the global + // namespace's tranche, like any other top-level symbol. + sym->Parent = SymbolID::global; + sym->Extraction = mode; + populate(*sym, MI); + + // When `extract-all-macros` is off (the default), require a doc + // comment: an undocumented macro is treated as an implementation + // detail and dropped, while a documented one is public API. + MRDOCS_CHECK_OR_CONTINUE(config_->extractAllMacros || sym->doc); + + sym->Loc.DefLoc = Location( + file->full_path, + file->short_path, + file->source_path, + presLoc.getLine(), + presLoc.getColumn(), + sym->doc.has_value()); + + // Register kept-but-undocumented macros for the + // `warn-if-undocumented` pass, like other symbols. (Undocumented + // macros are already dropped above when `extract-all` is off.) + if (config_->warnIfUndocumented && !sym->doc) + { + UndocumentedSymbol undoc(id, name, SymbolKind::Macro); + undoc.Loc = sym->Loc; + undocumented_.insert(std::move(undoc)); + } + + // Record the macro in the global namespace's member list so + // it is reached by the normal scope traversal, like every + // other symbol. + Symbol* global = find(SymbolID::global); + MRDOCS_ASSERT(global); + addMember(global->asNamespace(), *sym); + + info_.emplace(std::move(sym)); + } +} + +void +ASTVisitor:: +populate(MacroSymbol& I, clang::MacroInfo const* MI) +{ + I.IsFunctionLike = !MI->isObjectLike(); + I.IsVariadic = MI->isVariadic(); + I.Parameters = gatherMacroParameters(MI); + + if (clang::RawComment const* RC = + context_.getRawCommentForAnyRedecl(MI)) + { + // A macro has no `Decl`, but `RawComment::parse` dereferences + // its `Decl` argument, so pass the translation unit as a + // valid, non-null stand-in. + clang::comments::FullComment* FC = RC->parse( + context_, &sema_.getPreprocessor(), + context_.getTranslationUnitDecl()); + if (FC) + { + populateDocComment(I.doc, FC, context_, config_, diags_); + } + } +} + +ExtractionMode +ASTVisitor:: +macroNameMode(std::string_view name) const +{ + using enum SymbolCheckType; + auto const matches = [&]( + std::vector const& p) + { + return checkSymbolFiltersImpl(p, name); + }; + // Macros only need include/exclude filtering. The + // implementation-defined and see-below modes don't apply: a macro + // has no scope to hide and no synopsis body to abbreviate, so + // "implementation-defined" is just exclusion and "see-below" has + // nothing to elide. + // + // Exclusion is the union of the macro-specific and general exclude + // lists: a name excluded anywhere is excluded here too. + if (matches(config_->excludeMacros) || matches(config_->excludeSymbols)) + { + return ExtractionMode::Dependency; + } + // Inclusion: the macro-specific list is the restrictor. Empty means + // "every macro is a candidate" (so documented macros surface with no + // configuration). When non-empty, a macro is kept if it matches that + // list or is explicitly named in the general include-symbols list. + // Namespace-scoped include-symbols never restricts macros, since + // macro names are unqualified and can't match a scoped pattern. + if (!config_->includeMacros.empty() && + !matches(config_->includeMacros) && + !matches(config_->includeSymbols)) + { + return ExtractionMode::Dependency; + } + return ExtractionMode::Regular; } template < @@ -598,7 +777,7 @@ populate( clang::comments::FullComment* FC = RC->parse(D->getASTContext(), &sema_.getPreprocessor(), D); MRDOCS_CHECK_OR(FC, false); - populateDocComment(doc, FC, D, config_, diags_); + populateDocComment(doc, FC, D->getASTContext(), config_, diags_); return true; } @@ -2067,6 +2246,11 @@ addMember( addMember(I.Members.Usings, *U); return; } + if (auto const* U = Member.asMacroPtr()) + { + addMember(I.Members.Macros, *U); + return; + } report::error("Cannot push {} of type {} into members of namespace {}", Member.Name, mrdocs::toString(Member.Kind), diff --git a/src/lib/AST/ASTVisitor.hpp b/src/lib/AST/ASTVisitor.hpp index 7b700c780b..226376a970 100644 --- a/src/lib/AST/ASTVisitor.hpp +++ b/src/lib/AST/ASTVisitor.hpp @@ -7,6 +7,7 @@ // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2023 Krystian Stasiowski (sdkrystian@gmail.com) // Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -15,6 +16,7 @@ #define MRDOCS_LIB_AST_ASTVISITOR_HPP #include +#include #include #include #include @@ -24,6 +26,7 @@ #include #include #include +#include namespace mrdocs { @@ -75,6 +78,10 @@ class ASTVisitor // Semantic analysis clang::Sema& sema_; + // Macros captured by `MacroCollector` during preprocessing. + // Owned by `ASTAction`. + std::vector& macroDefs_; + // An unordered set of all extracted Info declarations SymbolSet info_; @@ -282,13 +289,15 @@ class ASTVisitor @param compiler The compiler instance. @param context The AST context. @param sema The clang::Sema object. + @param macroDefs The macro definitions captured by MacroCollector. */ ASTVisitor( ConfigImpl const& config, Diagnostics const& diags, clang::CompilerInstance& compiler, clang::ASTContext& context, - clang::Sema& sema) noexcept; + clang::Sema& sema, + std::vector& macroDefs) noexcept; /** Build the metadata representation from the AST. @@ -340,6 +349,35 @@ class ASTVisitor } private: + /* Turn the captured macros into `MacroSymbol`s in `info_`. + + Runs after traversal, once the preprocessor has finished + and `macroDefs_` holds every user `#define`. Each entry is + filtered, given a `MacroSymbol`, and populated via + `populate`. Macros carry `Parent = SymbolID::invalid` + (they live at the corpus root, not under any namespace). + */ + void + populateMacros(); + + /* Populate a `MacroSymbol` from its `clang::MacroInfo`. + + The macro analogue of the `populate` overloads used for + declarations. `MacroInfo` is not a `clang::Decl`, so it + cannot be reached through `traverse`; this fills the symbol + (kind flags, parameters, doc comment) directly instead. + */ + void + populate(MacroSymbol& I, clang::MacroInfo const* MI); + + /* Apply the configured symbol-pattern filters to a macro + name and return the corresponding extraction mode. + `Dependency` means "filter out". Mirrors the pattern + precedence used for declarations. + */ + ExtractionMode + macroNameMode(std::string_view name) const; + // ================================================= // AST Traversal // ================================================= diff --git a/src/lib/AST/ASTVisitorConsumer.cpp b/src/lib/AST/ASTVisitorConsumer.cpp index dd96ce1ee0..ed70490b31 100644 --- a/src/lib/AST/ASTVisitorConsumer.cpp +++ b/src/lib/AST/ASTVisitorConsumer.cpp @@ -28,7 +28,8 @@ HandleTranslationUnit(clang::ASTContext& Context) diags, compiler_, Context, - *sema_); + *sema_, + macroDefs_); visitor.build(); ex_.report(std::move(visitor.results()), std::move(diags), std::move(visitor.undocumented())); } diff --git a/src/lib/AST/ASTVisitorConsumer.hpp b/src/lib/AST/ASTVisitorConsumer.hpp index ed13878725..860e6b6743 100644 --- a/src/lib/AST/ASTVisitorConsumer.hpp +++ b/src/lib/AST/ASTVisitorConsumer.hpp @@ -15,10 +15,12 @@ #define MRDOCS_LIB_AST_ASTVISITORCONSUMER_HPP #include +#include #include #include #include #include +#include namespace mrdocs { @@ -42,16 +44,19 @@ class ASTVisitorConsumer ConfigImpl const& config_; ExecutionContext& ex_; clang::CompilerInstance& compiler_; + std::vector& macroDefs_; clang::Sema* sema_ = nullptr; public: ASTVisitorConsumer( ConfigImpl const& config, ExecutionContext& ex, - clang::CompilerInstance& compiler) noexcept + clang::CompilerInstance& compiler, + std::vector& macroDefs) noexcept : config_(config) , ex_(ex) , compiler_(compiler) + , macroDefs_(macroDefs) { } diff --git a/src/lib/AST/ExtractDocComment.cpp b/src/lib/AST/ExtractDocComment.cpp index d3c9d9f06b..5f21516fc4 100644 --- a/src/lib/AST/ExtractDocComment.cpp +++ b/src/lib/AST/ExtractDocComment.cpp @@ -1766,11 +1766,11 @@ class DocCommentVisitor public: DocCommentVisitor( clang::comments::FullComment const* FC, - clang::Decl const* D, + clang::ASTContext const& ctx, Config const& config, Diagnostics& diags) : config_(config) - , ctx_(D->getASTContext()) + , ctx_(ctx) , sm_(ctx_.getSourceManager()) , FC_(FC) , diags_(diags) @@ -1806,12 +1806,12 @@ void populateDocComment( Optional& jd, clang::comments::FullComment const* FC, - clang::Decl const* D, + clang::ASTContext const& ctx, Config const& config, Diagnostics& diags) { - MRDOCS_COMMENT_TRACE(FC, D->getASTContext()); - DocCommentVisitor visitor(FC, D, config, diags); + MRDOCS_COMMENT_TRACE(FC, ctx); + DocCommentVisitor visitor(FC, ctx, config, diags); auto result = visitor.build(); if (!result.empty()) { diff --git a/src/lib/AST/ExtractDocComment.hpp b/src/lib/AST/ExtractDocComment.hpp index 627efeb40c..4a12486a8a 100644 --- a/src/lib/AST/ExtractDocComment.hpp +++ b/src/lib/AST/ExtractDocComment.hpp @@ -18,7 +18,6 @@ #include namespace clang { -class Decl; class ASTContext; class RawComment; namespace comments { @@ -37,14 +36,16 @@ void initCustomCommentCommands( clang::ASTContext& ctx); -/** Extract doc comments from a declaration +/** Extract doc comments from a parsed FullComment. - Extract the DocComment from a declaration, populating the - DocComment object with the information parsed by clang. + Populate the DocComment object with the information + parsed by Clang. Used both for declaration comments + (caller passes `someDecl->getASTContext()`) and for + macro comments, where there is no `Decl`. @param jd The DocComment object to populate @param FC The full comment to parse - @param D The declaration to which the comment applies + @param ctx The ASTContext owning the comment @param config The MrDocs configuration object @param diags The diagnostics object */ @@ -52,7 +53,7 @@ void populateDocComment( Optional& jd, clang::comments::FullComment const* FC, - clang::Decl const* D, + clang::ASTContext const& ctx, Config const& config, Diagnostics& diags); diff --git a/src/lib/AST/MacroCollector.cpp b/src/lib/AST/MacroCollector.cpp new file mode 100644 index 0000000000..5b2a41c528 --- /dev/null +++ b/src/lib/AST/MacroCollector.cpp @@ -0,0 +1,74 @@ +// +// 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) +// Copyright (c) 2026 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#include +#include +#include +#include +#include + +namespace mrdocs { + +namespace { + +bool +inSyntheticBuffer( + clang::SourceLocation loc, + clang::SourceManager const& SM) +{ + // `` holds target predefines (`_MT`, `_MSC_VER`, ...); + // `` holds `-D` macros from the command line + // (e.g. mrdocs's own `__MRDOCS__`). Neither is user source. + return SM.isInSystemHeader(loc) || + SM.isWrittenInBuiltinFile(loc) || + SM.isWrittenInCommandLineFile(loc); +} + +bool +shouldSkip( + clang::MacroInfo const* MI, + clang::SourceLocation defLoc, + clang::SourceManager const& SM) +{ + if (!MI || MI->isBuiltinMacro() || defLoc.isInvalid()) + { + return true; + } + return inSyntheticBuffer(defLoc, SM); +} + +} // unnamed namespace + +void +MacroCollector:: +MacroDefined( + clang::Token const& MacroNameTok, + clang::MacroDirective const* MD) +{ + if (!MD) + { + return; + } + clang::MacroInfo const* const MI = MD->getMacroInfo(); + clang::SourceLocation const defLoc = MI ? + MI->getDefinitionLoc() : clang::SourceLocation(); + clang::SourceManager const& SM = pp_.getSourceManager(); + clang::IdentifierInfo const* const II = MacroNameTok.getIdentifierInfo(); + if (!II || shouldSkip(MI, defLoc, SM)) + { + return; + } + // Record only the raw Clang handles; the visitor derives the + // `MacroSymbol` from them in `populate`, like any other symbol. + sink_.push_back(CollectedMacro{II, MI}); +} + +} // mrdocs diff --git a/src/lib/AST/MacroCollector.hpp b/src/lib/AST/MacroCollector.hpp new file mode 100644 index 0000000000..b6f0eb484d --- /dev/null +++ b/src/lib/AST/MacroCollector.hpp @@ -0,0 +1,78 @@ +// +// 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) +// Copyright (c) 2026 Alan de Freitas (alandefreitas@gmail.com) +// +// Official repository: https://github.com/cppalliance/mrdocs +// + +#ifndef MRDOCS_LIB_AST_MACROCOLLECTOR_HPP +#define MRDOCS_LIB_AST_MACROCOLLECTOR_HPP + +#include +#include + +namespace clang { +class IdentifierInfo; +class MacroDirective; +class MacroInfo; +class Preprocessor; +class Token; +} + +namespace mrdocs { + +/** A macro definition captured from the preprocessor. + + Holds only the raw Clang handles the visitor needs later: + the macro's identifier (for its name) and the preprocessor's + record of the definition (`MacroInfo`, for everything else). + Everything a @ref MacroSymbol needs is derived from these by + `ASTVisitor::populate`, so this carries no MrDocs-level state + of its own. +*/ +struct CollectedMacro +{ + /** The macro's identifier, used for its name. + */ + clang::IdentifierInfo const* Identifier = nullptr; + + /** The preprocessor's record of the definition. + */ + clang::MacroInfo const* Info = nullptr; +}; + +/** Capture `MacroDefined` events from the preprocessor. + + Skip definitions in system headers and built-in macros; + the visitor applies any further filtering. +*/ +class MacroCollector final : public clang::PPCallbacks { + std::vector& sink_; + clang::Preprocessor const& pp_; + +public: + /** Construct a collector that pushes into the given sink. + */ + MacroCollector( + std::vector& sink, + clang::Preprocessor const& pp) noexcept + : sink_(sink) + , pp_(pp) + { + } + + /** Record a `#define` directive. + */ + void + MacroDefined( + clang::Token const& MacroNameTok, + clang::MacroDirective const* MD) override; +}; + +} // mrdocs + +#endif // MRDOCS_LIB_AST_MACROCOLLECTOR_HPP diff --git a/src/lib/ConfigOptions.json b/src/lib/ConfigOptions.json index 497eed6d47..5736a948e0 100644 --- a/src/lib/ConfigOptions.json +++ b/src/lib/ConfigOptions.json @@ -222,6 +222,27 @@ "type": "list", "default": [] }, + { + "name": "extract-all-macros", + "brief": "Extract all macros", + "details": "Like `extract-all`, but for preprocessor macros. When `false` (the default), MrDocs extracts only macros that carry a documentation comment, on the assumption that an undocumented macro is an implementation detail while a documented one is public API meant to be shown. When `true`, every macro that passes the macro filters is extracted whether documented or not. This is independent of `extract-all`, which does not apply to macros.", + "type": "bool", + "default": false + }, + { + "name": "include-macros", + "brief": "Macro patterns to include", + "details": "If any patterns are defined here, only macros whose name matches one of them (or one of `include-symbols`) are extracted. When empty (the default), every macro is a candidate, so with `extract-all-macros` off the documented macros are shown and can then be narrowed with these patterns. Macro names are unqualified, so namespace-scoped `include-symbols` patterns never match a macro; use these patterns to scope macros by name (e.g. `MY_LIB_*`). A macro also listed in `include-symbols` is still included. See `include-symbols` for the pattern syntax.", + "type": "list", + "default": [] + }, + { + "name": "exclude-macros", + "brief": "Macro patterns to exclude", + "details": "A macro whose name matches one of these patterns, or one of `exclude-symbols`, is not extracted even if it would otherwise be included. See `include-symbols` for the pattern syntax.", + "type": "list", + "default": [] + }, { "name": "auto-function-objects", "brief": "Automatically detect algorithm function objects", diff --git a/src/lib/Corpus.cpp b/src/lib/Corpus.cpp index 831ed66e93..6c0276242b 100644 --- a/src/lib/Corpus.cpp +++ b/src/lib/Corpus.cpp @@ -5,6 +5,8 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -16,6 +18,8 @@ #include #include #include +#include +#include #include namespace mrdocs { diff --git a/src/lib/Gen/adoc/AdocGenerator.cpp b/src/lib/Gen/adoc/AdocGenerator.cpp index 934f89602a..d8787afcd6 100644 --- a/src/lib/Gen/adoc/AdocGenerator.cpp +++ b/src/lib/Gen/adoc/AdocGenerator.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // diff --git a/src/lib/Gen/adoc/AdocGenerator.hpp b/src/lib/Gen/adoc/AdocGenerator.hpp index 525543f69c..01134e87a4 100644 --- a/src/lib/Gen/adoc/AdocGenerator.hpp +++ b/src/lib/Gen/adoc/AdocGenerator.hpp @@ -6,6 +6,7 @@ // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // diff --git a/src/lib/Gen/hbs/Builder.cpp b/src/lib/Gen/hbs/Builder.cpp index 8deddf63b5..d6809a6d5f 100644 --- a/src/lib/Gen/hbs/Builder.cpp +++ b/src/lib/Gen/hbs/Builder.cpp @@ -456,7 +456,10 @@ loadLayoutTemplate( loaded = true; } if (!loaded) - formatError("Template {} not found in addons search path", filename).Throw(); + { + return Unexpected(formatError( + "Template {} not found in addons search path", filename)); + } return {}; } @@ -562,6 +565,7 @@ createContext(Symbol const& I) // settings, e.g. lookup config.generator-options.. ctx.set("generatorId", domCorpus.fileExtension); ctx.set("generatorConfig", generatorConfig(domCorpus)); + return ctx; } diff --git a/src/lib/Gen/hbs/HandlebarsGenerator.cpp b/src/lib/Gen/hbs/HandlebarsGenerator.cpp index 92ca798313..3c0fa1c076 100644 --- a/src/lib/Gen/hbs/HandlebarsGenerator.cpp +++ b/src/lib/Gen/hbs/HandlebarsGenerator.cpp @@ -183,6 +183,7 @@ build(Corpus const& corpus) const MRDOCS_TRY(ExecutorGroup ex, createExecutors(*this, domCorpus)); MultiPageVisitor visitor(ex, outputPath, corpus); visitor(corpus.globalNamespace()); + auto errors = ex.wait(); MRDOCS_CHECK_OR(errors.empty(), Unexpected(errors)); report::info("Generated {} pages", visitor.count()); diff --git a/src/lib/Gen/hbs/SinglePageVisitor.cpp b/src/lib/Gen/hbs/SinglePageVisitor.cpp index 295d053336..46d99a3987 100644 --- a/src/lib/Gen/hbs/SinglePageVisitor.cpp +++ b/src/lib/Gen/hbs/SinglePageVisitor.cpp @@ -5,6 +5,7 @@ // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -12,7 +13,9 @@ #include "SinglePageVisitor.hpp" #include "VisitorHelpers.hpp" #include +#include #include +#include namespace mrdocs::hbs { @@ -46,6 +49,17 @@ operator()(T const& I) #define INFO(T) template void SinglePageVisitor::operator()(T##Symbol const&); #include +void +SinglePageVisitor:: +writeText(std::string text) +{ + std::size_t const symbolIdx = numSymbols_++; + ex_.async([this, symbolIdx, text = std::move(text)](Builder&) + { + writePage(text, symbolIdx); + }); +} + // pageNumber is zero-based void SinglePageVisitor:: diff --git a/src/lib/Gen/hbs/SinglePageVisitor.hpp b/src/lib/Gen/hbs/SinglePageVisitor.hpp index ac52b7ea3a..5e2d044ba5 100644 --- a/src/lib/Gen/hbs/SinglePageVisitor.hpp +++ b/src/lib/Gen/hbs/SinglePageVisitor.hpp @@ -5,6 +5,7 @@ // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // @@ -53,6 +54,14 @@ class SinglePageVisitor */ template T> void operator()(T const& I); + + /** Write a literal string at the next position in the page. + + The text is enqueued behind whatever symbols are already + in flight, so the order of `operator()` calls and + `writeText` calls is preserved in the output. + */ + void writeText(std::string text); }; } // mrdocs::hbs diff --git a/src/lib/Gen/html/HTMLGenerator.cpp b/src/lib/Gen/html/HTMLGenerator.cpp index 9342006d5d..5486d7e691 100644 --- a/src/lib/Gen/html/HTMLGenerator.cpp +++ b/src/lib/Gen/html/HTMLGenerator.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // diff --git a/src/lib/Gen/html/HTMLGenerator.hpp b/src/lib/Gen/html/HTMLGenerator.hpp index d10b81704c..e77dd0e921 100644 --- a/src/lib/Gen/html/HTMLGenerator.hpp +++ b/src/lib/Gen/html/HTMLGenerator.hpp @@ -6,6 +6,7 @@ // // Copyright (c) 2023 Vinnie Falco (vinnie.falco@gmail.com) // Copyright (c) 2024 Alan de Freitas (alandefreitas@gmail.com) +// Copyright (c) 2026 Gennaro Prota (gennaro.prota@gmail.com) // // Official repository: https://github.com/cppalliance/mrdocs // diff --git a/src/lib/Metadata/Finalizers/DocComment/Function.hpp b/src/lib/Metadata/Finalizers/DocComment/Function.hpp index c278a3b7d7..364dfb4d28 100644 --- a/src/lib/Metadata/Finalizers/DocComment/Function.hpp +++ b/src/lib/Metadata/Finalizers/DocComment/Function.hpp @@ -429,7 +429,8 @@ populateFunctionReturns(FunctionSymbol& I, CorpusImpl const& corpus) The doc parameter names can contain a single parameter or a list of parameters separated by commas. This function - returns a list of all parameter names in the doc. + returns a list of all parameter names in the doc. Empty + entries (e.g. from a malformed `@param a,`) are skipped. */ llvm::SmallVector getDocCommentParamNames(DocComment const& doc) @@ -443,7 +444,10 @@ getDocCommentParamNames(DocComment const& doc) { auto const trimmed = trim(std::string_view(paramName.begin(), paramName.end())); - result.emplace_back(trimmed); + if (!trimmed.empty()) + { + result.emplace_back(trimmed); + } } } return result; diff --git a/src/lib/Metadata/Finalizers/DocCommentFinalizer.cpp b/src/lib/Metadata/Finalizers/DocCommentFinalizer.cpp index d2b511e372..e050ccc88c 100644 --- a/src/lib/Metadata/Finalizers/DocCommentFinalizer.cpp +++ b/src/lib/Metadata/Finalizers/DocCommentFinalizer.cpp @@ -2096,8 +2096,14 @@ warnDocErrors() { MRDOCS_CHECK_OR_CONTINUE(I->Extraction == ExtractionMode::Regular); MRDOCS_CHECK_OR_CONTINUE(I->IsCopyFromInherited == false); - MRDOCS_CHECK_OR_CONTINUE(I->isFunction()); - warnParamErrors(dynamic_cast(*I)); + if (I->isFunction()) + { + warnParamErrors(dynamic_cast(*I)); + } + else if (I->isMacro()) + { + warnParamErrors(dynamic_cast(*I)); + } } } @@ -2143,6 +2149,54 @@ warnParamErrors(FunctionSymbol const& I) } +void +DocCommentFinalizer:: +warnParamErrors(MacroSymbol const& I) +{ + MRDOCS_CHECK_OR(I.doc); + + auto docParamNames = getDocCommentParamNames(*I.doc); + + // Check for duplicate doc parameters + std::ranges::sort(docParamNames); + auto [firstDup, lastUnique] = std::ranges::unique(docParamNames); + auto duplicateParamNames = std::ranges::subrange(firstDup, lastUnique); + auto [firstDupDup, _] = std::ranges::unique(duplicateParamNames); + for (auto const uniqueDuplicateParamNames = std::ranges::subrange(firstDup, firstDupDup); + std::string_view duplicateParamName: uniqueDuplicateParamNames) + { + this->warn( + *getPrimaryLocation(I), + "{}: Duplicate parameter documentation for '{}'", + corpus_.Corpus::qualifiedName(I), + duplicateParamName); + } + docParamNames.erase(lastUnique, docParamNames.end()); + + // Check for documented parameters that don't exist on the macro. + // For variadic macros, accept both `...` and `__VA_ARGS__` as valid + // names for the variadic argument list. + auto isMacroParam = [&](std::string_view const name) + { + if (std::ranges::find(I.Parameters, name) != I.Parameters.end()) + { + return true; + } + return I.IsVariadic && (name == "..." || name == "__VA_ARGS__"); + }; + for (std::string_view docParamName: docParamNames) + { + if (!isMacroParam(docParamName)) + { + this->warn( + *getPrimaryLocation(I), + "{}: Documented parameter '{}' does not exist", + corpus_.Corpus::qualifiedName(I), + docParamName); + } + } +} + void DocCommentFinalizer:: warnNoParamDocs() @@ -2152,9 +2206,15 @@ warnNoParamDocs() { MRDOCS_CHECK_OR_CONTINUE(I->Extraction == ExtractionMode::Regular); MRDOCS_CHECK_OR_CONTINUE(I->IsCopyFromInherited == false); - MRDOCS_CHECK_OR_CONTINUE(I->isFunction()); MRDOCS_CHECK_OR_CONTINUE(I->doc); - warnNoParamDocs(dynamic_cast(*I)); + if (I->isFunction()) + { + warnNoParamDocs(dynamic_cast(*I)); + } + else if (I->isMacro()) + { + warnNoParamDocs(dynamic_cast(*I)); + } } } @@ -2205,6 +2265,29 @@ warnNoParamDocs(FunctionSymbol const& I) } } +void +DocCommentFinalizer:: +warnNoParamDocs(MacroSymbol const& I) +{ + // Only the named parameters are required to be + // documented. The variadic argument list is optional; + // if the user does document it (as `@param ...` or + // `@param __VA_ARGS__`), the chosen name's validity + // is checked in `warnParamErrors`. + auto docParamNames = getDocCommentParamNames(*I.doc); + for (std::string_view paramName : I.Parameters) + { + if (std::ranges::find(docParamNames, paramName) == docParamNames.end()) + { + this->warn( + *getPrimaryLocation(I), + "{}: Missing documentation for parameter '{}'", + corpus_.Corpus::qualifiedName(I), + paramName); + } + } +} + void DocCommentFinalizer:: warnUndocEnumValues() diff --git a/src/lib/Metadata/Finalizers/DocCommentFinalizer.hpp b/src/lib/Metadata/Finalizers/DocCommentFinalizer.hpp index f203487fe6..4392594b99 100644 --- a/src/lib/Metadata/Finalizers/DocCommentFinalizer.hpp +++ b/src/lib/Metadata/Finalizers/DocCommentFinalizer.hpp @@ -312,12 +312,18 @@ class DocCommentFinalizer { void warnParamErrors(FunctionSymbol const& I); + void + warnParamErrors(MacroSymbol const& I); + void warnNoParamDocs(); void warnNoParamDocs(FunctionSymbol const& I); + void + warnNoParamDocs(MacroSymbol const& I); + void warnUndocEnumValues(); diff --git a/test-files/golden-tests/config/exclude-macros/macros-excluded.cpp b/test-files/golden-tests/config/exclude-macros/macros-excluded.cpp new file mode 100644 index 0000000000..8554091357 --- /dev/null +++ b/test-files/golden-tests/config/exclude-macros/macros-excluded.cpp @@ -0,0 +1,10 @@ +// Macros matching `exclude-macros` are dropped even when documented. + +/// Public macro, kept. +#define MYLIB_VERSION 1 + +/// Implementation detail, dropped by the exclude pattern. +#define MYLIB_DETAIL_INTERNAL 0 + +/// Public assert, kept. +#define MYLIB_ASSERT(x) ((x) ? (void)0 : abort()) diff --git a/test-files/golden-tests/config/exclude-macros/macros-excluded.xml b/test-files/golden-tests/config/exclude-macros/macros-excluded.xml new file mode 100644 index 0000000000..6b6b8a6cda --- /dev/null +++ b/test-files/golden-tests/config/exclude-macros/macros-excluded.xml @@ -0,0 +1,70 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + 25TXh1YkaJx4KSKRnd6vM4Cz56V8 3swZTuggskjgutsth2qaMNHouYwY + + + + MYLIB_VERSION + + + macros-excluded.cpp + macros-excluded.cpp + 4 + 9 + + + + macro + 25TXh1YkaJx4KSKRnd6vM4Cz56V8 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Public macro, kept. + + + + + + + MYLIB_ASSERT + + + macros-excluded.cpp + macros-excluded.cpp + 10 + 9 + + + + macro + 3swZTuggskjgutsth2qaMNHouYwY + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Public assert, kept. + + + + + + + x + + + diff --git a/test-files/golden-tests/config/exclude-macros/macros-excluded.yml b/test-files/golden-tests/config/exclude-macros/macros-excluded.yml new file mode 100644 index 0000000000..5a578edd17 --- /dev/null +++ b/test-files/golden-tests/config/exclude-macros/macros-excluded.yml @@ -0,0 +1,2 @@ +exclude-macros: + - 'MYLIB_DETAIL_*' diff --git a/test-files/golden-tests/config/exclude-symbols/macros-excluded.cpp b/test-files/golden-tests/config/exclude-symbols/macros-excluded.cpp new file mode 100644 index 0000000000..ee8f517a56 --- /dev/null +++ b/test-files/golden-tests/config/exclude-symbols/macros-excluded.cpp @@ -0,0 +1,13 @@ +// Macros matching the exclude pattern are dropped. + +/// Public macro, kept. +#define MYLIB_VERSION 1 + +/// Implementation detail, dropped by the exclude pattern. +#define MYLIB_DETAIL_INTERNAL 0 + +/// Public assert, kept. +#define MYLIB_ASSERT(x) ((x) ? (void)0 : abort()) + +/// Another internal helper, dropped. +#define MYLIB_DETAIL_HELPER(y) ((y) + 1) diff --git a/test-files/golden-tests/config/exclude-symbols/macros-excluded.xml b/test-files/golden-tests/config/exclude-symbols/macros-excluded.xml new file mode 100644 index 0000000000..6b6b8a6cda --- /dev/null +++ b/test-files/golden-tests/config/exclude-symbols/macros-excluded.xml @@ -0,0 +1,70 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + 25TXh1YkaJx4KSKRnd6vM4Cz56V8 3swZTuggskjgutsth2qaMNHouYwY + + + + MYLIB_VERSION + + + macros-excluded.cpp + macros-excluded.cpp + 4 + 9 + + + + macro + 25TXh1YkaJx4KSKRnd6vM4Cz56V8 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Public macro, kept. + + + + + + + MYLIB_ASSERT + + + macros-excluded.cpp + macros-excluded.cpp + 10 + 9 + + + + macro + 3swZTuggskjgutsth2qaMNHouYwY + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Public assert, kept. + + + + + + + x + + + diff --git a/test-files/golden-tests/config/exclude-symbols/macros-excluded.yml b/test-files/golden-tests/config/exclude-symbols/macros-excluded.yml new file mode 100644 index 0000000000..d7604b1773 --- /dev/null +++ b/test-files/golden-tests/config/exclude-symbols/macros-excluded.yml @@ -0,0 +1,2 @@ +exclude-symbols: + - 'MYLIB_DETAIL_*' diff --git a/test-files/golden-tests/config/extract-all-macros/extract-all-macros.cpp b/test-files/golden-tests/config/extract-all-macros/extract-all-macros.cpp new file mode 100644 index 0000000000..ea66528c2f --- /dev/null +++ b/test-files/golden-tests/config/extract-all-macros/extract-all-macros.cpp @@ -0,0 +1,12 @@ +// With `extract-all-macros` on, undocumented macros are extracted too, +// not just the documented ones that show by default. + +#define UNDOC_OBJECT 1 + +/// Documented object-like macro. +#define DOC_OBJECT 2 + +#define UNDOC_FUNC(x) (x) + +/// Documented function-like macro. +#define DOC_FUNC(x) (x) diff --git a/test-files/golden-tests/config/extract-all-macros/extract-all-macros.xml b/test-files/golden-tests/config/extract-all-macros/extract-all-macros.xml new file mode 100644 index 0000000000..c356e4cc35 --- /dev/null +++ b/test-files/golden-tests/config/extract-all-macros/extract-all-macros.xml @@ -0,0 +1,104 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + tQFuKKgXKZ1UPrAexsi9ALYdgMN 4N9PhViqArL8roP1btrbEg6udQs7 i8TH7jf7ovtrtBTF3HpHBvRTvAg 28RUXy6f87xcyyVYtJrwNuMNbb75 + + + + UNDOC_OBJECT + + + extract-all-macros.cpp + extract-all-macros.cpp + 4 + 9 + + + macro + tQFuKKgXKZ1UPrAexsi9ALYdgMN + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + DOC_OBJECT + + + extract-all-macros.cpp + extract-all-macros.cpp + 7 + 9 + + + + macro + 4N9PhViqArL8roP1btrbEg6udQs7 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Documented object-like macro. + + + + + + + UNDOC_FUNC + + + extract-all-macros.cpp + extract-all-macros.cpp + 9 + 9 + + + macro + i8TH7jf7ovtrtBTF3HpHBvRTvAg + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + x + + + + DOC_FUNC + + + extract-all-macros.cpp + extract-all-macros.cpp + 12 + 9 + + + + macro + 28RUXy6f87xcyyVYtJrwNuMNbb75 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Documented function-like macro. + + + + + + + x + + + diff --git a/test-files/golden-tests/config/extract-all-macros/extract-all-macros.yml b/test-files/golden-tests/config/extract-all-macros/extract-all-macros.yml new file mode 100644 index 0000000000..f9d963317c --- /dev/null +++ b/test-files/golden-tests/config/extract-all-macros/extract-all-macros.yml @@ -0,0 +1,2 @@ +extract-all-macros: true +warn-if-undocumented: false diff --git a/test-files/golden-tests/config/extract-all/no-extract-all-macros.cpp b/test-files/golden-tests/config/extract-all/no-extract-all-macros.cpp new file mode 100644 index 0000000000..5e12c97451 --- /dev/null +++ b/test-files/golden-tests/config/extract-all/no-extract-all-macros.cpp @@ -0,0 +1,14 @@ +// Undocumented macros are dropped when `extract-all` is off. + +#define UNDOC_OBJECT 1 + +/// Documented object-like macro. +#define DOC_OBJECT 2 + +#define UNDOC_FUNC(x) (x) + +/** Documented function-like macro. + + @param x Argument. +*/ +#define DOC_FUNC(x) (x) diff --git a/test-files/golden-tests/config/extract-all/no-extract-all-macros.xml b/test-files/golden-tests/config/extract-all/no-extract-all-macros.xml new file mode 100644 index 0000000000..62dfea2299 --- /dev/null +++ b/test-files/golden-tests/config/extract-all/no-extract-all-macros.xml @@ -0,0 +1,82 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + ifZRvs46MXdbXRNPkmDXUFZWNfX 2MJA7aC2EtYDDyqqWvAzp1EbS7FY + + + + DOC_OBJECT + + + no-extract-all-macros.cpp + no-extract-all-macros.cpp + 6 + 9 + + + + macro + ifZRvs46MXdbXRNPkmDXUFZWNfX + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Documented object-like macro. + + + + + + + DOC_FUNC + + + no-extract-all-macros.cpp + no-extract-all-macros.cpp + 14 + 9 + + + + macro + 2MJA7aC2EtYDDyqqWvAzp1EbS7FY + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Documented function-like macro. + + + + + + param + + + text + Argument. + + + x + + + + + + x + + + diff --git a/test-files/golden-tests/config/extract-all/no-extract-all-macros.yml b/test-files/golden-tests/config/extract-all/no-extract-all-macros.yml new file mode 100644 index 0000000000..50005f5805 --- /dev/null +++ b/test-files/golden-tests/config/extract-all/no-extract-all-macros.yml @@ -0,0 +1,2 @@ +extract-all: false +warn-if-undocumented: false diff --git a/test-files/golden-tests/config/include-macros/macros-allowlist.cpp b/test-files/golden-tests/config/include-macros/macros-allowlist.cpp new file mode 100644 index 0000000000..b4c09ef1f0 --- /dev/null +++ b/test-files/golden-tests/config/include-macros/macros-allowlist.cpp @@ -0,0 +1,15 @@ +// Only macros matching `include-macros` are extracted. Namespace-scoped +// `include-symbols` never restricts macros, so the allowlist here is what +// scopes them by name. + +/// Public API version. +#define MYLIB_VERSION 1 + +/// Public assert. +#define MYLIB_ASSERT(x) ((x) ? (void)0 : abort()) + +/// Implementation detail, still under MYLIB_, so still allowed. +#define MYLIB_DETAIL_INTERNAL 0 + +/// Unrelated macro outside the allowlist, dropped. +#define UNRELATED 42 diff --git a/test-files/golden-tests/config/include-macros/macros-allowlist.xml b/test-files/golden-tests/config/include-macros/macros-allowlist.xml new file mode 100644 index 0000000000..883a581c19 --- /dev/null +++ b/test-files/golden-tests/config/include-macros/macros-allowlist.xml @@ -0,0 +1,97 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + 6oML46RphGNbT5N6xF9kXpXn57i n5QAufKQ7hqBwDXk7Ecp6GzZHLE 2MoUpS3Z8krDThUcEryJorBPvsJp + + + + MYLIB_VERSION + + + macros-allowlist.cpp + macros-allowlist.cpp + 6 + 9 + + + + macro + 6oML46RphGNbT5N6xF9kXpXn57i + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Public API version. + + + + + + + MYLIB_ASSERT + + + macros-allowlist.cpp + macros-allowlist.cpp + 9 + 9 + + + + macro + n5QAufKQ7hqBwDXk7Ecp6GzZHLE + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Public assert. + + + + + + + x + + + + MYLIB_DETAIL_INTERNAL + + + macros-allowlist.cpp + macros-allowlist.cpp + 12 + 9 + + + + macro + 2MoUpS3Z8krDThUcEryJorBPvsJp + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Implementation detail, still under MYLIB_, so still allowed. + + + + + + diff --git a/test-files/golden-tests/config/include-macros/macros-allowlist.yml b/test-files/golden-tests/config/include-macros/macros-allowlist.yml new file mode 100644 index 0000000000..4b8785dd6d --- /dev/null +++ b/test-files/golden-tests/config/include-macros/macros-allowlist.yml @@ -0,0 +1,2 @@ +include-macros: + - 'MYLIB_*' diff --git a/test-files/golden-tests/config/multipage/multipage-macros.cpp b/test-files/golden-tests/config/multipage/multipage-macros.cpp new file mode 100644 index 0000000000..4b4be6861f --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.cpp @@ -0,0 +1,23 @@ +// Multipage rendering with corpus-level macros. +// +// Verifies that: +// - A `macros.{ext}` index page is generated. +// - The global-namespace page carries a "See also: Macros" +// navigation hint. +// - Each macro still gets its own per-symbol page. + +namespace foo { + +/// A function in a namespace. +void bar(); + +} // namespace foo + +/// Object-like macro. +#define MY_VERSION 1 + +/** Function-like macro. + + @param x The argument. +*/ +#define MY_INC(x) ((x) + 1) diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/MY_INC.adoc b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/MY_INC.adoc new file mode 100644 index 0000000000..81158523e4 --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/MY_INC.adoc @@ -0,0 +1,27 @@ +[#MY_INC] += MY_INC +:mrdocs: + +Function‐like macro. + +== Synopsis + +Declared in `<multipage‐macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define MY_INC(x) + +---- + +== Parameters + +[cols="1,4"] +|=== +| Name| Description +| *x* +| The argument. +|=== + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/MY_VERSION.adoc b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/MY_VERSION.adoc new file mode 100644 index 0000000000..12046526df --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/MY_VERSION.adoc @@ -0,0 +1,18 @@ +[#MY_VERSION] += MY_VERSION +:mrdocs: + +Object‐like macro. + +== Synopsis + +Declared in `<multipage‐macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define MY_VERSION + +---- + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/foo.adoc b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/foo.adoc new file mode 100644 index 0000000000..531023d1e2 --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/foo.adoc @@ -0,0 +1,16 @@ +[#foo] += foo +:mrdocs: + +== Functions + +[cols="1,4"] +|=== +| Name| Description +| xref:foo/bar.adoc[`bar`] +| A function in a namespace. +|=== + + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/foo/bar.adoc b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/foo/bar.adoc new file mode 100644 index 0000000000..bbba553354 --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/foo/bar.adoc @@ -0,0 +1,19 @@ +[#foo-bar] += xref:foo.adoc[foo]::bar +:relfileprefix: ../ +:mrdocs: + +A function in a namespace. + +== Synopsis + +Declared in `<multipage‐macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +bar(); +---- + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/index.adoc b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/index.adoc new file mode 100644 index 0000000000..42232e1cfb --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/adoc/index.adoc @@ -0,0 +1,39 @@ +[#index] += Global namespace +:mrdocs: + +== Namespaces + +[cols="1"] +|=== +| Name +| xref:foo.adoc[`foo`] +|=== + + +== Macros + +[cols="1,4"] +|=== +| Name| Description +| xref:MY_VERSION.adoc[`MY_VERSION`] +| Object‐like macro. +| xref:MY_INC.adoc[`MY_INC`] +| Function‐like macro. +|=== + + +== xref:foo.adoc[foo] namespace + +=== Functions + +[cols="1,4"] +|=== +| Name| Description +| xref:foo/bar.adoc[`bar`] +| A function in a namespace. +|=== + + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/MY_INC.html b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/MY_INC.html new file mode 100644 index 0000000000..a4a8a07411 --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/MY_INC.html @@ -0,0 +1,38 @@ + + +Reference: MY_INC + + + +
+

MY_INC

+
+
+
+

Function-like macro.

+
+
+

Synopsis

+

Declared in <multipage-macros.cpp>

+
#define MY_INC(x)
+
+
+
+

Parameters

+ + + + + + + +
NameDescription
xThe argument.
+
+
+ +
+ + + \ No newline at end of file diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/MY_VERSION.html b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/MY_VERSION.html new file mode 100644 index 0000000000..a2295c0be4 --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/MY_VERSION.html @@ -0,0 +1,27 @@ + + +Reference: MY_VERSION + + + +
+

MY_VERSION

+
+
+
+

Object-like macro.

+
+
+

Synopsis

+

Declared in <multipage-macros.cpp>

+
#define MY_VERSION
+
+
+
+ +
+ + + \ No newline at end of file diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/foo.html b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/foo.html new file mode 100644 index 0000000000..995483e74a --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/foo.html @@ -0,0 +1,29 @@ + + +Reference: foo + + + +
+

foo

+
+
+
+

Functions

+ + + + + + + +
NameDescription
bar A function in a namespace.
+ +
+ +
+ + + \ No newline at end of file diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/foo/bar.html b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/foo/bar.html new file mode 100644 index 0000000000..f0f6981cc2 --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/foo/bar.html @@ -0,0 +1,27 @@ + + +Reference: bar + + + +
+

foo::bar

+
+
+
+

A function in a namespace.

+
+
+

Synopsis

+

Declared in <multipage-macros.cpp>

+
void
+bar();
+
+
+ +
+ + + \ No newline at end of file diff --git a/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/index.html b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/index.html new file mode 100644 index 0000000000..c043b3ae55 --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.multipage/html/index.html @@ -0,0 +1,55 @@ + + +Reference + + + +
+

Global namespace

+
+
+
+

Namespaces

+ + + + + + + +
Name
foo
+ +

Macros

+ + + + + + + + +
NameDescription
MY_VERSION Object-like macro.
MY_INC Function-like macro.
+ +

foo namespace

+
+
+
+

Functions

+ + + + + + + +
NameDescription
bar A function in a namespace.
+ +
+
+ +
+ + + \ No newline at end of file diff --git a/test-files/golden-tests/config/multipage/multipage-macros.yml b/test-files/golden-tests/config/multipage/multipage-macros.yml new file mode 100644 index 0000000000..c7172e94d6 --- /dev/null +++ b/test-files/golden-tests/config/multipage/multipage-macros.yml @@ -0,0 +1,2 @@ +multipage: true +generator: [adoc, html] diff --git a/test-files/golden-tests/generator/hbs/macros/macros.adoc b/test-files/golden-tests/generator/hbs/macros/macros.adoc new file mode 100644 index 0000000000..36965db409 --- /dev/null +++ b/test-files/golden-tests/generator/hbs/macros/macros.adoc @@ -0,0 +1,95 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + +=== Macros + +[cols="1,4"] +|=== +| Name| Description +| link:#EXAMPLE_ABI_VERSION[`EXAMPLE_ABI_VERSION`] +| The library's ABI version. +| link:#EXAMPLE_CLAMP[`EXAMPLE_CLAMP`] +| Clamp `x` to the closed range [`lo`, `hi`]. +| link:#EXAMPLE_LOG[`EXAMPLE_LOG`] +| Log a formatted message. +|=== + + +[#EXAMPLE_ABI_VERSION] +== EXAMPLE_ABI_VERSION + +The library's ABI version. + +=== Synopsis + +Declared in `<macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define EXAMPLE_ABI_VERSION + +---- + +[#EXAMPLE_CLAMP] +== EXAMPLE_CLAMP + +Clamp `x` to the closed range [`lo`, `hi`]. + +=== Synopsis + +Declared in `<macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define EXAMPLE_CLAMP(x, lo, hi) + +---- + +=== Return Value + +`x` clamped to the range. + +=== Parameters + +[cols="1,4"] +|=== +| Name| Description +| *x* +| The value to clamp. +| *lo* +| The lower bound. +| *hi* +| The upper bound. +|=== + +[#EXAMPLE_LOG] +== EXAMPLE_LOG + +Log a formatted message. + +=== Synopsis + +Declared in `<macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define EXAMPLE_LOG(fmt, ...) + +---- + +=== Parameters + +[cols="1,4"] +|=== +| Name| Description +| *fmt* +| A `printf`‐style format string. +| *...* +| Arguments referenced by `fmt`. +|=== + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/generator/hbs/macros/macros.cpp b/test-files/golden-tests/generator/hbs/macros/macros.cpp new file mode 100644 index 0000000000..becace85e9 --- /dev/null +++ b/test-files/golden-tests/generator/hbs/macros/macros.cpp @@ -0,0 +1,22 @@ +// Rendering coverage for documented macros: the single-page +// `Macros` section heading, the `signature/macro.hbs` synopsis +// (object-like, function-like, variadic) and the doc comment. + +/// The library's ABI version. +#define EXAMPLE_ABI_VERSION 3 + +/** Clamp `x` to the closed range [`lo`, `hi`]. + + @param x The value to clamp. + @param lo The lower bound. + @param hi The upper bound. + @return `x` clamped to the range. +*/ +#define EXAMPLE_CLAMP(x, lo, hi) ((x) < (lo) ? (lo) : ((x) > (hi) ? (hi) : (x))) + +/** Log a formatted message. + + @param fmt A `printf`-style format string. + @param ... Arguments referenced by `fmt`. +*/ +#define EXAMPLE_LOG(fmt, ...) ::example::log_impl((fmt), __VA_ARGS__) diff --git a/test-files/golden-tests/generator/hbs/macros/macros.html b/test-files/golden-tests/generator/hbs/macros/macros.html new file mode 100644 index 0000000000..feea24ceac --- /dev/null +++ b/test-files/golden-tests/generator/hbs/macros/macros.html @@ -0,0 +1,100 @@ + + +Reference + + + +
+

Reference

+
+
+

Global namespace

+
+

Macros

+ + + + + + + + + +
NameDescription
EXAMPLE_ABI_VERSION The library's ABI version.
EXAMPLE_CLAMP Clamp x to the closed range [lo, hi].
EXAMPLE_LOG Log a formatted message.
+ +
+
+
+

EXAMPLE_ABI_VERSION

+
+

The library's ABI version.

+
+
+

Synopsis

+

Declared in <macros.cpp>

+
#define EXAMPLE_ABI_VERSION
+
+
+
+
+
+

EXAMPLE_CLAMP

+
+

Clamp x to the closed range [lo, hi].

+
+
+

Synopsis

+

Declared in <macros.cpp>

+
#define EXAMPLE_CLAMP(x, lo, hi)
+
+
+
+

Return Value

+

x clamped to the range.

+
+
+

Parameters

+ + + + + + + + + +
NameDescription
xThe value to clamp.
loThe lower bound.
hiThe upper bound.
+
+
+
+
+

EXAMPLE_LOG

+
+

Log a formatted message.

+
+
+

Synopsis

+

Declared in <macros.cpp>

+
#define EXAMPLE_LOG(fmt, ...)
+
+
+
+

Parameters

+ + + + + + + + +
NameDescription
fmtA printf-style format string.
...Arguments referenced by fmt.
+
+
+ +
+ + + \ No newline at end of file diff --git a/test-files/golden-tests/generator/hbs/macros/macros.xml b/test-files/golden-tests/generator/hbs/macros/macros.xml new file mode 100644 index 0000000000..59a521566e --- /dev/null +++ b/test-files/golden-tests/generator/hbs/macros/macros.xml @@ -0,0 +1,243 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + BXjLH3FoLuneZMZEsmHVVLE95m2 4QC1G22zW8fuoswW2heMXiU3sKXW 3TgUu4akaEHtmpZHsYDRxsyAgVni + + + + EXAMPLE_ABI_VERSION + + + macros.cpp + macros.cpp + 6 + 9 + + + + macro + BXjLH3FoLuneZMZEsmHVVLE95m2 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + The library's ABI version. + + + + + + + EXAMPLE_CLAMP + + + macros.cpp + macros.cpp + 15 + 9 + + + + macro + 4QC1G22zW8fuoswW2heMXiU3sKXW + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Clamp + + + code + + + text + x + + + + + text + to the closed range [ + + + code + + + text + lo + + + + + text + , + + + code + + + text + hi + + + + + text + ]. + + + + + + returns + + + code + + + text + x + + + + + text + clamped to the range. + + + + + + + param + + + text + The value to clamp. + + + x + + + param + + + text + The lower bound. + + + lo + + + param + + + text + The upper bound. + + + hi + + + + + + x + lo + hi + + + + EXAMPLE_LOG + + + macros.cpp + macros.cpp + 22 + 9 + + + + macro + 3TgUu4akaEHtmpZHsYDRxsyAgVni + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Log a formatted message. + + + + + + param + + + text + A + + + code + + + text + printf + + + + + text + -style format string. + + + fmt + + + param + + + text + Arguments referenced by + + + code + + + text + fmt + + + + + text + . + + + ... + + + + + + fmt + + + + diff --git a/test-files/golden-tests/generator/hbs/macros/mrdocs.yml b/test-files/golden-tests/generator/hbs/macros/mrdocs.yml new file mode 100644 index 0000000000..3cb41f10c7 --- /dev/null +++ b/test-files/golden-tests/generator/hbs/macros/mrdocs.yml @@ -0,0 +1,5 @@ +generator: [adoc, html, xml] +multipage: false +no-default-styles: true +warn-if-undocumented: false +source-root: . diff --git a/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.adoc b/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.adoc new file mode 100644 index 0000000000..fec0dc37be --- /dev/null +++ b/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.adoc @@ -0,0 +1,20 @@ += Reference +:mrdocs: + +[#MY_LIB_ASSERT] +== MY_LIB_ASSERT + +The public assertion macro. + +=== Synopsis + +Declared in `<exclude‐macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define MY_LIB_ASSERT(x) + +---- + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.cpp b/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.cpp new file mode 100644 index 0000000000..1df38e7e96 --- /dev/null +++ b/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.cpp @@ -0,0 +1,8 @@ +/// The public assertion macro. +#define MY_LIB_ASSERT(x) ((x) ? (void)0 : ::my_lib::fail()) + +/// An internal helper, dropped by the exclude pattern. +#define MY_LIB_DETAIL_CHECK(x) ((void)(x)) + +/// The implementation behind MY_LIB_ASSERT, dropped by the exclude pattern. +#define MY_LIB_ASSERT_IMPL(x) ((void)(x)) diff --git a/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.yml b/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.yml new file mode 100644 index 0000000000..7ccc14edaf --- /dev/null +++ b/test-files/golden-tests/snippets/options/exclude-macros/exclude-macros.yml @@ -0,0 +1,5 @@ +include-macros: + - 'MY_LIB_*' +exclude-macros: + - 'MY_LIB_DETAIL_*' + - '*_IMPL' diff --git a/test-files/golden-tests/snippets/options/extract-all-macros/extract-all-macros.adoc b/test-files/golden-tests/snippets/options/extract-all-macros/extract-all-macros.adoc new file mode 100644 index 0000000000..5a07b1290f --- /dev/null +++ b/test-files/golden-tests/snippets/options/extract-all-macros/extract-all-macros.adoc @@ -0,0 +1,20 @@ += Reference +:mrdocs: + +[#MY_LIB_VERSION_STRING] +== MY_LIB_VERSION_STRING + +The library version, as a string literal. + +=== Synopsis + +Declared in `<extract‐all‐macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define MY_LIB_VERSION_STRING + +---- + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/snippets/options/extract-all-macros/extract-all-macros.cpp b/test-files/golden-tests/snippets/options/extract-all-macros/extract-all-macros.cpp new file mode 100644 index 0000000000..72a896fff8 --- /dev/null +++ b/test-files/golden-tests/snippets/options/extract-all-macros/extract-all-macros.cpp @@ -0,0 +1,6 @@ +// Intermediary helper used to build the public macro below. It has an +// ordinary comment, not a documentation comment, so it stays private. +#define MY_LIB_DETAIL_STRINGIZE(x) #x + +/// The library version, as a string literal. +#define MY_LIB_VERSION_STRING MY_LIB_DETAIL_STRINGIZE(1.0) diff --git a/test-files/golden-tests/snippets/options/include-macros/include-macros.adoc b/test-files/golden-tests/snippets/options/include-macros/include-macros.adoc new file mode 100644 index 0000000000..99fa485b08 --- /dev/null +++ b/test-files/golden-tests/snippets/options/include-macros/include-macros.adoc @@ -0,0 +1,20 @@ += Reference +:mrdocs: + +[#MY_LIB_VERSION] +== MY_LIB_VERSION + +The public version macro. + +=== Synopsis + +Declared in `<include‐macros.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define MY_LIB_VERSION + +---- + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/snippets/options/include-macros/include-macros.cpp b/test-files/golden-tests/snippets/options/include-macros/include-macros.cpp new file mode 100644 index 0000000000..f220fc167f --- /dev/null +++ b/test-files/golden-tests/snippets/options/include-macros/include-macros.cpp @@ -0,0 +1,5 @@ +/// The public version macro. +#define MY_LIB_VERSION 1 + +/// A helper outside the library's public prefix. +#define OTHER_HELPER(x) (x) diff --git a/test-files/golden-tests/snippets/options/include-macros/include-macros.yml b/test-files/golden-tests/snippets/options/include-macros/include-macros.yml new file mode 100644 index 0000000000..eb98f87a16 --- /dev/null +++ b/test-files/golden-tests/snippets/options/include-macros/include-macros.yml @@ -0,0 +1,2 @@ +include-macros: + - 'MY_LIB_*' diff --git a/test-files/golden-tests/snippets/options/macros-guard/macros-guard.adoc b/test-files/golden-tests/snippets/options/macros-guard/macros-guard.adoc new file mode 100644 index 0000000000..40bb99098e --- /dev/null +++ b/test-files/golden-tests/snippets/options/macros-guard/macros-guard.adoc @@ -0,0 +1,20 @@ += Reference +:mrdocs: + +[#MY_LIB_HAS_INT128] +== MY_LIB_HAS_INT128 + +Defined by the library when 128‐bit integer support is available. + +=== Synopsis + +Declared in `<macros‐guard.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +#define MY_LIB_HAS_INT128 + +---- + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/snippets/options/macros-guard/macros-guard.cpp b/test-files/golden-tests/snippets/options/macros-guard/macros-guard.cpp new file mode 100644 index 0000000000..8325a6c32e --- /dev/null +++ b/test-files/golden-tests/snippets/options/macros-guard/macros-guard.cpp @@ -0,0 +1,13 @@ +// The real definition exists only where the platform provides 128-bit +// integers, which many toolchains (MSVC, 32-bit targets) do not, so +// MrDocs may never see this #define. +#if defined(__SIZEOF_INT128__) +#define MY_LIB_HAS_INT128 1 +#endif + +// MrDocs always defines __MRDOCS__, so a guarded definition lets the +// feature-test macro be documented regardless of the platform. +#ifdef __MRDOCS__ +/// Defined by the library when 128-bit integer support is available. +#define MY_LIB_HAS_INT128 +#endif diff --git a/test-files/golden-tests/symbols/macro/macros-edge-cases.cpp b/test-files/golden-tests/symbols/macro/macros-edge-cases.cpp new file mode 100644 index 0000000000..34d5dd8ef4 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-edge-cases.cpp @@ -0,0 +1,28 @@ +#define FIRST_THING_IN_FILE 1 + +// Edge cases for macro extraction. +// +// - `FIRST_THING_IN_FILE`'s definition starts with the +// first token in the file: there is no preceding comment +// of any kind. +// +// - `REDEFINED_MACRO` is defined twice (with an `#undef` +// in between) so the corpus ends up with two macro +// symbols that share a name. +// +// - `DOC_THEN_BLANK` is preceded by a doc comment with +// one blank line in between. +// +// - `SAME_LINE` has its doc comment on the same line as +// the directive. + +#define REDEFINED_MACRO 100 +#undef REDEFINED_MACRO +#define REDEFINED_MACRO 200 + +/// Documentation for `DOC_THEN_BLANK`; one blank line +/// separates this comment from the macro definition. + +#define DOC_THEN_BLANK 1 + +/** Doc-comment that ends on the macro's line. */ #define SAME_LINE 2 diff --git a/test-files/golden-tests/symbols/macro/macros-edge-cases.xml b/test-files/golden-tests/symbols/macro/macros-edge-cases.xml new file mode 100644 index 0000000000..bd1bca131c --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-edge-cases.xml @@ -0,0 +1,124 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + taiRsNxDnEPbGDX6TUAdTGe8FsT 2gZEKEXddibTYTDBviFnMfz3ri1H 2RxkLvemUQ3X8C4baVnh1hcpUDq1 2CjEHzyvJeernxCXkT7QGgKDP22D 3hM8jc31JWkocdaQKVddcEVJSd7F + + + + FIRST_THING_IN_FILE + + + macros-edge-cases.cpp + macros-edge-cases.cpp + 1 + 9 + + + macro + taiRsNxDnEPbGDX6TUAdTGe8FsT + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + REDEFINED_MACRO + + + macros-edge-cases.cpp + macros-edge-cases.cpp + 19 + 9 + + + macro + 2gZEKEXddibTYTDBviFnMfz3ri1H + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + REDEFINED_MACRO + + + macros-edge-cases.cpp + macros-edge-cases.cpp + 21 + 9 + + + macro + 2RxkLvemUQ3X8C4baVnh1hcpUDq1 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + DOC_THEN_BLANK + + + macros-edge-cases.cpp + macros-edge-cases.cpp + 26 + 9 + + + + macro + 2CjEHzyvJeernxCXkT7QGgKDP22D + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Documentation for + + + code + + + text + DOC_THEN_BLANK + + + + + text + ; one blank line separates this comment from the macro definition. + + + + + + + SAME_LINE + + + macros-edge-cases.cpp + macros-edge-cases.cpp + 28 + 59 + + + + macro + 3hM8jc31JWkocdaQKVddcEVJSd7F + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Doc-comment that ends on the macro's line. + + + + + + diff --git a/test-files/golden-tests/symbols/macro/macros-edge-cases.yml b/test-files/golden-tests/symbols/macro/macros-edge-cases.yml new file mode 100644 index 0000000000..b4831ad201 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-edge-cases.yml @@ -0,0 +1 @@ +extract-all-macros: true diff --git a/test-files/golden-tests/symbols/macro/macros-embedded.cpp b/test-files/golden-tests/symbols/macro/macros-embedded.cpp new file mode 100644 index 0000000000..a3e2978829 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-embedded.cpp @@ -0,0 +1,14 @@ +// Single-page output in embedded mode with at least one +// macro present. Exercises the embedded-mode branch of +// `HandlebarsGenerator::buildOne`, which is otherwise +// unreached by the wrapped-mode default of the rest of the +// suite. + +/// A simple object-like macro. +#define EMBED_OBJECT 1 + +/** A simple function-like macro. + + @param x The argument. +*/ +#define EMBED_FUNC(x) (x) diff --git a/test-files/golden-tests/symbols/macro/macros-embedded.xml b/test-files/golden-tests/symbols/macro/macros-embedded.xml new file mode 100644 index 0000000000..9d9d3ced3f --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-embedded.xml @@ -0,0 +1,82 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + vdpMVAYZuWBsRhcnASK83Yx5S4F 2dFYE4LwLyAkA3uA7Kd4gfZhuyLt + + + + EMBED_OBJECT + + + macros-embedded.cpp + macros-embedded.cpp + 8 + 9 + + + + macro + vdpMVAYZuWBsRhcnASK83Yx5S4F + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + A simple object-like macro. + + + + + + + EMBED_FUNC + + + macros-embedded.cpp + macros-embedded.cpp + 14 + 9 + + + + macro + 2dFYE4LwLyAkA3uA7Kd4gfZhuyLt + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + A simple function-like macro. + + + + + + param + + + text + The argument. + + + x + + + + + + x + + + diff --git a/test-files/golden-tests/symbols/macro/macros-embedded.yml b/test-files/golden-tests/symbols/macro/macros-embedded.yml new file mode 100644 index 0000000000..603e9b1172 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-embedded.yml @@ -0,0 +1 @@ +embedded: true diff --git a/test-files/golden-tests/symbols/macro/macros-header-guard.cpp b/test-files/golden-tests/symbols/macro/macros-header-guard.cpp new file mode 100644 index 0000000000..9fb4f1f013 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-header-guard.cpp @@ -0,0 +1,4 @@ +// Header-guard suppression: the companion header wraps its body +// in an `#ifndef`/`#define`/`#endif` guard, so the corpus keeps +// `MHG_LIMIT` but drops the guard macro. +#include "macros-header-guard.hpp" diff --git a/test-files/golden-tests/symbols/macro/macros-header-guard.hpp b/test-files/golden-tests/symbols/macro/macros-header-guard.hpp new file mode 100644 index 0000000000..d17acc6d15 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-header-guard.hpp @@ -0,0 +1,10 @@ +// A guarded header. The include guard `MACROS_HEADER_GUARD_HPP` +// is an inclusion artifact and must not be extracted; the +// documented `MHG_LIMIT` must. +#ifndef MACROS_HEADER_GUARD_HPP +#define MACROS_HEADER_GUARD_HPP + +/// The maximum supported value. +#define MHG_LIMIT 256 + +#endif // MACROS_HEADER_GUARD_HPP diff --git a/test-files/golden-tests/symbols/macro/macros-header-guard.xml b/test-files/golden-tests/symbols/macro/macros-header-guard.xml new file mode 100644 index 0000000000..757acaca58 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-header-guard.xml @@ -0,0 +1,39 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + 1gwTwMaiDbHBadfKwUXwHkduac7 + + + + MHG_LIMIT + + + macros-header-guard.hpp + macros-header-guard.hpp + 8 + 9 + + + + macro + 1gwTwMaiDbHBadfKwUXwHkduac7 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + The maximum supported value. + + + + + + diff --git a/test-files/golden-tests/symbols/macro/macros-multi-file.cpp b/test-files/golden-tests/symbols/macro/macros-multi-file.cpp new file mode 100644 index 0000000000..1739b1ce43 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-multi-file.cpp @@ -0,0 +1,10 @@ +// Multi-file macro extraction. The companion header +// `macros-multi-file.hpp` defines `SHARED_MACRO`; this +// translation unit `#undef`s it and redefines it. The +// resulting corpus has two macro symbols sharing the +// `SHARED_MACRO` name but living in different files. + +#include "macros-multi-file.hpp" + +#undef SHARED_MACRO +#define SHARED_MACRO 2 diff --git a/test-files/golden-tests/symbols/macro/macros-multi-file.hpp b/test-files/golden-tests/symbols/macro/macros-multi-file.hpp new file mode 100644 index 0000000000..8fa388f2a9 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-multi-file.hpp @@ -0,0 +1,5 @@ +// Companion header for macros-multi-file.cpp. The macro +// defined here shares its name with one defined in the +// .cpp, so the corpus ends up with two macro symbols whose +// only difference is the source file. +#define SHARED_MACRO 1 diff --git a/test-files/golden-tests/symbols/macro/macros-multi-file.xml b/test-files/golden-tests/symbols/macro/macros-multi-file.xml new file mode 100644 index 0000000000..b27025db3c --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-multi-file.xml @@ -0,0 +1,42 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + QG5iRzeDE2tatU6QHB3vtFnqFhG 2vqFfC4Y3Y3Ru4FxrrfLh9FYKv8S + + + + SHARED_MACRO + + + macros-multi-file.hpp + macros-multi-file.hpp + 5 + 9 + + + macro + QG5iRzeDE2tatU6QHB3vtFnqFhG + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + SHARED_MACRO + + + macros-multi-file.cpp + macros-multi-file.cpp + 10 + 9 + + + macro + 2vqFfC4Y3Y3Ru4FxrrfLh9FYKv8S + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + diff --git a/test-files/golden-tests/symbols/macro/macros-multi-file.yml b/test-files/golden-tests/symbols/macro/macros-multi-file.yml new file mode 100644 index 0000000000..b4831ad201 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-multi-file.yml @@ -0,0 +1 @@ +extract-all-macros: true diff --git a/test-files/golden-tests/symbols/macro/macros-with-symbols.cpp b/test-files/golden-tests/symbols/macro/macros-with-symbols.cpp new file mode 100644 index 0000000000..5f96bd6d5e --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-with-symbols.cpp @@ -0,0 +1,19 @@ +// A macro and regular symbols coexist in a single corpus. The macro +// lands in the global namespace's macro list, while the namespace, +// function, and struct keep their usual places. Their SymbolIDs never +// collide: a macro ID hashes "macro:NAME@path:line" whereas a +// declaration ID hashes the Clang USR, so the two id spaces are +// disjoint by construction. + +/// A documented function-like macro. +#define WIDGET_ASSERT(cond) ((void)0) + +namespace widget { + +/// A regular function. +void configure(); + +/// A regular struct. +struct Config {}; + +} // namespace widget diff --git a/test-files/golden-tests/symbols/macro/macros-with-symbols.xml b/test-files/golden-tests/symbols/macro/macros-with-symbols.xml new file mode 100644 index 0000000000..f5a0b69595 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros-with-symbols.xml @@ -0,0 +1,135 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + py9LKA7N6XHWx31nJCjhre3iKsW + LBrdDbU1mAnHHHb6uYccUsa4UVE + + + + widget + + + + macros-with-symbols.cpp + macros-with-symbols.cpp + 11 + 1 + + + + namespace + py9LKA7N6XHWx31nJCjhre3iKsW + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + 3iqodPZa5WPhQmTTuaiSyMjSpne8 + hsVk6NuBm4h7myX7qyMYpi6VG4t + + + + Config + + + macros-with-symbols.cpp + macros-with-symbols.cpp + 17 + 1 + + + + record + 3iqodPZa5WPhQmTTuaiSyMjSpne8 + regular + py9LKA7N6XHWx31nJCjhre3iKsW + + + brief + + + text + A regular struct. + + + + + struct + + + configure + + + + macros-with-symbols.cpp + macros-with-symbols.cpp + 14 + 1 + + + + + function + hsVk6NuBm4h7myX7qyMYpi6VG4t + regular + py9LKA7N6XHWx31nJCjhre3iKsW + + + brief + + + text + A regular function. + + + + + + + named + + + identifier + void + + + void + + + normal + + + WIDGET_ASSERT + + + macros-with-symbols.cpp + macros-with-symbols.cpp + 9 + 9 + + + + macro + LBrdDbU1mAnHHHb6uYccUsa4UVE + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + A documented function-like macro. + + + + + + + cond + + + diff --git a/test-files/golden-tests/symbols/macro/macros.cpp b/test-files/golden-tests/symbols/macro/macros.cpp new file mode 100644 index 0000000000..fe5c311275 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros.cpp @@ -0,0 +1,118 @@ +// Object-like macros with various body shapes. +/// A documentation comment. +#define FOO 1 +#define PI 3.14 +#define GREETING "hello" + +// Object-like macro that expands to nothing. +#define EMPTY + +/** Add two values. + + @param a First operand. + @param b Second operand. +*/ +#define ADD(a, b) ((a) + (b)) + +/** Log @p msg to `stderr`, tagged with the current file and line number. + + @param msg The message to log. +*/ +#define LOG(msg) fprintf(stderr, "%s:%d: %s\n", __FILE__, __LINE__, msg) + +// Variadic macro. +#define LOG_F(fmt, ...) printf(fmt, __VA_ARGS__) + +// Variadic-only (no named parameters). +#define VLOG(...) printf(__VA_ARGS__) + +// Body spread across lines via backslash continuation. +#define ANSWER \ + (40 \ + + \ + 2) + +// Function-like macro with line continuation. +#define MIN(a, b) \ + ((a) < (b) \ + ? (a) \ + : (b)) + +// Macro definition with whitespace between `#` and `define`, plus +// backslashes. The backslashes should remain aligned in the output, +// despite the removal of the whitespace between `#` and `define`. +# define SQUARE(x) ( \ + (x) \ + * \ + (x)) + +// Body with a continuation line having fewer leading +// whitespace chars than were stripped from the `#define` +// line. +# define MISALIGNED(x) ( \ + (x) ) + +/** Sum a variadic list of values. + + @param ... The values to sum. +*/ +#define SUM(...) (__VA_ARGS__) + +/** Variadic `printf` wrapper, documenting the variadic + argument list with the conventional `...` form. + + @param fmt The format string. + @param ... Format arguments. +*/ +#define PRINTF(fmt, ...) printf(fmt, __VA_ARGS__) + +/** Variadic printf wrapper, documented with the explicit + `__VA_ARGS__` name. + + @param fmt The format string. + @param __VA_ARGS__ Format arguments. +*/ +#define VPRINTF(fmt, ...) printf(fmt, __VA_ARGS__) + +/** Compute the median of three values. + + @param a,b,c The three values to compare. +*/ +#define MEDIAN3(a, b, c) \ + (((a) > (b)) ? \ + (((b) > (c)) ? (b) : (((a) > (c)) ? (c) : (a))) : \ + (((a) > (c)) ? (a) : (((b) > (c)) ? (c) : (b)))) + +// --- Intentionally bad doc comments. These exercise the +// parameter-doc validation warnings that fire during +// finalization. The macros still render normally; the +// warnings go to `stderr` only. --- + +/** Duplicate parameter documentation. + + @param x One thing. + @param x Same thing again. +*/ +#define DUP(x) (x) + +/** Documentation that names a parameter the macro + does not actually have. + + @param y Not actually a parameter of WRONG_PARAM. +*/ +#define WRONG_PARAM(x) (x) + +/** Non-variadic macro that mistakenly documents a + variadic argument list. + + @param x The argument. + @param ... Not really variadic. +*/ +#define NON_VARIADIC(x) (x) + +/** Object-like macro with a spurious parameter + documentation block. + + @param x Should not be here. +*/ +#define OBJECT_WITH_PARAM 1 diff --git a/test-files/golden-tests/symbols/macro/macros.xml b/test-files/golden-tests/symbols/macro/macros.xml new file mode 100644 index 0000000000..d59f783bc5 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros.xml @@ -0,0 +1,743 @@ + + + + namespace + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + regular + + 3KokfPfZXiD4EAfFZytrfCT6Qjxr idKKNbHYquVJgZX7GPChW7WYqSh 3XnVYwGqrtnkzT5sMaWVCDsKLdD2 bNPfz3TBGnpgJaxGa5wc1r85hq7 gbEzDQ11PFhDs5EXfjVsHNukAc5 4Q7TTx3mZusx1BmztiXtcv41BEPz 4F6vnNtWjsCJvAspRPpFz3FtrdA3 4Fbi3tQEeTohxw38nyq4Ju4oFmzj 2mse2tzUeyDpF8QrLzMBXzyzhUUm 3g9xse138ygnrEV7iXMcAhvEwHzP 3uc94queU1WFsN6HZDjAQbRedRwv 2EefAggSG8L7H4vHJtCnn85PFMfw 3Syex11XobpE3ALhGXqz1zpq97TH 2YmbqbEZhyx5i19pKEaBDjHm4yKS 3ec1weFMC8QuRH2sXH5b8jFFozUR 3kxr1ifc8LVeq2pHKi7RFJoMJBEC 4YvEmEyzaDiEZ22KKjKi8pPZFqN5 2DeCzuXQbMbRRHU6BnKV9exULsuV 2Q78jvu4FSWoSEUrSMNs6wmhxSvK nsoUTEXNh8agVJaaYL2Hb2SnmiW + + + + FOO + + + macros.cpp + macros.cpp + 3 + 9 + + + + macro + 3KokfPfZXiD4EAfFZytrfCT6Qjxr + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + A documentation comment. + + + + + + + PI + + + macros.cpp + macros.cpp + 4 + 9 + + + macro + idKKNbHYquVJgZX7GPChW7WYqSh + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + GREETING + + + macros.cpp + macros.cpp + 5 + 9 + + + macro + 3XnVYwGqrtnkzT5sMaWVCDsKLdD2 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + EMPTY + + + macros.cpp + macros.cpp + 8 + 9 + + + macro + bNPfz3TBGnpgJaxGa5wc1r85hq7 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + ADD + + + macros.cpp + macros.cpp + 15 + 9 + + + + macro + gbEzDQ11PFhDs5EXfjVsHNukAc5 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Add two values. + + + + + + param + + + text + First operand. + + + a + + + param + + + text + Second operand. + + + b + + + + + + a + b + + + + LOG + + + macros.cpp + macros.cpp + 21 + 9 + + + + macro + 4Q7TTx3mZusx1BmztiXtcv41BEPz + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Log + + + code + + + text + msg + + + + + text + to + + + code + + + text + stderr + + + + + text + , tagged with the current file and line number. + + + + + + param + + + text + The message to log. + + + msg + + + + + + msg + + + + LOG_F + + + macros.cpp + macros.cpp + 24 + 9 + + + macro + 4F6vnNtWjsCJvAspRPpFz3FtrdA3 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + fmt + + + + + VLOG + + + macros.cpp + macros.cpp + 27 + 9 + + + macro + 4Fbi3tQEeTohxw38nyq4Ju4oFmzj + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + + + ANSWER + + + macros.cpp + macros.cpp + 30 + 9 + + + macro + 2mse2tzUeyDpF8QrLzMBXzyzhUUm + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + MIN + + + macros.cpp + macros.cpp + 36 + 9 + + + macro + 3g9xse138ygnrEV7iXMcAhvEwHzP + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + a + b + + + + SQUARE + + + macros.cpp + macros.cpp + 44 + 12 + + + macro + 3uc94queU1WFsN6HZDjAQbRedRwv + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + x + + + + MISALIGNED + + + macros.cpp + macros.cpp + 52 + 12 + + + macro + 2EefAggSG8L7H4vHJtCnn85PFMfw + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + x + + + + SUM + + + macros.cpp + macros.cpp + 59 + 9 + + + + macro + 3Syex11XobpE3ALhGXqz1zpq97TH + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Sum a variadic list of values. + + + + + + param + + + text + The values to sum. + + + ... + + + + + + + + PRINTF + + + macros.cpp + macros.cpp + 67 + 9 + + + + macro + 2YmbqbEZhyx5i19pKEaBDjHm4yKS + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Variadic + + + code + + + text + printf + + + + + text + wrapper, documenting the variadic argument list with the conventional + + + code + + + text + ... + + + + + text + form. + + + + + + param + + + text + The format string. + + + fmt + + + param + + + text + Format arguments. + + + ... + + + + + + fmt + + + + + VPRINTF + + + macros.cpp + macros.cpp + 75 + 9 + + + + macro + 3ec1weFMC8QuRH2sXH5b8jFFozUR + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Variadic printf wrapper, documented with the explicit + + + code + + + strong + + + text + VA_ARGS + + + + + + + text + name. + + + + + + param + + + text + The format string. + + + fmt + + + param + + + text + Format arguments. + + + __VA_ARGS__ + + + + + + fmt + + + + + MEDIAN3 + + + macros.cpp + macros.cpp + 81 + 9 + + + + macro + 3kxr1ifc8LVeq2pHKi7RFJoMJBEC + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Compute the median of three values. + + + + + + param + + + text + The three values to compare. + + + a,b,c + + + + + + a + b + c + + + + DUP + + + macros.cpp + macros.cpp + 96 + 9 + + + + macro + 4YvEmEyzaDiEZ22KKjKi8pPZFqN5 + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Duplicate parameter documentation. + + + + + + param + + + text + One thing. + + + x + + + param + + + text + Same thing again. + + + x + + + + + + x + + + + WRONG_PARAM + + + macros.cpp + macros.cpp + 103 + 9 + + + + macro + 2DeCzuXQbMbRRHU6BnKV9exULsuV + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Documentation that names a parameter the macro does not actually have. + + + + + + param + + + text + Not actually a parameter of WRONG_PARAM. + + + y + + + + + + x + + + + NON_VARIADIC + + + macros.cpp + macros.cpp + 111 + 9 + + + + macro + 2Q78jvu4FSWoSEUrSMNs6wmhxSvK + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Non-variadic macro that mistakenly documents a variadic argument list. + + + + + + param + + + text + The argument. + + + x + + + param + + + text + Not really variadic. + + + ... + + + + + + x + + + + OBJECT_WITH_PARAM + + + macros.cpp + macros.cpp + 118 + 9 + + + + macro + nsoUTEXNh8agVJaaYL2Hb2SnmiW + regular + 4ZrjxJnU1LA5xSyrWMNuXTvSYKwt + + + brief + + + text + Object-like macro with a spurious parameter documentation block. + + + + + + param + + + text + Should not be here. + + + x + + + + + diff --git a/test-files/golden-tests/symbols/macro/macros.yml b/test-files/golden-tests/symbols/macro/macros.yml new file mode 100644 index 0000000000..b4831ad201 --- /dev/null +++ b/test-files/golden-tests/symbols/macro/macros.yml @@ -0,0 +1 @@ +extract-all-macros: true