diff --git a/docs/modules/ROOT/pages/config-file.adoc b/docs/modules/ROOT/pages/config-file.adoc index 052c68e39c..92fc81e131 100644 --- a/docs/modules/ROOT/pages/config-file.adoc +++ b/docs/modules/ROOT/pages/config-file.adoc @@ -156,8 +156,6 @@ see-below: - 'my_library::see_below::**' ---- -Symbols are only checked against the `implementation-detail` and `see-below` options if they match the `include-symbols` option. - [,cpp] ---- namespace my_library @@ -184,6 +182,21 @@ namespace my_library } ---- +=== Whitelisting Rules + +The rules for whitelisting symbols (`include-symbols`, `implementation-defined`, and `see-below`) are less strict than the rules for blacklisting symbols (`exclude-symbols`). A symbol is considered whitelisted if it matches any of the following conditions: + +1. The symbol strictly matches one of the patterns. +** For instance, the patterns `std::vector` and `std::*` both match `std::vector` strictly. +2. The symbol is a parent namespace of an included symbol. +** For instance, the pattern `std::filesystem::*` also includes `std` and `std::filesystem`. +3. The parent symbol is also included. +** For instance, the pattern `std::*` also matches `std::vector::iterator` because `std::vector::iterator` is a member of `std::vector`, which is matches the pattern. +4. The symbol is a child of a literal pattern representing a namespace. +** For instance, the literal pattern `std` matches `std::filesystem::path::iterator` because `std` is a literal pattern matching a namespace. In other words, these literal patterns represent the namespace and its subnamespaces as if the pattern were `std::**`. + +For exclusion rules, the symbol must strictly match the pattern to be excluded. + [#config-options-reference] == Reference diff --git a/include/mrdocs/Support/Glob.hpp b/include/mrdocs/Support/Glob.hpp index 288844c0b1..a65d127fd7 100644 --- a/include/mrdocs/Support/Glob.hpp +++ b/include/mrdocs/Support/Glob.hpp @@ -97,6 +97,16 @@ class GlobPattern { bool matchPatternPrefix(std::string_view prefix, char delimiter) const; + /** Checks if the glob pattern is a literal string. + + This function determines if the glob pattern does not contain + any special characters. In other words, it matches a single string. + + @return true if the glob pattern is a literal string, false otherwise. + */ + bool + isLiteral() const; + /** Returns the glob pattern. @return The glob pattern as a string view. @@ -185,6 +195,19 @@ class PathGlobPattern { return glob_.matchPatternPrefix(prefix, '/'); } + /** Checks if the glob pattern is a literal string. + + This function determines if the glob pattern does not contain + any special characters. In other words, it matches a single string. + + @return true if the glob pattern is a literal string, false otherwise. + */ + bool + isLiteral() const + { + return glob_.isLiteral(); + } + /** Returns the glob pattern. @return The glob pattern as a string view. @@ -276,6 +299,19 @@ class SymbolGlobPattern { return glob_.matchPatternPrefix(prefix, ':'); } + /** Checks if the glob pattern is a literal string. + + This function determines if the glob pattern does not contain + any special characters. In other words, it matches a single string. + + @return true if the glob pattern is a literal string, false otherwise. + */ + bool + isLiteral() const + { + return glob_.isLiteral(); + } + /** Returns the glob pattern. @return The glob pattern as a string view. diff --git a/src/lib/AST/ASTVisitor.cpp b/src/lib/AST/ASTVisitor.cpp index 1ab9e0f5a5..e38324a111 100644 --- a/src/lib/AST/ASTVisitor.cpp +++ b/src/lib/AST/ASTVisitor.cpp @@ -18,7 +18,6 @@ #include "lib/AST/TypeInfoBuilder.hpp" #include "lib/Support/Path.hpp" #include "lib/Support/Debug.hpp" -#include "lib/Support/Glob.hpp" #include "lib/Lib/Diagnostics.hpp" #include #include @@ -34,7 +33,6 @@ #include #include #include -#include #include #include #include @@ -46,8 +44,8 @@ namespace clang::mrdocs { ASTVisitor:: ASTVisitor( - const ConfigImpl& config, - Diagnostics& diags, + ConfigImpl const& config, + Diagnostics const& diags, CompilerInstance& compiler, ASTContext& context, Sema& sema) noexcept @@ -160,7 +158,7 @@ ASTVisitor:: traverse(UsingDirectiveDecl* D) { // Find the parent namespace - ScopeExitRestore s1(mode_, ExtractionMode::Dependency); + ScopeExitRestore s1(mode_, TraversalMode::Dependency); Decl* P = getParent(D); MRDOCS_SYMBOL_TRACE(P, context_); Info* PI = findOrTraverse(P); @@ -171,7 +169,7 @@ traverse(UsingDirectiveDecl* D) // Find the nominated namespace Decl* ND = D->getNominatedNamespace(); MRDOCS_SYMBOL_TRACE(ND, context_); - ScopeExitRestore s2(mode_, ExtractionMode::Dependency); + ScopeExitRestore s2(mode_, TraversalMode::Dependency); Info* NDI = findOrTraverse(ND); MRDOCS_CHECK_OR(NDI, nullptr); @@ -205,29 +203,35 @@ traverseMembers(InfoTy& I, DeclTy* DC) std::derived_from) { // We only need members of regular symbols and see-below namespaces - // - SeeBelow symbols (that are not namespaces) are only the - // symbol and the documentation. - // - ImplementationDefined symbols have no pages - // - Dependency symbols only need the names - MRDOCS_CHECK_OR( - I.Extraction == ExtractionMode::Regular || - I.Extraction == ExtractionMode::SeeBelow); + // - If symbol is SeeBelow we want the members if it's a namespace MRDOCS_CHECK_OR( I.Extraction != ExtractionMode::SeeBelow || I.Kind == InfoKind::Namespace); - // Set the context for the members - ScopeExitRestore s(mode_, I.Extraction); + // - If symbol is a Dependency, we only want the members if + // the traversal mode is BaseClass + MRDOCS_CHECK_OR( + I.Extraction != ExtractionMode::Dependency || + mode_ == TraversalMode::BaseClass); + + // - If symbol is ImplementationDefined, we only want the members if + // the traversal mode is BaseClass + MRDOCS_CHECK_OR( + I.Extraction != ExtractionMode::ImplementationDefined || + mode_ == TraversalMode::BaseClass); - // There are many implicit declarations in the - // translation unit declaration, so we preemtively - // skip them here. + // There are many implicit declarations, especially in the + // translation unit declaration, so we preemtively skip them here. auto explicitMembers = std::ranges::views::filter(DC->decls(), [](Decl* D) { return !D->isImplicit() || isa(D); }); for (auto* D : explicitMembers) { + // No matter what happens in the process, we restore the + // traversal mode to the original mode for the next member + ScopeExitRestore s(mode_); + // Traverse the member traverse(D); } } @@ -259,12 +263,11 @@ traverseParents(InfoTy& I, DeclTy* DC) // Check if we haven't already extracted or started // to extract the parent scope - // Traverse the parent scope as a dependency if it // hasn't been extracted yet Info* PI = nullptr; { - ScopeExitRestore s(mode_, ExtractionMode::Dependency); + ScopeExitRestore s(mode_, Dependency); if (PI = findOrTraverse(PD); !PI) { return; @@ -534,116 +537,6 @@ populateInfoBases(InfoTy& I, bool const isNew, DeclTy* D) } } - // If a symbol is extracted as a new dependency, check if - // the symbol passes the include filters and already promote - // it to regular. - if (isNew && - I.Extraction == ExtractionMode::Dependency) - { - // Try an exact match here - auto qualifiedName = this->qualifiedName(D); - if (checkSymbolFiltersImpl(std::string_view(qualifiedName.str())) && - checkFileFilters(D)) - { - I.Extraction = ExtractionMode::Regular; - // default mode also becomes regular for its - // members - mode_ = ExtractionMode::Regular; - } - } - - // Determine the reason why the symbol is being extracted - if (I.Extraction == ExtractionMode::Regular || - I.Extraction == ExtractionMode::SeeBelow) - { - auto matchQualifiedName = - [qualifiedName = this->qualifiedName(D)] - (SymbolGlobPattern const& pattern) - { - std::string_view const qualifiedNameSV = qualifiedName.str(); - return pattern.match(std::string_view(qualifiedNameSV)); - }; - - // Promote the extraction mode to SeeBelow if the symbol - // matches any of the patterns or if any of its parents - // are being extracted in SeeBelow mode - if (I.Extraction == ExtractionMode::Regular && - !config_->seeBelow.empty()) - { - if (std::ranges::any_of(config_->seeBelow, matchQualifiedName)) - { - I.Extraction = ExtractionMode::SeeBelow; - } - else if (I.Parent) - { - Info const* PI = find(I.Parent); - while (PI) - { - if (PI->Extraction == ExtractionMode::SeeBelow) - { - I.Extraction = ExtractionMode::SeeBelow; - break; - } - PI = find(PI->Parent); - } - } - else - { - auto* PD = getParent(D); - Info const* PI = findOrTraverse(PD); - while (PI) - { - if (PI->Extraction == ExtractionMode::SeeBelow) - { - I.Extraction = ExtractionMode::SeeBelow; - break; - } - PD = getParent(PD); - PI = find(PD); - } - } - } - - // Promote the extraction mode to ImplementationDefined if the symbol - // matches any of the patterns or if any of its parents - // are being extracted in ImplementationDefined mode - if (!config_->implementationDefined.empty()) - { - if (std::ranges::any_of(config_->implementationDefined, matchQualifiedName)) - { - I.Extraction = ExtractionMode::ImplementationDefined; - } - else if (I.Parent) - { - Info const* PI = find(I.Parent); - while (PI) - { - if (PI->Extraction == ExtractionMode::ImplementationDefined) - { - I.Extraction = ExtractionMode::ImplementationDefined; - break; - } - PI = find(PI->Parent); - } - } - else - { - auto* PD = getParent(D); - Info const* PI = findOrTraverse(PD); - while (PI) - { - if (PI->Extraction == ExtractionMode::ImplementationDefined) - { - I.Extraction = ExtractionMode::ImplementationDefined; - break; - } - PD = getParent(PD); - PI = find(PD); - } - } - } - } - // All other information is redundant if the symbol is not new MRDOCS_CHECK_OR(isNew); @@ -773,7 +666,7 @@ populate( } QualType const BT = B.getType(); - auto BaseType = toTypeInfo(BT); + auto BaseType = toTypeInfo(BT, BaseClass); // CXXBaseSpecifier::getEllipsisLoc indicates whether the // base was a pack expansion; a PackExpansionType is not built @@ -873,7 +766,7 @@ populate( I.IsDeleted |= FD->isDeleted(); I.IsDeletedAsWritten |= FD->isDeletedAsWritten(); I.IsNoReturn |= FD->isNoReturn(); - I.HasOverrideAttr |= FD->template hasAttr(); + I.HasOverrideAttr |= FD->hasAttr(); if (ConstexprSpecKind const CSK = FD->getConstexprKind(); CSK != ConstexprSpecKind::Unspecified) @@ -886,7 +779,7 @@ populate( I.StorageClass = toStorageClassKind(SC); } - I.IsNodiscard |= FD->template hasAttr(); + I.IsNodiscard |= FD->hasAttr(); I.IsExplicitObjectMemberFunction |= FD->hasCXXExplicitFunctionObjectParameter(); // @@ -901,7 +794,7 @@ populate( I.IsConst |= MD->isConst(); I.IsVolatile |= MD->isVolatile(); I.RefQualifier = toReferenceKind(MD->getRefQualifier()); - I.IsFinal |= MD->template hasAttr(); + I.IsFinal |= MD->hasAttr(); //MD->isCopyAssignmentOperator() //MD->isMoveAssignmentOperator() //MD->isOverloadedOperator(); @@ -1210,10 +1103,9 @@ populate( I.Qualifier = toNameInfo(D->getQualifier()); for (UsingShadowDecl const* UDS: D->shadows()) { - ScopeExitRestore s(mode_, ExtractionMode::Dependency); + ScopeExitRestore s(mode_, Dependency); Decl* S = UDS->getTargetDecl(); - Info* SI = findOrTraverse(S); - if (SI) + if (Info* SI = findOrTraverse(S)) { I.UsingSymbols.emplace_back(SI->id); } @@ -1629,6 +1521,27 @@ extractName(DeclarationName const N) return result; } +SmallString<256> +ASTVisitor:: +qualifiedName(Decl const* D) const +{ + if (auto* ND = dyn_cast(D)) + { + return qualifiedName(ND); + } + return {}; +} + +SmallString<256> +ASTVisitor:: +qualifiedName(NamedDecl const* ND) const +{ + SmallString<256> name; + llvm::raw_svector_ostream stream(name); + getQualifiedName(ND, stream, context_.getPrintingPolicy()); + return name; +} + bool ASTVisitor:: generateJavadoc( @@ -1647,7 +1560,7 @@ generateJavadoc( std::unique_ptr ASTVisitor:: -toTypeInfo(QualType const qt) +toTypeInfo(QualType const qt, TraversalMode const mode) { MRDOCS_SYMBOL_TRACE(qt, context_); @@ -1655,7 +1568,7 @@ toTypeInfo(QualType const qt) // For library types, can be proved wrong and the Info type promoted // to a regular type later on if the type matches the regular // extraction criteria - ScopeExitRestore s(mode_, ExtractionMode::Dependency); + ScopeExitRestore s(mode_, mode); // Build the TypeInfo representation for the type TypeInfoBuilder Builder(*this); @@ -1674,7 +1587,7 @@ toNameInfo( } MRDOCS_SYMBOL_TRACE(NNS, context_); - ScopeExitRestore scope(mode_,ExtractionMode::Dependency); + ScopeExitRestore scope(mode_, Dependency); std::unique_ptr I = nullptr; if (const Type* T = NNS->getAsType()) { @@ -1763,7 +1676,7 @@ toNameInfo( { return nullptr; } - ScopeExitRestore scope(mode_, ExtractionMode::Dependency); + ScopeExitRestore scope(mode_, Dependency); auto* ID = getInstantiatedFrom(D); if (Info const* info = findOrTraverse(const_cast(ID))) { @@ -2384,9 +2297,9 @@ tryGetTemplateArgument( return std::nullopt; } -bool +ExtractionMode ASTVisitor:: -shouldExtract( +checkFilters( const Decl* D, AccessSpecifier const access) { @@ -2394,56 +2307,30 @@ shouldExtract( // global namespace. It can't fail any of the filters // because its qualified name is represented by the // empty string, and it has no file associated with it. - MRDOCS_CHECK_OR(!isa(D), true); + MRDOCS_CHECK_OR(!isa(D), ExtractionMode::Regular); // Check if this kind of symbol should be extracted. - // This filters symbols supported by mrdocs and + // This filters symbols supported by MrDocs and // symbol types whitelisted in the configuration, // such as private members and anonymous namespaces. - MRDOCS_CHECK_OR(checkTypeFilters(D, access), false); - - // In dependency mode, we don't need the file and symbol - // filters because this is a dependency of another - // declaration that passes the filters. - if (mode_ == ExtractionMode::Dependency) - { - // If the whole declaration is implicit, we should - // not promote the extraction mode to regular - // even if it passes the filters. We should - // extract `D` in dependency mode so that - // its symbol ID is available but there's - // no need to extract its members. - if (isAllImplicit(D)) - { - return true; - } - - // So, the filters are used to determine if we - // should upgrade the extraction mode already. - // This is not a scoped promotion because - // parents and members should also assume - // the same base extraction mode. - if (checkSymbolFilters(D) && - checkFileFilters(D)) - { - mode_ = ExtractionMode::Regular; - } - // But we return true either way - return true; - } + MRDOCS_CHECK_OR(checkTypeFilters(D, access), ExtractionMode::Dependency); // Check if this symbol should be extracted according // to its qualified name. This checks if it matches // the symbol patterns and if it's not excluded. - MRDOCS_CHECK_OR(checkSymbolFilters(D), false); + auto const [Cat, Kind] = checkSymbolFilters(D); + if (Cat == ExtractionMode::Dependency) + { + return Cat; + } // Check if this symbol should be extracted according // to its location. This checks if it's in one of the // input directories, if it matches the file patterns, // and it's not in an excluded file. - MRDOCS_CHECK_OR(checkFileFilters(D), false); + MRDOCS_CHECK_OR(checkFileFilters(D), ExtractionMode::Dependency); - return true; + return Cat; } bool @@ -2477,7 +2364,7 @@ checkTypeFilters(Decl const* D, AccessSpecifier access) // KRYSTIAN FIXME: is this correct? a namespace should not // be extracted as a dependency (until namespace aliases and // using directives are supported) - MRDOCS_CHECK_OR(mode_ == ExtractionMode::Regular, false); + MRDOCS_CHECK_OR(mode_ == TraversalMode::Regular, false); } return true; @@ -2563,44 +2450,244 @@ checkFileFilters(std::string_view const symbolPath) const return true; } -bool +ASTVisitor::ExtractionInfo ASTVisitor:: -checkSymbolFilters(Decl const* D) const +checkSymbolFilters(Decl const* D, bool AllowParent) { + // Use the cache + if (auto const it = extraction_.find(D); it != extraction_.end()) + { + return it->second; + } + + // Update cache + auto updateCache = [this, D](ExtractionInfo const result) { + extraction_.emplace(D, result); + }; + // If not a NamedDecl, then symbol filters don't apply const auto* ND = dyn_cast(D); - MRDOCS_CHECK_OR(ND, true); - return checkSymbolFilters(ND, isa(D)); -} + if (!ND) + { + ExtractionInfo const res{ExtractionMode::Regular, ExtractionMatchType::Strict}; + updateCache(res); + return res; + } -bool -ASTVisitor:: -checkSymbolFilters(NamedDecl const* ND, bool const isScope) const -{ + // Get the symbol name SmallString<256> const name = qualifiedName(ND); - return checkSymbolFilters(name.str(), isScope); -} + auto const symbolName = name.str(); -bool -ASTVisitor:: -checkSymbolFilters(std::string_view const symbolName, bool const isScope) const -{ - if (isScope) + // We should check the exclusion filters first. If a symbol is + // explicitly excluded, there's nothing else to check. + if (!config_->excludeSymbols.empty() && + checkSymbolFiltersImpl(config_->excludeSymbols, symbolName)) { - return checkSymbolFiltersImpl(symbolName); + ExtractionInfo const res{ExtractionMode::Dependency, ExtractionMatchType::Strict}; + updateCache(res); + return res; } - return checkSymbolFiltersImpl(symbolName); + + // If not excluded, we should check the filters in this order: + // - implementation-defined + // - see-below + // - include-symbols + // These filters have precedence over each other. + std::array const patternsAndModes = { + std::make_pair(&config_->implementationDefined, ExtractionMode::ImplementationDefined), + std::make_pair(&config_->seeBelow, ExtractionMode::SeeBelow), + std::make_pair(&config_->includeSymbols, ExtractionMode::Regular) + }; + + // 1) The symbol strictly matches one of the patterns + for (auto const& [patterns, mode] : patternsAndModes) + { + if (!patterns->empty() && + checkSymbolFiltersImpl(*patterns, symbolName)) + { + ExtractionInfo res = {mode, ExtractionMatchType::Strict}; + updateCache(res); + return res; + } + } + + // 2) A namespace where the symbol is defined matches one of the + // literal patterns in `include-symbols`. + // For instance, if the literal pattern `std` is in `include-symbols`, + // then `std::filesystem::path::iterator` is extracted even though + // the pattern only matches `std`. + // In other words, because `std` is a namespace and `std` is a + // literal pattern, it matches all symbols in the `std` namespace + // and its subnamespaces as if the pattern were `std::**`. + // 2a) Check if there are any literal patterns in the filters. + // This is an optimization to avoid checking the parent namespaces + // if there are no literal patterns in the filters. + bool const containsLiteralPatterns = std::ranges::any_of(patternsAndModes, + [&](auto const& v) + { + auto& [patterns, mode] = v; + return std::ranges::any_of(*patterns, [](auto const& pattern) + { + return pattern.isLiteral(); + }); + }); + if (containsLiteralPatterns) + { + // 2b) For each parent namespace + Decl const* Cur = getParent(D); + while (Cur) + { + if (isa(Cur)) + { + // 2c) Check if it matches any literal pattern + SmallString<256> const namespaceName = qualifiedName(Cur); + for (auto const& [patterns, mode] : patternsAndModes) + { + if (!patterns->empty() && + checkSymbolFiltersImpl(*patterns, namespaceName.str())) + { + ExtractionInfo const res = {mode, ExtractionMatchType::LiteralParent}; + updateCache(res); + return res; + } + } + } + Cur = getParent(Cur); + } + } + + // 3) Child symbols imply this symbol should be included + // If symbol is a namespace, the namespace is the parent of a symbol that matches + // one of the patterns in the filters. + // For instance, if `std::filesystem::*` is in `include-symbols`, then `std` + // and `std::filesystem` are extracted even though `std::` and `std::filesystem::` + // only match the prefix of the pattern. + // In other words, including `std::filesystem::*` implies `std` and `std::filesystem` + // should be included. + // We evaluate this rule in the reverse order of precedence of the filters + // because, for instance, if a namespace matches as a prefix for `include-symbol` + // and `implementation-defined`, we should extract it as `include-symbol`, + // since symbols that only pass `include-symbol` will also be included in this namespace + // later on. + if (isa(D) || isa(D)) + { + SmallString<256> symbolAsPrefix{ symbolName }; + symbolAsPrefix += "::"; + for (auto const& [patterns, mode] : std::ranges::views::reverse(patternsAndModes)) + { + if (!patterns->empty() && + checkSymbolFiltersImpl(*patterns, symbolAsPrefix.str())) + { + // We know this namespace matches one of the pattern + // prefixes that can potentially include children, but + // we have to check if any children actually matches + // the pattern strictly. + DeclContext const* DC = cast(D); + auto childrenMode = ExtractionMode::Dependency; + for (auto* M : DC->decls()) + { + if (M->isImplicit() && !isa(M)) + { + // Ignore implicit members + continue; + } + if (getParent(M) != D) + { + // Not a semantic member + continue; + } + auto const R = checkSymbolFilters(M, false); + if (R.mode == ExtractionMode::Dependency) + { + // The child should not be extracted. + // Go to next child. + continue; + } + if (childrenMode == ExtractionMode::Dependency) + { + // Still a dependency. Initialize it with child mode. + childrenMode = R.mode; + } + else + { + // Children mode already initialized. Get the least specific one. + childrenMode = leastSpecific(childrenMode, R.mode); + } + if (childrenMode == ExtractionMode::Regular) + { + // Already the least specific + break; + } + } + if (childrenMode != ExtractionMode::Dependency) + { + ExtractionInfo const res = {mode, ExtractionMatchType::Prefix}; + updateCache(res); + return res; + } + } + } + } + else if (AllowParent) + { + Decl const* P = getParent(D); + if (P) + { + // 4) Parent symbols imply this symbol should be included + // If the first record, enum, or namespace parent of the symbol + // matches one of the patterns, we extract the symbol in the same mode. + // For instance, if `std::*` is in `include-symbols`, then + // `std::vector::iterator` is extracted even though the + // pattern only matches `std::vector`. + // In other words, including `std::vector` implies + // `std::vector::iterator` should be included. + // This operates recursively, which will already update + // the cache with the proper extraction mode for this parent. + if (auto const [mode, kind] = checkSymbolFilters(P); + mode != ExtractionMode::Dependency && + kind != ExtractionMatchType::Prefix) + { + // The parent is being extracted and the reason + // is not because it's a prefix. + // When it's a prefix, the parent is only + // being extracted so that symbols that match + // the full pattern are included and not all symbols. + ExtractionInfo const res = {mode, ExtractionMatchType::StrictParent}; + updateCache(res); + return res; + } + } + } + + // 4) It doesn't match any of the filters + // 4a) If this happened because there are no include-symbol + // filters, we assume the `include-symbol` works as if + // `**` is included instead of nothing being included. + // Thus, we should extract the symbol. + if (config_->includeSymbols.empty()) + { + constexpr ExtractionInfo res = {ExtractionMode::Regular, ExtractionMatchType::Strict}; + updateCache(res); + return res; + } + // 4b) Otherwise, we don't extract the symbol + // because it doesn't match any of `include-symbol` filters + constexpr ExtractionInfo res = {ExtractionMode::Dependency, ExtractionMatchType::Strict}; + updateCache(res); + return res; } -template +template bool ASTVisitor:: -checkSymbolFiltersImpl(std::string_view const symbolName) const +checkSymbolFiltersImpl( + std::vector const& patterns, + std::string_view const symbolName) const { // Don't extract declarations that fail the symbol filter auto includeMatchFn = [&](SymbolGlobPattern const& pattern) { - if constexpr (isScope) + if constexpr (t == SymbolCheckType::PrefixOnly) { // If the symbol is a scope, such as a namespace or class, // we want to know if symbols in that scope might match @@ -2612,51 +2699,22 @@ checkSymbolFiltersImpl(std::string_view const symbolName) const // symbol pattern for the escope. return pattern.matchPatternPrefix(symbolName); } - else + else if constexpr (t == SymbolCheckType::Literal) + { + return pattern.isLiteral() && pattern.match(symbolName); + } + else if constexpr (t == SymbolCheckType::Strict) { + // Strict match return pattern.match(symbolName); } }; MRDOCS_CHECK_OR( - config_->includeSymbols.empty() || - std::ranges::any_of(config_->includeSymbols, includeMatchFn), false); - - // Don't extract declarations that fail the exclude symbol filter - auto excludeMatchFn = [&](SymbolGlobPattern const& pattern) - { - // Unlike the include filter, we want to match the entire symbol name - // for the exclude filter regardless of whether the symbol is a scope. - // If the scope is explicitly excluded, we already know we want to - // exclude all symbols in that scope - return pattern.match(symbolName); - }; - MRDOCS_CHECK_OR( - config_->excludeSymbols.empty() || - std::ranges::none_of(config_->excludeSymbols, excludeMatchFn), false); + std::ranges::any_of(patterns, includeMatchFn), false); return true; } -SmallString<256> -ASTVisitor:: -qualifiedName(Decl const* D) const -{ - if (auto* ND = dyn_cast(D)) - { - return qualifiedName(ND); - } - return {}; -} - -SmallString<256> -ASTVisitor:: -qualifiedName(NamedDecl const* ND) const -{ - SmallString<256> name; - llvm::raw_svector_ostream stream(name); - getQualifiedName(ND, stream, context_.getPrintingPolicy()); - return name; -} Info* ASTVisitor:: @@ -2832,7 +2890,9 @@ upsert(SymbolID const& id) { info = info_.emplace(std::make_unique< InfoTy>(id)).first->get(); - info->Extraction = mostSpecific(info->Extraction, mode_); + auto const minExtract = mode_ == TraversalMode::Regular ? + ExtractionMode::Regular : ExtractionMode::Dependency; + info->Extraction = mostSpecific(info->Extraction, minExtract); } MRDOCS_ASSERT(info->Kind == InfoTy::kind_id); return {static_cast(*info), isNew}; @@ -2850,10 +2910,20 @@ Expected< ASTVisitor:: upsert(DeclType* D) { - AccessSpecifier access = getAccess(D); - if (!shouldExtract(D, access)) + ExtractionMode const m = checkFilters(D); + if (m == ExtractionMode::Dependency) { - return Unexpected(Error("Symbol should not be extracted")); + if (mode_ == Regular) + { + return Unexpected(Error("Symbol should not be extracted")); + } + if (!isAllImplicit(D)) + { + // We should not extract explicit declarations in dependency mode. + // The calling code should handle this case instead of + // populating the symbol table with instantiations. + return Unexpected(Error("Explicit declaration in dependency mode")); + } } SymbolID const id = generateID(D); @@ -2864,22 +2934,13 @@ upsert(DeclType* D) InfoTypeFor_t, InfoTy>; auto [I, isNew] = upsert(id); - I.Access = toAccessKind(access); - // If the symbol was previously extracted as a dependency - // and is now being extracted as a regular symbol because - // it passed the more constrained filters, update the - // extraction mode and set the symbol as new so it's populated - // this time. - bool const previouslyExtractedAsDependency = - !isNew && - mode_ != ExtractionMode::Dependency && - I.Extraction == ExtractionMode::Dependency; - if (previouslyExtractedAsDependency) - { - I.Extraction = mostSpecific(I.Extraction, mode_); - isNew = true; - } + // Already populate the extraction mode + I.Extraction = mostSpecific(I.Extraction, m); + + // Already populate the access specifier + AccessSpecifier const access = getAccess(D); + I.Access = toAccessKind(access); return upsertResult{std::ref(I), isNew}; } diff --git a/src/lib/AST/ASTVisitor.hpp b/src/lib/AST/ASTVisitor.hpp index fec2b9aee3..eee76ca488 100644 --- a/src/lib/AST/ASTVisitor.hpp +++ b/src/lib/AST/ASTVisitor.hpp @@ -116,11 +116,100 @@ class ASTVisitor if a file should be extracted or to add the SourceInfo to an Info object. */ - std::unordered_map files_; + std::unordered_map files_; - /* The current extraction mode + /* Determine how a Decl matched the filters + */ + enum class ExtractionMatchType { + // It matches one of the patterns as is + Strict, + // It matches the prefix of a pattern, meaning + // children of this symbol might be included + Prefix, + // A parent of this symbol matches a literal pattern, + // meaning `parent::**`. + LiteralParent, + // The symbol is included because the parent is + // included and the parent is not a prefix. + StrictParent + }; + + /* Extraction Info + + This struct is used to store information about + the filters a Decl pass. + */ + struct ExtractionInfo + { + // The extraction mode for this symbol + ExtractionMode mode; + + // Whether this is extraction mode is due to + // it matching a prefix of another symbol. + ExtractionMatchType kind; + }; + + /* A map of Clang Decl objects to ExtractionMode values + + This map is used to store the extraction mode for + declarations that have been identified through the + translation unit AST. + + This map is later used by the @ref shouldExtract + function to determine if a declaration should be + extracted based on the extraction mode. + */ + std::unordered_map extraction_; + + /* How we should traverse the current node + */ + enum TraversalMode { + /* Only symbols that pass the filters will be extracted + + Excluded symbols are not traversed. All other symbols + are traversed and their appropriate ExtractionMode + is determined. - This defines the extraction mode assigned to + Member of these symbols are also traversed. + + */ + Regular, + + /* All symbols are extracted + + Even excluded symbols are traversed. If a sy bol + doesn't pass the filters, it is extracted as a + dependency. + + Members of these dependency symbols are not + traversed. + + This is used when an included symbol makes a + reference to an excluded symbol. + */ + Dependency, + + /* All symbols will be extracted and traversed + + This mode is used to extract all symbols, including + those that are excluded by the filters. These + excluded symbols are marked as dependencies. + + However, members of these dependency symbols are + also traversed. + + This is used when an included record makes use + of an excluded record as a base class. + In this case, we also need information about the + excluded base class members to populate the + derived class. + */ + BaseClass + }; + + /* The current traversal mode + + This defines the traversal mode assigned to new Info types. A symbol that passes the filters will be @@ -128,7 +217,7 @@ class ASTVisitor If a symbol also passes the see-below or implementation-defined filters, we - change the current extraction mode + change the current traversal mode accordingly so the symbol can be tagged. Members of this symbol types are not extracted. @@ -139,7 +228,7 @@ class ASTVisitor when another symbol makes a reference to it. */ - ExtractionMode mode_ = ExtractionMode::Regular; + TraversalMode mode_ = Regular; public: /** Constructor for ASTVisitor. @@ -158,8 +247,8 @@ class ASTVisitor @param sema The Sema object. */ ASTVisitor( - const ConfigImpl& config, - Diagnostics& diags, + ConfigImpl const& config, + Diagnostics const& diags, CompilerInstance& compiler, ASTContext& context, Sema& sema) noexcept; @@ -558,6 +647,12 @@ class ASTVisitor std::string extractName(DeclarationName N); + SmallString<256> + qualifiedName(Decl const* D) const; + + SmallString<256> + qualifiedName(NamedDecl const* ND) const; + /* Parse the comments above a declaration as Javadoc This function will parse the comments above a declaration @@ -573,7 +668,13 @@ class ASTVisitor Decl const* D); std::unique_ptr - toTypeInfo(QualType qt); + toTypeInfo(QualType qt, TraversalMode mode); + + std::unique_ptr + toTypeInfo(QualType qt) + { + return toTypeInfo(qt, TraversalMode::Dependency); + } std::unique_ptr toNameInfo( @@ -769,11 +870,16 @@ class ASTVisitor // Filters // ================================================= - /* Determine if a declaration should be extracted + /* Determine in what mode a declaration should be extracted This function will determine whether a declaration - should be extracted based on the current extraction - mode, and the current symbol filter state. + should be extracted and what extraction mode + applies to the symbol. + + If the symbol should not be extracted, the function + will return `ExtractionMode::Dependency`, meaning + we should only extract it if the traversal is in + dependency mode. The function filters private symbols, symbols outside the input files, and symbols in files that do not match @@ -785,24 +891,25 @@ class ASTVisitor @param D the declaration to check @param access the access specifier of the declaration - @return true if the declaration should be extracted, - and false otherwise. + @return Return the most specific extraction mode + possible for the declaration. */ - bool - shouldExtract(Decl const* D, AccessSpecifier access); + ExtractionMode + checkFilters(Decl const* D, AccessSpecifier access); static - bool - shouldExtract(TranslationUnitDecl const*, AccessSpecifier) + ExtractionMode + checkFilters(TranslationUnitDecl const*, AccessSpecifier) { - return true; + return ExtractionMode::Regular; } template DeclTy> - bool - shouldExtract(DeclTy const* D) + ExtractionMode + checkFilters(DeclTy const* D) { - return shouldExtract(D, getAccess(D)); + AccessSpecifier A = getAccess(D); + return checkFilters(D, A); } bool @@ -814,24 +921,45 @@ class ASTVisitor bool checkFileFilters(std::string_view symbolPath) const; - bool - checkSymbolFilters(Decl const* D) const; + /* Check all symbol filters for a declaration - bool - checkSymbolFilters(NamedDecl const* ND, bool const isScope) const; + @param D The declaration to check + @param AllowParent Whether a member declaration should + be allowed to inherit the value from the parent. + */ + ExtractionInfo + checkSymbolFilters(Decl const* D, bool AllowParent); - bool - checkSymbolFilters(std::string_view symbolName, bool const isScope) const; + ExtractionInfo + checkSymbolFilters(Decl const* D) + { + return checkSymbolFilters(D, true); + } - template - bool - checkSymbolFiltersImpl(std::string_view symbolName) const; + /* The strategy for checking filters in `checkSymbolFilters` + */ + enum SymbolCheckType { + // Check if the symbol matches one of the patterns as a whole + Strict, + // Check if the symbol matches the prefix of the patterns + // even if the symbol is not a whole match + PrefixOnly, + // Check if the pattern is literal and the symbol contains + // exactly the same contents. + Literal + }; - SmallString<256> - qualifiedName(Decl const* D) const; + /* Check if the symbol name matches one of the patterns + + This function checks if the symbol name matches one of the + patterns according to the strategy defined by `t`. + */ + template + bool + checkSymbolFiltersImpl( + std::vector const& patterns, + std::string_view symbolName) const; - SmallString<256> - qualifiedName(NamedDecl const* ND) const; // ================================================= // Element access diff --git a/src/lib/AST/ClangHelpers.cpp b/src/lib/AST/ClangHelpers.cpp index 21101853c3..529e31dd59 100644 --- a/src/lib/AST/ClangHelpers.cpp +++ b/src/lib/AST/ClangHelpers.cpp @@ -334,6 +334,12 @@ isAllImplicit(Decl const* D) { return false; } + if (auto const* TSD = dynamic_cast(D); + TSD && + TSD->isExplicitSpecialization()) + { + return false; + } auto const* P = getParent(D); return isAllImplicit(P); } diff --git a/src/lib/AST/ClangHelpers.hpp b/src/lib/AST/ClangHelpers.hpp index 835245c439..27b5fa8122 100644 --- a/src/lib/AST/ClangHelpers.hpp +++ b/src/lib/AST/ClangHelpers.hpp @@ -790,7 +790,7 @@ decayToPrimaryTemplate(Decl const* D); // Iterate the Decl and check if this is a template specialization // also considering the parent declarations. For instance, // S<0>::M<0> is a template specialization of S<0> and M<0>. -// This function returns true is both S<0> and M<0> are implicit +// This function returns true if both S<0> and M<0> are implicit // template specializations. MRDOCS_DECL bool diff --git a/src/lib/Metadata/Interface.cpp b/src/lib/Metadata/Interface.cpp index 62ff8387cd..e8af6d5a59 100644 --- a/src/lib/Metadata/Interface.cpp +++ b/src/lib/Metadata/Interface.cpp @@ -9,23 +9,24 @@ // Official repository: https://github.com/cppalliance/mrdocs // +#include "lib/Dom/LazyArray.hpp" #include "lib/Lib/ConfigImpl.hpp" #include "lib/Support/Debug.hpp" -#include "lib/Dom/LazyArray.hpp" -#include -#include -#include +#include "mrdocs/Support/ScopeExit.hpp" +#include +#include +#include #include #include +#include #include #include +#include #include #include #include #include -#include -#include -#include +#include namespace clang { namespace mrdocs { @@ -42,6 +43,7 @@ class TrancheBuilder Tranche* private_; bool includePrivate_ = true; + bool buildingFromBase_ = false; Tranche* trancheFor(AccessKind access) { @@ -86,10 +88,43 @@ class TrancheBuilder push( ListTy Tranche::* list, AccessKind access, - const Info& member) + Info const& member) { - if(Tranche* tranche = trancheFor(access)) + if (Tranche* tranche = trancheFor(access)) + { push(tranche->*list, member); + } + } + + void + push( + std::vector& list, + AccessKind access, + FunctionInfo const& member) + { + // When adding a function to the tranche, we have to + // check if the function isn't already overriden by + // a function with the same name and signature. + if (Tranche* tranche = trancheFor(access)) + { + if (buildingFromBase_) + { + // Iterate members of list + for (auto const& id: list) + { + // Get the function info + auto const& I = corpus_.get(id); + // Check if the function is overriden or shadowed + // TODO: We need to improve the function signature comparison + if (I.Name == member.Name) + { + // The function is shadowed, we don't add it + return; + } + } + } + push(list, member); + } } static @@ -98,15 +133,21 @@ class TrancheBuilder AccessKind memberAccess, AccessKind baseAccess) noexcept { - if(memberAccess == AccessKind::None || + if (memberAccess == AccessKind::None || baseAccess == AccessKind::None) + { return AccessKind::None; - if(memberAccess == AccessKind::Private || + } + if (memberAccess == AccessKind::Private || baseAccess == AccessKind::Private) + { return AccessKind::Private; - if(memberAccess == AccessKind::Protected || + } + if (memberAccess == AccessKind::Protected || baseAccess == AccessKind::Protected) + { return AccessKind::Protected; + } return AccessKind::Public; } @@ -121,8 +162,11 @@ class TrancheBuilder const Info* lookThroughTypedefs(const Info* I) { - if(! I || ! I->isTypedef()) + if (!I || + !I->isTypedef()) + { return I; + } auto* TI = static_cast(I); return lookThroughTypedefs( corpus_.find(TI->Type->namedSymbol())); @@ -150,7 +194,7 @@ class TrancheBuilder void add( - const SymbolID& id, + SymbolID const& id, AccessKind baseAccess) { const auto& I = corpus_.get(id); @@ -158,69 +202,93 @@ class TrancheBuilder visit(I, *this, actualAccess); } + // Add members from RecordInfo to the tranches void addFrom( const RecordInfo& I, AccessKind baseAccess) { - for(auto const& B : I.Bases) + for (auto const& id: I.Members) + { + add(id, baseAccess); + } + // Add members from the base classes to the tranches + ScopeExitRestore s(buildingFromBase_, true); + for (auto const& B : I.Bases) { - auto actualAccess = effectiveAccess(baseAccess, B.Access); + auto const actualAccess = + effectiveAccess(baseAccess, B.Access); - if( ! includePrivate_ && + if (!includePrivate_ && actualAccess == AccessKind::Private) + { continue; - const Info* Base = lookThroughTypedefs( + } + Info const* Base = lookThroughTypedefs( corpus_.find(B.Type->namedSymbol())); - if(! Base || Base->id == I.id || - ! Base->isRecord()) + if (!Base || + Base->id == I.id || + !Base->isRecord()) + { continue; + } addFrom(*static_cast< const RecordInfo*>(Base), actualAccess); } - for(auto const& id : I.Members) - add(id, baseAccess); } - void addFrom(const NamespaceInfo& I) + // Add members from RecordInfo to the tranches + void + addFrom(const NamespaceInfo& I) { - for(auto const& id : I.Members) + for (auto const& id: I.Members) + { add(id, AccessKind::None); + } } - void operator()( + // Add a namespace to the tranche + void + operator()( const NamespaceInfo& I, AccessKind access) { push(&Tranche::Namespaces, access, I); } - void operator()( + // Add a record to the tranche + void + operator()( const RecordInfo& I, AccessKind access) { push(&Tranche::Records, access, I); } - void operator()( - const SpecializationInfo&, + void + operator()( + SpecializationInfo const&, AccessKind) { // KRYSTIAN FIXME: currently unimplemented } + // Add a function to the tranche void operator()( const FunctionInfo& I, - AccessKind access) + AccessKind const access) { // do not inherit constructors or destructors - if(parent_.isRecord() && ! isFromParent(I) && - (I.Class == FunctionClass::Constructor || - I.Class == FunctionClass::Destructor)) + if (parent_.isRecord() && !isFromParent(I) + && (I.Class == FunctionClass::Constructor + || I.Class == FunctionClass::Destructor)) + { return; + } - bool isStatic = I.StorageClass == StorageClassKind::Static; - if(! parent_.isRecord() || ! isStatic) + bool const isStatic = I.StorageClass == StorageClassKind::Static; + if (!parent_.isRecord() || + !isStatic) { push(&Tranche::Functions, access, I); push(&Tranche::Overloads, access, I); @@ -324,10 +392,14 @@ buildTranches( Private); visit(I, [&](const InfoTy& II) { - if constexpr(InfoTy::isRecord()) + if constexpr (InfoTy::isRecord()) + { builder.addFrom(II, AccessKind::Public); - if constexpr(InfoTy::isNamespace()) + } + if constexpr (InfoTy::isNamespace()) + { builder.addFrom(II); + } }); } diff --git a/src/lib/Support/Glob.cpp b/src/lib/Support/Glob.cpp index 52d06a216e..e67e49121d 100644 --- a/src/lib/Support/Glob.cpp +++ b/src/lib/Support/Glob.cpp @@ -467,6 +467,90 @@ match(std::string_view str, char const delimiter) const } return MatchType::PARTIAL; } + +/* Check if a character at a given position is escaped. + + @param pattern The glob pattern. + @param pos The position of the character to check. + @return true if the character is escaped, false otherwise. + */ +bool isEscaped(std::string_view pattern, std::size_t pos) +{ + if (pos == 0) + { + return false; + } + std::size_t backslashCount = 0; + while (pos > 0 && pattern[--pos] == '\\') + { + ++backslashCount; + } + return backslashCount % 2 != 0; +} + +struct PrefixInfo +{ + // The unescaped prefix + std::string prefix; + + // The encoded prefix size + std::size_t prefixSize; +}; + + +/* Extract the prefix of a glob pattern up to the first non-escaped metacharacter. + + @param pattern The glob pattern. + @return The prefix of the pattern. + */ +PrefixInfo +extractPrefix(std::string_view pattern) +{ + PrefixInfo result; + std::size_t pos = 0; + + while (pos < pattern.size()) + { + // Find the first metacharacter or backslash + std::size_t const metacharPos = pattern.find_first_of("?*[{\\", pos); + + // Copy all literal characters up to the metacharacter + result.prefix.append(pattern.substr(pos, metacharPos - pos)); + pos = metacharPos; + + if (pos == std::string_view::npos) + { + // No more characters, we're done + result.prefixSize = pattern.size(); + return result; + } + + if (char const c = pattern[pos]; + c == '\\' && + pos + 1 < pattern.size()) + { + // Push the escaped character instead of the backslash + result.prefix += pattern[pos + 1]; + pos += 2; + } + else if (c == '?' || c == '*' || c == '[' || c == '{') + { + // If it's escaped, it should have been handled in the + // previous case. + MRDOCS_ASSERT(!isEscaped(pattern, pos)); + break; + } + else + { + // Handle a backslash that is not escaping anything + result.prefix += c; + ++pos; + } + } + result.prefixSize = pos; + return result; +} + } // (anon) struct GlobPattern::Impl { @@ -492,12 +576,12 @@ create( // Store the original pattern. res.impl_->pattern = std::string(pattern); - // Store the prefix that does not contain any metacharacter. - std::size_t const prefixSize = pattern.find_first_of("?*[{\\"); - res.impl_->prefix = pattern.substr(0, prefixSize); - if (prefixSize == std::string::npos) + // Store the pattern literal prefix. + auto [prefix, prefixSize] = extractPrefix(pattern); + res.impl_->prefix = std::move(prefix); + if (prefixSize == pattern.size()) { - // The pattern does not contain any metacharacter. + // The pattern does not contain any unescaped metacharacter. return res; } @@ -581,9 +665,9 @@ match(std::string_view const str, char const delimiter) const } bool -GlobPattern:: -matchPatternPrefix(std::string_view const str, char const delimiter) const -{ +GlobPattern::matchPatternPrefix( + std::string_view const str, + char const delimiter) const { if (!impl_) { return str.empty(); @@ -604,7 +688,8 @@ matchPatternPrefix(std::string_view const str, char const delimiter) const for (auto& subGlob: impl_->subGlobs) { if (auto m = subGlob.match(suffix, delimiter); - m == SubGlobPattern::MatchType::FULL || m == SubGlobPattern::MatchType::PARTIAL) + m == SubGlobPattern::MatchType::FULL + || m == SubGlobPattern::MatchType::PARTIAL) { return true; } @@ -612,6 +697,16 @@ matchPatternPrefix(std::string_view const str, char const delimiter) const return false; } +bool +GlobPattern::isLiteral() const +{ + if (!impl_) + { + return true; + } + return impl_->subGlobs.empty(); +} + std::string_view GlobPattern:: pattern() const diff --git a/src/test/Support/Glob.cpp b/src/test/Support/Glob.cpp index 701fd9a875..be83be4fdb 100644 --- a/src/test/Support/Glob.cpp +++ b/src/test/Support/Glob.cpp @@ -384,10 +384,9 @@ struct Glob_test BOOST_TEST_NOT(glob.match("aab")); } - // invalid + // stray \ becomes part of the literal prefix { - // stray \ at the end - BOOST_TEST_NOT(PathGlobPattern::create("a\\")); + BOOST_TEST(PathGlobPattern::create("a\\")); } } @@ -551,6 +550,58 @@ struct Glob_test BOOST_TEST_NOT(glob.matchPatternPrefix("std")); } } + + // isLiteral + { + // default constructed to empty string + { + PathGlobPattern glob; + BOOST_TEST(glob.isLiteral()); + BOOST_TEST(glob.match("")); + BOOST_TEST_NOT(glob.match("a")); + } + + // empty string + { + auto globExp = PathGlobPattern::create(""); + BOOST_TEST(globExp); + PathGlobPattern const& glob = *globExp; + BOOST_TEST(glob.isLiteral()); + BOOST_TEST(glob.match("")); + BOOST_TEST_NOT(glob.match("a")); + } + + // literal string + { + auto globExp = PathGlobPattern::create("abc"); + BOOST_TEST(globExp); + PathGlobPattern const& glob = *globExp; + BOOST_TEST(glob.isLiteral()); + BOOST_TEST(glob.match("abc")); + BOOST_TEST_NOT(glob.match("abcd")); + BOOST_TEST_NOT(glob.match("a/b/c")); + } + + // literal string with escaped characters + { + auto globExp = PathGlobPattern::create("a\\*b"); + BOOST_TEST(globExp); + PathGlobPattern const& glob = *globExp; + BOOST_TEST(glob.isLiteral()); + BOOST_TEST(glob.match("a*b")); + BOOST_TEST_NOT(glob.match("aab")); + } + + // literal string with all special characters escaped + { + auto globExp = PathGlobPattern::create("a\\*\\?\\[\\{\\}\\^\\!\\-\\]\\c"); + BOOST_TEST(globExp); + PathGlobPattern const& glob = *globExp; + BOOST_TEST(glob.isLiteral()); + BOOST_TEST(glob.match("a*?[{}^!-]c")); + BOOST_TEST_NOT(glob.match("a")); + } + } } }; diff --git a/test-files/golden-tests/filters/symbol-name/excluded-base-class.adoc b/test-files/golden-tests/filters/symbol-name/excluded-base-class.adoc new file mode 100644 index 0000000000..24f0bb85d6 --- /dev/null +++ b/test-files/golden-tests/filters/symbol-name/excluded-base-class.adoc @@ -0,0 +1,166 @@ += Reference +:mrdocs: + +[#index] +== Global namespace + + +=== Namespaces + +[cols=1] +|=== +| Name + +| <> +|=== + +[#A] +== A + + +=== Types + +[cols=1] +|=== +| Name + +| <> +| <> +|=== + +[#A-D] +== <>::D + + +=== Synopsis + + +Declared in `<excluded‐base‐class.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +class D + : public B::C; +---- + +=== Member Functions + +[cols=1] +|=== +| Name + +| <> [.small]#[virtual]# +| <> [.small]#[virtual]# +| <> +|=== + + + +[#A-D-g] +== <>::<>::g + + +=== Synopsis + + +Declared in `<excluded‐base‐class.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +virtual +void +g() override; +---- + +[#A-D-h] +== <>::<>::h + + +=== Synopsis + + +Declared in `<excluded‐base‐class.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +virtual +void +h() override; +---- + +[#A-D-i] +== <>::<>::i + + +=== Synopsis + + +Declared in `<excluded‐base‐class.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +i(); +---- + +[#A-E] +== <>::E + + +=== Synopsis + + +Declared in `<excluded‐base‐class.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +class E + : public B::C; +---- + +=== Member Functions + +[cols=1] +|=== +| Name + +| <> [.small]#[virtual]# +| <> +|=== + + + +[#A-E-h] +== <>::<>::h + + +=== Synopsis + + +Declared in `<excluded‐base‐class.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +virtual +void +h() override; +---- + +[#A-E-i] +== <>::<>::i + + +=== Synopsis + + +Declared in `<excluded‐base‐class.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +i(); +---- + + + +[.small]#Created with https://www.mrdocs.com[MrDocs]# diff --git a/test-files/golden-tests/filters/symbol-name/excluded-base-class.cpp b/test-files/golden-tests/filters/symbol-name/excluded-base-class.cpp new file mode 100644 index 0000000000..7686f3f536 --- /dev/null +++ b/test-files/golden-tests/filters/symbol-name/excluded-base-class.cpp @@ -0,0 +1,32 @@ +namespace A { + // This namespace and everything in it should be excluded + namespace B { + class C { + public: + void f(); + virtual void g(); + virtual void h() = 0; + }; + } + + // B::C and its members will be extracted as + // dependencies so that this information can + // be used by D. + class D : public B::C { + public: + void g() override; + void h() override; + void i(); + }; + + // B::C and its members will be extracted as + // dependencies so that this information can + // be used by D. + // g() is not defined and is only inherited + // from B::C + class E : public B::C { + public: + void h() override; + void i(); + }; +} diff --git a/test-files/golden-tests/filters/symbol-name/excluded-base-class.html b/test-files/golden-tests/filters/symbol-name/excluded-base-class.html new file mode 100644 index 0000000000..48c8f571c9 --- /dev/null +++ b/test-files/golden-tests/filters/symbol-name/excluded-base-class.html @@ -0,0 +1,195 @@ + + +Reference + + +
+

Reference

+
+
+

Global namespace

+
+

Namespaces

+ + + + + + + + + + +
Name
A
+
+
+
+

A

+
+

Types

+ + + + + + + + + + + +
Name
D
E
+
+
+
+

A::D

+
+
+

Synopsis

+
+Declared in <excluded-base-class.cpp>
+
+
+class D
+    : public B::C;
+
+
+
+

Member Functions

+ + + + + + + + + + + + +
Name
g [virtual]
h [virtual]
i
+ + +
+
+
+

A::D::g

+
+
+

Synopsis

+
+Declared in <excluded-base-class.cpp>
+
+
+virtual
+void
+g() override;
+
+
+
+
+
+
+

A::D::h

+
+
+

Synopsis

+
+Declared in <excluded-base-class.cpp>
+
+
+virtual
+void
+h() override;
+
+
+
+
+
+
+

A::D::i

+
+
+

Synopsis

+
+Declared in <excluded-base-class.cpp>
+
+
+void
+i();
+
+
+
+
+
+
+

A::E

+
+
+

Synopsis

+
+Declared in <excluded-base-class.cpp>
+
+
+class E
+    : public B::C;
+
+
+
+

Member Functions

+ + + + + + + + + + + +
Name
h [virtual]
i
+ + +
+
+
+

A::E::h

+
+
+

Synopsis

+
+Declared in <excluded-base-class.cpp>
+
+
+virtual
+void
+h() override;
+
+
+
+
+
+
+

A::E::i

+
+
+

Synopsis

+
+Declared in <excluded-base-class.cpp>
+
+
+void
+i();
+
+
+
+
+ +
+
+

Created with MrDocs

+
+ + \ No newline at end of file diff --git a/test-files/golden-tests/filters/symbol-name/excluded-base-class.xml b/test-files/golden-tests/filters/symbol-name/excluded-base-class.xml new file mode 100644 index 0000000000..884ad6ea23 --- /dev/null +++ b/test-files/golden-tests/filters/symbol-name/excluded-base-class.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test-files/golden-tests/filters/symbol-name/excluded-base-class.yml b/test-files/golden-tests/filters/symbol-name/excluded-base-class.yml new file mode 100644 index 0000000000..22984b9c9a --- /dev/null +++ b/test-files/golden-tests/filters/symbol-name/excluded-base-class.yml @@ -0,0 +1,3 @@ +exclude-symbols: + - 'A::B' + - 'A::B::**' \ No newline at end of file diff --git a/test-files/golden-tests/filters/symbol-name/whitelist_0.adoc b/test-files/golden-tests/filters/symbol-name/whitelist_0.adoc index 3d270dc3da..cd941e9ab9 100644 --- a/test-files/golden-tests/filters/symbol-name/whitelist_0.adoc +++ b/test-files/golden-tests/filters/symbol-name/whitelist_0.adoc @@ -7,32 +7,54 @@ === Namespaces -[cols=1] +[cols=2] |=== -| Name +| Name | Description | <> +| This namespace should extracted because it's implied by `N0::f0_WL` + | <> +| This namespace should extracted because it's implied by `N1::N3_WL` and `N1::N4::f1_WL` + | <> +| This namespace should extracted because it's implied by `N5::N6::*7` + +|=== +=== Types + +[cols=2] +|=== +| Name | Description + +| <> +| This namespace should be included because it strictly matches `C` + |=== [#N0] == N0 +This namespace should extracted because it's implied by `N0::f0_WL` + === Functions -[cols=1] +[cols=2] |=== -| Name +| Name | Description | <> +| This function should be included because it matches `N0::f0_WL` + |=== [#N0-f0_WL] == <>::f0_WL +This function should be included because it matches `N0::f0_WL` + === Synopsis @@ -48,38 +70,79 @@ f0_WL(); == N1 +This namespace should extracted because it's implied by `N1::N3_WL` and `N1::N4::f1_WL` + === Namespaces -[cols=1] +[cols=2] |=== -| Name +| Name | Description | <> +| This namespace should extracted because it's explicitly included by `N1::N3_WL` + | <> +| This namespace should extracted because it's implied by `N1::N4::f1_WL` + |=== [#N1-N3_WL] == <>::N3_WL +This namespace should extracted because it's explicitly included by `N1::N3_WL` + +=== Functions + +[cols=2] +|=== +| Name | Description + +| <> +| This function should extracted because the namespace `N1::N3_WL` is included as a literal. + +|=== + +[#N1-N3_WL-f1_WL] +== <>::<>::f1_WL + + +This function should extracted because the namespace `N1::N3_WL` is included as a literal. + +=== Synopsis + + +Declared in `<whitelist_0.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +f1_WL(); +---- [#N1-N4] == <>::N4 +This namespace should extracted because it's implied by `N1::N4::f1_WL` + === Functions -[cols=1] +[cols=2] |=== -| Name +| Name | Description | <> +| This function should extracted because it matches `N1::N4::f1_WL` + |=== [#N1-N4-f1_WL] == <>::<>::f1_WL +This function should extracted because it matches `N1::N4::f1_WL` + === Synopsis @@ -95,56 +158,197 @@ f1_WL(); == N5 +This namespace should extracted because it's implied by `N5::N6::*7` + === Namespaces -[cols=1] +[cols=2] |=== -| Name +| Name | Description + +| <> +| This namespace should extracted because it's implied by `N5::N6::*7` -| <> |=== -[#N5-N6_BL] -== <>::N6_BL +[#N5-N6] +== <>::N6 + +This namespace should extracted because it's implied by `N5::N6::*7` === Namespaces -[cols=1] +[cols=2] +|=== +| Name | Description + +| <> +| This namespace should be included because it matches `N5::N6::*7` + +| <> +| This namespace should be included because it matches `N5::N6::*7` + +|=== + +[#N5-N6-M7] +== <>::<>::M7 + + +This namespace should be included because it matches `N5::N6::*7` + +=== Functions + +[cols=2] |=== -| Name +| Name | Description + +| <> +| This function should be included because it's a member of `M7`, which matches `N5::N6::*7` -| <> -| <> -| <> |=== + +[#N5-N6-M7-f2_WL] +== <>::<>::<>::f2_WL + + +This function should be included because it's a member of `M7`, which matches `N5::N6::*7` + +=== Synopsis + + +Declared in `<whitelist_0.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +f2_WL(); +---- + +[#N5-N6-N7] +== <>::<>::N7 + + +This namespace should be included because it matches `N5::N6::*7` + === Functions -[cols=1] +[cols=2] +|=== +| Name | Description + +| <> +| This function should be included because it's a member of `N7`, which matches `N5::N6::*7` + +|=== + +[#N5-N6-N7-f2_WL] +== <>::<>::<>::f2_WL + + +This function should be included because it's a member of `N7`, which matches `N5::N6::*7` + +=== Synopsis + + +Declared in `<whitelist_0.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +f2_WL(); +---- + +[#C] +== C + + +This namespace should be included because it strictly matches `C` + +=== Synopsis + + +Declared in `<whitelist_0.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +struct C; +---- + +=== Types + +[cols=2] +|=== +| Name | Description + +| <> +| This struct should be included because it's a member of `C` + +|=== +=== Member Functions + +[cols=2] +|=== +| Name | Description + +| <> +| This function should be included because it's a member of `C` + +|=== + + + +[#C-D] +== <>::D + + +This struct should be included because it's a member of `C` + +=== Synopsis + + +Declared in `<whitelist_0.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +struct D; +---- + +=== Member Functions + +[cols=2] |=== -| Name +| Name | Description + +| <> +| This function should be included because it's a member of `D` -| <> |=== -[#N5-N6_BL-M7] -== <>::<>::M7 +[#C-D-f1_WL] +== <>::<>::f1_WL -[#N5-N6_BL-N7] -== <>::<>::N7 +This function should be included because it's a member of `D` +=== Synopsis -[#N5-N6_BL-N8] -== <>::<>::N8 +Declared in `<whitelist_0.cpp>` + +[source,cpp,subs="verbatim,replacements,macros,-callouts"] +---- +void +f1_WL(); +---- +[#C-f0_WL] +== <>::f0_WL -[#N5-N6_BL-f1_BL] -== <>::<>::f1_BL +This function should be included because it's a member of `C` === Synopsis @@ -154,7 +358,7 @@ Declared in `<whitelist_0.cpp>` [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- void -f1_BL(); +f0_WL(); ---- diff --git a/test-files/golden-tests/filters/symbol-name/whitelist_0.cpp b/test-files/golden-tests/filters/symbol-name/whitelist_0.cpp index 341b2abfd1..16ec260e80 100644 --- a/test-files/golden-tests/filters/symbol-name/whitelist_0.cpp +++ b/test-files/golden-tests/filters/symbol-name/whitelist_0.cpp @@ -1,51 +1,129 @@ +/// This namespace should extracted because +/// it's implied by `N0::f0_WL` namespace N0 { + /// This function should be included because + /// it matches `N0::f0_WL` void f0_WL(); + + /// This function should be excluded because + /// it doesn't match `N0::f0_WL` void f1_BL(); } +/// This namespace should extracted because +/// it's implied by `N1::N3_WL` and `N1::N4::f1_WL` namespace N1 { + /// This function should be excluded because + /// it doesn't match `N1::N3_WL` or `N1::N4::f1_WL` void f0_BL(); + /// This namespace should be excluded because + /// it doesn't match `N1::N3_WL` or `N1::N4::f1_WL` namespace N2 { + /// This function should be excluded because + /// it doesn't match `N1::N3_WL` or `N1::N4::f1_WL` void f1_BL(); } + /// This namespace should extracted because + /// it's explicitly included by `N1::N3_WL` namespace N3_WL { + /// This function should extracted because + /// the namespace `N1::N3_WL` is included + /// as a literal. void f1_WL(); } + /// This namespace should extracted because + /// it's implied by `N1::N4::f1_WL` namespace N4 { + /// This function should extracted because + /// it matches `N1::N4::f1_WL` void f1_WL(); + + /// This function should be excluded because + /// it doesn't match `N1::N4::f1_WL` void f2_BL(); } } +/// This namespace should extracted because +/// it's implied by `N5::N6::*7` namespace N5 { + /// This function should be excluded because + /// it doesn't match `N5::N6::*7` void f0(); - namespace N6_BL + /// This namespace should extracted because + /// it's implied by `N5::N6::*7` + namespace N6 { + /// This function should be excluded because + /// it doesn't match `N5::N6::*7` void f1_BL(); + /// This namespace should be included because + /// it matches `N5::N6::*7` namespace N7 { + /// This function should be included because + /// it's a member of `N7`, which matches + /// `N5::N6::*7` void f2_WL(); } + /// This namespace should be included because + /// it matches `N5::N6::*7` namespace M7 { + /// This function should be included because + /// it's a member of `M7`, which matches + /// `N5::N6::*7` void f2_WL(); } + /// This namespace should be excluded because + /// it doesn't match or is implied by + /// `N5::N6::*7` namespace N8 { + /// This function should be excluded because + /// it doesn't match `N5::N6::*7` void f2_BL(); } } } + +/// This namespace should be included because +/// it strictly matches `C` +struct C +{ + /// This function should be included because + /// it's a member of `C` + void f0_WL(); + + /// This struct should be included because + /// it's a member of `C` + struct D { + /// This function should be included because + /// it's a member of `D` + void f1_WL(); + }; +}; + +/// This namespace should be excluded even +/// though it matches the prefix of `N9::*_WL` +/// because there are no members of `N9` that +/// actually match the filters. +namespace N9 +{ + /// This function should be excluded because + /// it doesn't match `N9::*_WL` + void f0_BL(); +} \ No newline at end of file diff --git a/test-files/golden-tests/filters/symbol-name/whitelist_0.html b/test-files/golden-tests/filters/symbol-name/whitelist_0.html index 78aabc9255..1888e11308 100644 --- a/test-files/golden-tests/filters/symbol-name/whitelist_0.html +++ b/test-files/golden-tests/filters/symbol-name/whitelist_0.html @@ -13,37 +13,69 @@

Namespaces

- + - - - + + + + +
NameNameDescription
N0
N1
N5
N0 This namespace should extracted because it's implied by N0::f0_WL + +
N1 This namespace should extracted because it's implied by N1::N3_WL and N1::N4::f1_WL + +
N5 This namespace should extracted because it's implied by N5::N6::*7 + +
+

Types

+ + + + + + + + +
NameDescription
C This namespace should be included because it strictly matches C + +

N0

+
+This namespace should extracted because it's implied by N0::f0_WL + + +

Functions

- + - +
NameNameDescription
f0_WL
f0_WL This function should be included because it matches N0::f0_WL + +

N0::f0_WL

+
+This function should be included because it matches N0::f0_WL + + +

Synopsis

@@ -60,46 +92,107 @@

Synopsis

N1

+
+This namespace should extracted because it's implied by N1::N3_WL and N1::N4::f1_WL + + +

Namespaces

- + - - + +
NameNameDescription
N3_WL
N4
N3_WL This namespace should extracted because it's explicitly included by N1::N3_WL + +
N4 This namespace should extracted because it's implied by N1::N4::f1_WL + +

N1::N3_WL

+
+This namespace should extracted because it's explicitly included by N1::N3_WL + + +
+
+

Functions

+ + + + + + + + + + +
NameDescription
f1_WL This function should extracted because the namespace N1::N3_WL is included as a literal. + +
+
+
+
+

N1::N3_WL::f1_WL

+
+This function should extracted because the namespace N1::N3_WL is included as a literal. + + +
+
+
+

Synopsis

+
+Declared in <whitelist_0.cpp>
+
+
+void
+f1_WL();
+
+

N1::N4

+
+This namespace should extracted because it's implied by N1::N4::f1_WL + + +

Functions

- + - +
NameNameDescription
f1_WL
f1_WL This function should extracted because it matches N1::N4::f1_WL + +

N1::N4::f1_WL

+
+This function should extracted because it matches N1::N4::f1_WL + + +

Synopsis

@@ -116,69 +209,259 @@

Synopsis

N5

+
+This namespace should extracted because it's implied by N5::N6::*7 + + +

Namespaces

- + - +
NameNameDescription
N6_BL
N6 This namespace should extracted because it's implied by N5::N6::*7 + +
-

N5::N6_BL

+

N5::N6

+
+This namespace should extracted because it's implied by N5::N6::*7 + + +

Namespaces

- + + + + + + + + +
NameNameDescription
M7 This namespace should be included because it matches N5::N6::*7 + +
N7 This namespace should be included because it matches N5::N6::*7 + +
+
+
+
+

N5::N6::M7

+
+This namespace should be included because it matches N5::N6::*7 + + +
+
+

Functions

+ + + + - - - +
NameDescription
M7
N7
N8
f2_WL This function should be included because it's a member of M7 , which matches N5::N6::*7 + +
+
+
+
+

N5::N6::M7::f2_WL

+
+This function should be included because it's a member of M7 , which matches N5::N6::*7 + + +
+
+
+

Synopsis

+
+Declared in <whitelist_0.cpp>
+
+
+void
+f2_WL();
+
+
+
+
+
+
+

N5::N6::N7

+
+This namespace should be included because it matches N5::N6::*7 + + +
+

Functions

- + - +
NameNameDescription
f1_BL
f2_WL This function should be included because it's a member of N7 , which matches N5::N6::*7 + +
-

N5::N6_BL::M7

+

N5::N6::N7::f2_WL

+
+This function should be included because it's a member of N7 , which matches N5::N6::*7 + +
+

Synopsis

-

N5::N6_BL::N7

+Declared in <whitelist_0.cpp>
+
+
+void
+f2_WL();
+
+
-

N5::N6_BL::N8

+

C

+
+This namespace should be included because it strictly matches C + +
+

Synopsis

-

N5::N6_BL::f1_BL

+Declared in <whitelist_0.cpp>
+
+
+struct C;
+
+
+
+

Types

+ + + + + + + + + + +
NameDescription
D This struct should be included because it's a member of C + +
+

Member Functions

+ + + + + + + + + + +
NameDescription
f0_WL This function should be included because it's a member of C + +
+ + +
+
+
+

C::D

+
+This struct should be included because it's a member of C + + +
+
+
+

Synopsis

+
+Declared in <whitelist_0.cpp>
+
+
+struct D;
+
+
+
+

Member Functions

+ + + + + + + + + + +
NameDescription
f1_WL This function should be included because it's a member of D + +
+ + +
+
+
+

C::D::f1_WL

+
+This function should be included because it's a member of D + + +
+
+
+

Synopsis

+
+Declared in <whitelist_0.cpp>
+
+
+void
+f1_WL();
+
+
+
+
+
+
+

C::f0_WL

+
+This function should be included because it's a member of C + + +

Synopsis

@@ -187,7 +470,7 @@

Synopsis

 
 void
-f1_BL();
+f0_WL();
 
 
diff --git a/test-files/golden-tests/filters/symbol-name/whitelist_0.xml b/test-files/golden-tests/filters/symbol-name/whitelist_0.xml index 26a0c49cf0..3c636fc03d 100644 --- a/test-files/golden-tests/filters/symbol-name/whitelist_0.xml +++ b/test-files/golden-tests/filters/symbol-name/whitelist_0.xml @@ -3,31 +3,159 @@ xsi:noNamespaceSchemaLocation="https://github.com/cppalliance/mrdocs/raw/develop/mrdocs.rnc"> + + + This namespace should extracted because it's implied by + N0::f0_WL + + - + + + + This function should be included because it matches + N0::f0_WL + + + + + This namespace should extracted because it's implied by + N1::N3_WL + and + N1::N4::f1_WL + + + + + This namespace should extracted because it's explicitly included by + N1::N3_WL + + + + + + + This function should extracted because the namespace + N1::N3_WL + is included + as a literal. + + + + + + This namespace should extracted because it's implied by + N1::N4::f1_WL + + - + + + + This function should extracted because it matches + N1::N4::f1_WL + + - - - - - - - + + + This namespace should extracted because it's implied by + N5::N6::*7 + + + + + + This namespace should extracted because it's implied by + N5::N6::*7 + + + + + + This namespace should be included because it matches + N5::N6::*7 + + + + + + + This function should be included because it's a member of + N7 + , which matches + + N5::N6::*7 + + + - + + + + This namespace should be included because it matches + N5::N6::*7 + + + + + + + This function should be included because it's a member of + M7 + , which matches + + N5::N6::*7 + + + + + + + + This namespace should be included because it strictly matches + C + + + + + + + This function should be included because it's a member of + C + + + + + + + + This struct should be included because it's a member of + C + + + + + + + This function should be included because it's a member of + D + + + + + diff --git a/test-files/golden-tests/filters/symbol-name/whitelist_0.yml b/test-files/golden-tests/filters/symbol-name/whitelist_0.yml index 8e00d807ae..084ffdeb0c 100644 --- a/test-files/golden-tests/filters/symbol-name/whitelist_0.yml +++ b/test-files/golden-tests/filters/symbol-name/whitelist_0.yml @@ -2,4 +2,6 @@ include-symbols: - 'N0::f0_WL' - 'N1::N3_WL' - 'N1::N4::f1_WL' - - 'N5::N6_BL::*7' + - 'N5::N6::*7' + - 'C' + - 'N9::*_WL' diff --git a/test-files/golden-tests/javadoc/ref.adoc b/test-files/golden-tests/javadoc/ref.adoc index e502316c1e..5d2b284879 100644 --- a/test-files/golden-tests/javadoc/ref.adoc +++ b/test-files/golden-tests/javadoc/ref.adoc @@ -209,7 +209,7 @@ struct D | Name | <> -| <> +| <> |=== diff --git a/test-files/golden-tests/javadoc/ref.html b/test-files/golden-tests/javadoc/ref.html index 5aceffe2c9..180f6b8559 100644 --- a/test-files/golden-tests/javadoc/ref.html +++ b/test-files/golden-tests/javadoc/ref.html @@ -249,7 +249,7 @@

Member Functions

f3 -f4 +f4 diff --git a/test-files/golden-tests/metadata/class-private-alias.adoc b/test-files/golden-tests/metadata/class-private-alias.adoc index dd662d67af..ef76435439 100644 --- a/test-files/golden-tests/metadata/class-private-alias.adoc +++ b/test-files/golden-tests/metadata/class-private-alias.adoc @@ -51,7 +51,7 @@ Declared in `<class‐private‐alias.cpp>` [source,cpp,subs="verbatim,replacements,macros,-callouts"] ---- void -f(type); +f(<>); ---- diff --git a/test-files/golden-tests/metadata/class-private-alias.html b/test-files/golden-tests/metadata/class-private-alias.html index ca7a760f5e..4ec44238ee 100644 --- a/test-files/golden-tests/metadata/class-private-alias.html +++ b/test-files/golden-tests/metadata/class-private-alias.html @@ -62,7 +62,7 @@

Synopsis

 
 void
-f(type);
+f(type);
 
 
diff --git a/test-files/golden-tests/metadata/class-private-alias.xml b/test-files/golden-tests/metadata/class-private-alias.xml index 6861b9e66e..f7f05211a1 100644 --- a/test-files/golden-tests/metadata/class-private-alias.xml +++ b/test-files/golden-tests/metadata/class-private-alias.xml @@ -7,7 +7,7 @@ - + diff --git a/test-files/golden-tests/metadata/mem-fn.adoc b/test-files/golden-tests/metadata/mem-fn.adoc index 31ecb76ffb..5de243d2d8 100644 --- a/test-files/golden-tests/metadata/mem-fn.adoc +++ b/test-files/golden-tests/metadata/mem-fn.adoc @@ -657,7 +657,7 @@ struct T17 |=== | Name -| <> [.small]#[virtual]# +| <> [.small]#[virtual]# |=== @@ -783,7 +783,7 @@ struct V | Name | <> -| <> [.small]#[virtual]# +| <> [.small]#[virtual]# |=== === Static Member Functions diff --git a/test-files/golden-tests/metadata/mem-fn.html b/test-files/golden-tests/metadata/mem-fn.html index e030428e80..b2bf6e0e57 100644 --- a/test-files/golden-tests/metadata/mem-fn.html +++ b/test-files/golden-tests/metadata/mem-fn.html @@ -743,7 +743,7 @@

Member Functions

-f [virtual] +f [virtual] @@ -885,7 +885,7 @@

Member Functions

f1 -f3 [virtual] +f3 [virtual]

Static Member Functions