From 7e5fc6e235d3bc7cb785ae5c49b8de5f2846e299 Mon Sep 17 00:00:00 2001
From: alandefreitas <alandefreitas@gmail.com>
Date: Thu, 27 Feb 2025 19:31:27 -0300
Subject: [PATCH 1/4] Parse support header

#improvement
---
 include/mrdocs/Metadata/Template.hpp |   4 +-
 include/mrdocs/Support/Error.hpp     |   4 +-
 include/mrdocs/Support/Parse.hpp     | 150 +++++++++++++++++++++++++++
 src/lib/AST/ParseRef.cpp             |  22 +++-
 src/lib/AST/ParseRef.hpp             |   8 +-
 src/test/lib/AST/ParseRef.cpp        |   4 +-
 6 files changed, 180 insertions(+), 12 deletions(-)
 create mode 100644 include/mrdocs/Support/Parse.hpp

diff --git a/include/mrdocs/Metadata/Template.hpp b/include/mrdocs/Metadata/Template.hpp
index b7b9d8156..b9572c39c 100644
--- a/include/mrdocs/Metadata/Template.hpp
+++ b/include/mrdocs/Metadata/Template.hpp
@@ -34,7 +34,9 @@ enum class TArgKind : int
     Template
 };
 
-MRDOCS_DECL std::string_view toString(TArgKind kind) noexcept;
+MRDOCS_DECL
+std::string_view
+toString(TArgKind kind) noexcept;
 
 inline
 void
diff --git a/include/mrdocs/Support/Error.hpp b/include/mrdocs/Support/Error.hpp
index aac800bb5..dbef752fd 100644
--- a/include/mrdocs/Support/Error.hpp
+++ b/include/mrdocs/Support/Error.hpp
@@ -53,7 +53,7 @@ class MRDOCS_DECL
         A default constructed error is
         equivalent to success.
     */
-    Error() noexcept = delete;
+    Error() noexcept = default;
 
     /** Constructor.
     */
@@ -122,7 +122,7 @@ class MRDOCS_DECL
     constexpr bool
     failed() const noexcept
     {
-        return ! message_.empty();
+        return !message_.empty();
     }
 
     /** Return true if this holds an error.
diff --git a/include/mrdocs/Support/Parse.hpp b/include/mrdocs/Support/Parse.hpp
new file mode 100644
index 000000000..c570bc491
--- /dev/null
+++ b/include/mrdocs/Support/Parse.hpp
@@ -0,0 +1,150 @@
+//
+// 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) 2025 Alan de Freitas (alandefreitas@gmail.com)
+//
+// Official repository: https://github.com/cppalliance/mrdocs
+//
+
+#ifndef MRDOCS_API_SUPPORT_PARSE_HPP
+#define MRDOCS_API_SUPPORT_PARSE_HPP
+
+#include <mrdocs/Support/Error.hpp>
+#include <mrdocs/Support/Expected.hpp>
+
+namespace clang::mrdocs {
+
+/** The result of a parse operation.
+
+    This class holds the result of a parse operation.
+    The structure is similar to `std::from_chars_result`,
+    where we have a `ptr` member that points to the
+    first character not parsed, and a `ec` member that
+    holds the error code.
+
+    If parsing was successful, then `ec` stores
+    a default constructed `Error` object, which
+    indicates success. The `operator bool` can
+    also be used to check for success.
+
+    The typical format of a parsing function is:
+
+    @code
+    ParseResult
+    parseType(
+        char const* first,
+        char const* last,
+        Type& value);
+    @endcode
+
+    where more parameters can be defined as needed for
+    parsing options.
+ */
+struct ParseResult {
+    const char* ptr;
+    Error ec;
+
+    friend
+    bool
+    operator==(
+        const ParseResult&,
+        const ParseResult& ) = default;
+
+    constexpr
+    explicit
+    operator bool() const noexcept
+    {
+        return !ec.failed();
+    }
+};
+
+/** Concept to determine if there's a parse function for a type.
+
+    This concept checks if a type `T` has a parse function
+    with the signature:
+
+    @code
+    ParseResult
+    parse(
+        char const* first,
+        char const* last,
+        T& value);
+    @endcode
+ */
+template <class T>
+concept HasParse = requires(
+    char const* first,
+    char const* last,
+    T& value)
+{
+    { parse(first, last, value) } -> std::same_as<ParseResult>;
+};
+
+/** Parse a string view
+
+    This function parses a string view `sv` into a value
+    of type `T`. The function calls the `parse` function
+    for the type `T` with the `sv.data()` and `sv.data() + sv.size()`
+    as the first and last pointers, respectively.
+
+    If the parse function returns an error, then the function
+    returns the error.
+
+    If the parse function returns success, but there are
+    characters left in the string view, then the function
+    returns an error with the message "trailing characters".
+
+    Otherwise, it returns the value.
+
+    @param sv The string view to parse
+    @param value The value to store the result
+ */
+template <HasParse T>
+ParseResult
+parse(std::string_view sv, T& value)
+{
+    ParseResult result = parse(sv.data(), sv.data() + sv.size(), value);
+    if (result)
+    {
+        if (result.ptr != sv.data() + sv.size())
+        {
+            result.ec = Error("trailing characters");
+        }
+    }
+    return result;
+}
+
+/** Parse a string view
+
+    Parse a string view `sv` as an object of type `T`.
+    If parsing fails, the function returns an error.
+
+    This overload does not return the `ParseResult` object
+    containing the pointer to the first character not parsed.
+    Instead, the position of the error is calculated and
+    the error message is formatted with the error position.
+
+    @copydetails parse(std::string_view, T&)
+
+ */
+template <HasParse T>
+Expected<T>
+parse(std::string_view sv)
+{
+    T v;
+    ParseResult const result = parse(sv, v);
+    if (result)
+    {
+        return v;
+    }
+    std::size_t const pos = result.ptr - sv.data();
+    return Unexpected(formatError(
+        "'{}' at position {}: {}",
+        sv, pos, result.ec.reason()));
+}
+
+} // clang::mrdocs
+
+#endif
\ No newline at end of file
diff --git a/src/lib/AST/ParseRef.cpp b/src/lib/AST/ParseRef.cpp
index b5cff716e..3e4e71917 100644
--- a/src/lib/AST/ParseRef.cpp
+++ b/src/lib/AST/ParseRef.cpp
@@ -2139,12 +2139,24 @@ class RefParser
 } // (anon)
 
 // Function to parse a C++ symbol name
-Expected<ParsedRef>
-parseRef(std::string_view sv)
+ParseResult
+parse(
+    char const* first,
+    char const* last,
+    ParsedRef& value)
 {
-    RefParser parser(sv);
-    parser.parse();
-    return parser.result();
+    RefParser parser(first, last, value);
+    ParseResult res;
+    if (parser.parse())
+    {
+        res.ptr = parser.position();
+    }
+    else
+    {
+        res.ec = parser.error();
+        res.ptr = parser.errorPos();
+    }
+    return res;
 }
 
 } // clang::mrdocs
diff --git a/src/lib/AST/ParseRef.hpp b/src/lib/AST/ParseRef.hpp
index 8ea15152d..9765991c2 100644
--- a/src/lib/AST/ParseRef.hpp
+++ b/src/lib/AST/ParseRef.hpp
@@ -13,6 +13,7 @@
 
 #include <mrdocs/Metadata/Specifiers.hpp>
 #include <mrdocs/Metadata/Type.hpp>
+#include <mrdocs/Support/Parse.hpp>
 #include <mrdocs/ADT/Polymorphic.hpp>
 #include <llvm/ADT/SmallVector.h>
 #include <string_view>
@@ -70,8 +71,11 @@ struct ParsedRef {
     NoexceptInfo ExceptionSpec;
 };
 
-Expected<ParsedRef>
-parseRef(std::string_view sv);
+ParseResult
+parse(
+    char const* first,
+    char const* last,
+    ParsedRef& value);
 
 } // clang::mrdocs
 
diff --git a/src/test/lib/AST/ParseRef.cpp b/src/test/lib/AST/ParseRef.cpp
index ff3920894..0811a53fd 100644
--- a/src/test/lib/AST/ParseRef.cpp
+++ b/src/test/lib/AST/ParseRef.cpp
@@ -15,8 +15,8 @@ namespace clang::mrdocs {
 struct ParseRef_test
 {
 
-#define ok(str) BOOST_TEST(parseRef(str).has_value())
-#define fail(str) BOOST_TEST(!parseRef(str).has_value())
+#define ok(str) BOOST_TEST(parse<ParsedRef>(str).has_value())
+#define fail(str) BOOST_TEST(!parse<ParsedRef>(str).has_value())
 
 void
 testComponents()

From 7628bd08d55c8ea0fed5d7d405b1766686a4d8c9 Mon Sep 17 00:00:00 2001
From: alandefreitas <alandefreitas@gmail.com>
Date: Thu, 27 Feb 2025 19:33:15 -0300
Subject: [PATCH 2/4] ref parser supports template arguments

#feat
---
 include/mrdocs/Support/String.hpp |   9 +
 src/lib/AST/ParseRef.cpp          | 310 ++++++++++++++++++------------
 src/lib/AST/ParseRef.hpp          |   3 +-
 src/test/lib/AST/ParseRef.cpp     |  19 +-
 4 files changed, 213 insertions(+), 128 deletions(-)

diff --git a/include/mrdocs/Support/String.hpp b/include/mrdocs/Support/String.hpp
index 4aa9233e5..dc17ec96c 100644
--- a/include/mrdocs/Support/String.hpp
+++ b/include/mrdocs/Support/String.hpp
@@ -77,6 +77,15 @@ MRDOCS_DECL
 void
 replace(std::string& s, std::string_view from, std::string_view to);
 
+/** Determine if a string is only whitespace.
+ */
+constexpr
+bool
+isWhitespace(std::string_view s) noexcept
+{
+    return s.find_first_not_of(" \t\n\v\f\r") == std::string::npos;
+}
+
 
 } // mrdocs
 } // clang
diff --git a/src/lib/AST/ParseRef.cpp b/src/lib/AST/ParseRef.cpp
index 3e4e71917..6a279bb78 100644
--- a/src/lib/AST/ParseRef.cpp
+++ b/src/lib/AST/ParseRef.cpp
@@ -64,18 +64,21 @@ class RefParser
     char const* first_;
     char const* ptr_;
     char const* last_;
-    ParsedRef result_;
-    std::string error_;
+    ParsedRef& result_;
+    std::string error_msg_;
     char const* error_pos_{nullptr};
 
 public:
     explicit
-    RefParser(std::string_view const str)
-        : first_(str.data())
-        , ptr_(first_)
-        , last_(first_ + str.size())
-    {
-    }
+    RefParser(
+        char const* first,
+        char const* last,
+        ParsedRef& result) noexcept
+        : first_(first),
+          ptr_(first),
+          last_(last),
+          result_(result)
+    {}
 
     bool
     parse()
@@ -85,8 +88,7 @@ class RefParser
         {
             result_.IsFullyQualified = true;
         }
-        MRDOCS_CHECK_OR(parseComponents(), false);
-        skipWhitespace();
+        MRDOCS_CHECK_OR(parseComponents(result_.Components), false);
         result_.HasFunctionParameters = peek('(', ' ');
         if (result_.HasFunctionParameters)
         {
@@ -100,21 +102,27 @@ class RefParser
             result_.Kind = functionParameters.Kind;
             result_.IsExplicitObjectMemberFunction = functionParameters.IsExplicitObjectMemberFunction;
         }
-        error_.clear();
+        error_msg_.clear();
         error_pos_ = nullptr;
         return true;
     }
 
-    Expected<ParsedRef>
-    result() const
+    Error
+    error() const
     {
-        if (error_pos_ == nullptr)
-        {
-            return result_;
-        }
-        std::string_view str(first_, last_ - first_);
-        std::size_t pos = error_pos_ - first_;
-        return Unexpected(formatError("'{}' at position {}: {}", str, pos, error_));
+        return Error(error_msg_);
+    }
+
+    char const*
+    errorPos() const
+    {
+        return error_pos_;
+    }
+
+    char const*
+    position() const
+    {
+        return ptr_;
     }
 
 private:
@@ -123,9 +131,9 @@ class RefParser
     {
         // Only set the error if it's not already set
         // with a more specific error message
-        if (!error_pos_ || error_.empty())
+        if (!error_pos_ || error_msg_.empty())
         {
-            error_ = std::string(str);
+            error_msg_ = std::string(str);
             error_pos_ = pos;
         }
     }
@@ -242,6 +250,21 @@ class RefParser
         return first != last_ && *first == c;
     }
 
+    bool
+    peek(std::string_view str, char skip)
+    {
+        char const* first = ptr_;
+        while (first != last_ && *first == skip)
+        {
+            ++first;
+        }
+        if (std::cmp_greater(str.size(), last_ - first))
+        {
+            return false;
+        }
+        return std::equal(str.begin(), str.end(), first);
+    }
+
     bool
     peekBack(char c, char skip)
     {
@@ -276,8 +299,27 @@ class RefParser
     }
 
     bool
-    rewindUntil(char c)
+    advance(char c)
+    {
+        char const* start = ptr_;
+        while (ptr_ != last_ && *ptr_ == c)
+        {
+            ++ptr_;
+        }
+        return ptr_ != start;
+    }
+
+    bool
+    rewindUntil(char const c)
     {
+        if (first_ == last_)
+        {
+            return false;
+        }
+        if (ptr_ == last_)
+        {
+            --ptr_;
+        }
         while (ptr_ != first_ && *ptr_ != c)
         {
             --ptr_;
@@ -286,25 +328,26 @@ class RefParser
     }
 
     bool
-    parseComponents()
+    parseComponents(llvm::SmallVector<ParsedRefComponent, 8>& components)
     {
         char const* start = ptr_;
         while (true)
         {
             char const* compStart = ptr_;
-            if (!parseComponent())
+            if (!parseComponent(result_.Components.emplace_back()))
             {
                 return false;
             }
-            skipWhitespace();
-            if (!parseLiteral("::"))
+            if (!peek("::", ' '))
             {
-                return !result_.Components.empty();
+                return !components.empty();
             }
+            skipWhitespace();
+            parseLiteral("::");
             // If we have a "::" separator, so this is not
             // the last component. Check the rules for
             // nested-name-specifier
-            ParsedRefComponent const& comp = result_.Components.back();
+            ParsedRefComponent const& comp = components.back();
             if (comp.isOperator())
             {
                 ptr_ = compStart;
@@ -323,7 +366,7 @@ class RefParser
     }
 
     bool
-    parseComponent()
+    parseComponent(ParsedRefComponent& c)
     {
         if (!hasMore())
         {
@@ -332,28 +375,26 @@ class RefParser
         }
         char const *start = ptr_;
         skipWhitespace();
-        result_.Components.emplace_back();
-        if (!parseUnqualifiedIdentifierExpression())
+        if (!parseUnqualifiedId(c))
         {
             setError("expected component name");
             ptr_ = start;
             return false;
         }
-        skipWhitespace();
-        if (peek('<')) {
-            if (!parseTemplateArguments())
+        if (peek('<', ' ')) {
+            skipWhitespace();
+            if (!parseTemplateArguments(c.TemplateArguments))
             {
                 setError("expected template arguments");
                 ptr_ = start;
                 return false;
             }
         }
-
         return true;
     }
 
     bool
-    parseUnqualifiedIdentifierExpression()
+    parseUnqualifiedId(ParsedRefComponent& c)
     {
         // https://en.cppreference.com/w/cpp/language/identifiers#Unqualified_identifiers
         // Besides suitably declared identifiers, the following unqualified identifier
@@ -374,31 +415,30 @@ class RefParser
         }
 
         // Try to parse as an operator
-        if (parseOperator())
+        if (parseOperator(c))
         {
             return true;
         }
 
         // Parse conversion operator
-        if (parseConversionOperator())
+        if (parseConversionOperator(c))
         {
             return true;
         }
 
         // Parse as a regular identifier
-        if (!parseDestructorOrIdentifier())
+        if (!parseDestructorOrIdentifier(c.Name))
         {
             setError("expected component name");
             ptr_ = start;
             return false;
         }
-        currentComponent().Name = std::string_view(start, ptr_ - start);
-        currentComponent().Operator = OperatorKind::None;
+        c.Operator = OperatorKind::None;
         return true;
     }
 
     bool
-    parseConversionOperator()
+    parseConversionOperator(ParsedRefComponent& c)
     {
         char const* start = ptr_;
         if (!parseKeyword("operator"))
@@ -413,12 +453,12 @@ class RefParser
             ptr_ = start;
             return false;
         }
-        currentComponent().ConversionType = std::move(conversionType);
+        c.ConversionType = std::move(conversionType);
         return true;
     }
 
     bool
-    parseDestructorOrIdentifier()
+    parseDestructorOrIdentifier(std::string_view& s)
     {
         // A regular identifier or a function name
         char const* start = ptr_;
@@ -438,6 +478,7 @@ class RefParser
             ptr_ = start;
             return false;
         }
+        s = std::string_view(start, ptr_ - start);
         return true;
     }
 
@@ -477,14 +518,8 @@ class RefParser
         return true;
     }
 
-    ParsedRefComponent&
-    currentComponent()
-    {
-        return result_.Components.back();
-    }
-
     bool
-    parseOperator()
+    parseOperator(ParsedRefComponent& c)
     {
         const char* start = ptr_;
         if (!parseLiteral("operator"))
@@ -501,9 +536,9 @@ class RefParser
         {
             if (parseLiteral(op))
             {
-                currentComponent().Operator = getOperatorKindFromSuffix(op);
-                MRDOCS_ASSERT(currentComponent().Operator != OperatorKind::None);
-                currentComponent().Name = getOperatorName(currentComponent().Operator, true);
+                c.Operator = getOperatorKindFromSuffix(op);
+                MRDOCS_ASSERT(c.Operator != OperatorKind::None);
+                c.Name = getOperatorName(c.Operator, true);
                 return true;
             }
         }
@@ -526,21 +561,21 @@ class RefParser
             return false;
         }
         std::string_view op(op_start, ptr_ - op_start);
-        currentComponent().Operator = getOperatorKindFromSuffix(op);
-        if (currentComponent().Operator == OperatorKind::None)
+        c.Operator = getOperatorKindFromSuffix(op);
+        if (c.Operator == OperatorKind::None)
         {
             // This operator doesn't exist
             ptr_ = start;
             return false;
         }
-        currentComponent().Name = getOperatorName(currentComponent().Operator, true);
+        c.Name = getOperatorName(c.Operator, true);
         return true;
     }
 
     bool
-    parseTemplateArguments()
+    parseTemplateArguments(std::vector<Polymorphic<TArg>>& TemplateArguments)
     {
-        // https://en.cppreference.com/w/cpp/language/template_parameters
+        // https://en.cppreference.com/w/cpp/language/template_parameters#Template_arguments
         char const* start = ptr_;
         if (!parseLiteral('<'))
         {
@@ -548,12 +583,14 @@ class RefParser
             return false;
         }
         skipWhitespace();
-        while (parseTemplateArgument())
+        TemplateArguments.emplace_back();
+        while (parseTemplateArgument(TemplateArguments.back()))
         {
             skipWhitespace();
             if (parseLiteral(','))
             {
                 skipWhitespace();
+                TemplateArguments.emplace_back();
             }
             else
             {
@@ -570,69 +607,49 @@ class RefParser
         return true;
     }
 
-    bool
-    parseTemplateArgument()
-    {
-        return parseTemplateArgument(currentComponent().TemplateArguments.emplace_back());
-    }
-
     bool
     parseTemplateArgument(Polymorphic<TArg>& dest)
     {
+        // https://en.cppreference.com/w/cpp/language/template_parameters#Template_arguments
+        // If an argument can be interpreted as both a type-id and an
+        // expression, it is always interpreted as a type-id, even if the
+        // corresponding template parameter is non-type:
         if (!hasMore())
         {
             return false;
         }
         skipWhitespace();
         char const* start = ptr_;
-        if (!parseTemplateArgumentName())
+        Polymorphic<TypeInfo> type;
+        if (parseTypeId(type))
         {
-            ptr_ = start;
-            setError("expected template argument name");
-            return false;
+            TypeTArg arg;
+            arg.Type = std::move(type);
+            dest = std::move(arg);
+            return true;
         }
-        skipWhitespace();
-        return true;
-    }
-
-    Polymorphic<TArg>&
-    currentTemplateArgument()
-    {
-        return currentComponent().TemplateArguments.back();
-    }
-
-    bool
-    parseTemplateArgumentName() {
-        auto& dest = currentTemplateArgument();
-        return parseTemplateArgumentName(dest);
-    }
-
-    bool
-    parseTemplateArgumentName(Polymorphic<TArg>& dest)
-    {
-        char const* start = ptr_;
 
-        if (!hasMore())
+        // If the argument is not a type-id, it is an expression
+        // The expression is internally balanced in regards to '<'
+        // and '>' and ends with a comma
+        char const* exprStart = ptr_;
+        while (parseBalanced("<", ">", {",", ">"}))
         {
-            setError("expected component name");
-            return false;
+            if (!peekAny({',', '>'}, ' '))
+            {
+                continue;
+            }
+            break;
         }
-
-        // Parse as a regular identifier
-        if (!parseIdentifier(true))
+        if (ptr_ == exprStart)
         {
-            setError("expected identifier name");
+            setError("expected template argument");
             ptr_ = start;
             return false;
         }
-        auto const nameStr = std::string_view(start, ptr_ - start);
-        TypeTArg arg;
-        NamedTypeInfo type;
-        NameInfo nameInfo;
-        nameInfo.Name = std::string(nameStr);
-        type.Name = nameInfo;
-        arg.Type = type;
-        dest = arg;
+        NonTypeTArg arg;
+        arg.Value.Written = trim(std::string_view(exprStart, ptr_ - exprStart));
+        dest = std::move(arg);
         return true;
     }
 
@@ -774,22 +791,10 @@ class RefParser
         // decl-specifier-seq
         dest.Params.emplace_back();
         auto& curParam = dest.Params.back();
-        if (!parseDeclarationSpecifiers(curParam))
-        {
-            ptr_ = start;
-            setError("expected parameter qualified type");
-            return false;
-        }
-
-        // If a parameter is not used in the function body, it does
-        // not need to be named (it's sufficient to use an
-        // abstract declarator).
-        // MrDocs refs only use abstract declarators. Any parameter
-        // name is ignored.
-        if (!parseAbstractDeclarator(curParam))
+        if (!parseTypeId(curParam))
         {
-            setError("expected abstract declarator");
             ptr_ = start;
+            setError("expected type-id");
             return false;
         }
 
@@ -817,6 +822,35 @@ class RefParser
         return true;
     }
 
+    bool
+    parseTypeId(Polymorphic<TypeInfo>& dest)
+    {
+        char const* start = ptr_;
+
+        // https://en.cppreference.com/w/cpp/language/function#Parameter_list
+        // decl-specifier-seq
+        if (!parseDeclarationSpecifiers(dest))
+        {
+            ptr_ = start;
+            setError("expected parameter qualified type");
+            return false;
+        }
+
+        // If a parameter is not used in the function body, it does
+        // not need to be named (it's sufficient to use an
+        // abstract declarator).
+        // MrDocs refs only use abstract declarators. Any parameter
+        // name is ignored.
+        if (!parseAbstractDeclarator(dest))
+        {
+            setError("expected abstract declarator");
+            ptr_ = start;
+            return false;
+        }
+
+        return true;
+    }
+
     bool
     parseDeclarationSpecifiers(Polymorphic<TypeInfo>& dest)
     {
@@ -859,7 +893,7 @@ class RefParser
                     return false;
                 }
                 // Clear the error and let the type modifiers set `dest`
-                error_.clear();
+                error_msg_.clear();
                 error_pos_ = nullptr;
                 break;
             }
@@ -1267,18 +1301,35 @@ class RefParser
     bool
     parseBalanced(
         std::string_view const openTag,
-        std::string_view const closeTag)
+        std::string_view const closeTag,
+        std::initializer_list<std::string_view> const until = {})
     {
         char const* start = ptr_;
         std::size_t depth = 0;
         while (hasMore())
         {
+            if (depth == 0)
+            {
+                for (std::string_view const& untilTag : until)
+                {
+                    if (peek(untilTag))
+                    {
+                        return true;
+                    }
+                }
+            }
             if (parseLiteral(openTag))
             {
                 ++depth;
             }
             else if (parseLiteral(closeTag))
             {
+                if (depth == 0)
+                {
+                    setError("unbalanced expression");
+                    ptr_ = start;
+                    return false;
+                }
                 --depth;
                 if (depth == 0)
                 {
@@ -1360,11 +1411,24 @@ class RefParser
             skipWhitespace();
             if (peek('<'))
             {
-                if (!parseTemplateArguments())
+                if (!dest->isNamed())
+                {
+                    setError("expected named type for template arguments");
+                    ptr_ = start;
+                    return false;
+                }
+                // Replace the nameinfo with a nameinfo with args
+                auto& namedParam = dynamic_cast<NamedTypeInfo&>(*dest);
+                SpecializationNameInfo SNI;
+                SNI.Name = std::move(namedParam.Name->Name);
+                SNI.Prefix = std::move(namedParam.Name->Prefix);
+                SNI.id = namedParam.Name->id;
+                if (!parseTemplateArguments(SNI.TemplateArgs))
                 {
                     ptr_ = start;
                     return false;
                 }
+                namedParam.Name = std::move(SNI);
             }
             else
             {
diff --git a/src/lib/AST/ParseRef.hpp b/src/lib/AST/ParseRef.hpp
index 9765991c2..da27d58b1 100644
--- a/src/lib/AST/ParseRef.hpp
+++ b/src/lib/AST/ParseRef.hpp
@@ -25,7 +25,8 @@ struct ParsedRefComponent {
     std::string_view Name;
 
     // If not empty, this is a specialization
-    llvm::SmallVector<Polymorphic<TArg>, 8> TemplateArguments;
+    bool HasTemplateArguments = false;
+    std::vector<Polymorphic<TArg>> TemplateArguments;
 
     // If not None, this is an operator
     // Only the last component can be an operator
diff --git a/src/test/lib/AST/ParseRef.cpp b/src/test/lib/AST/ParseRef.cpp
index 0811a53fd..a7837e711 100644
--- a/src/test/lib/AST/ParseRef.cpp
+++ b/src/test/lib/AST/ParseRef.cpp
@@ -24,7 +24,6 @@ testComponents()
     fail("");
     ok("a");
     ok("  a");
-    ok("  a  ");
     ok("::a");
     ok("a::b");
     ok("a::b::c");
@@ -32,8 +31,8 @@ testComponents()
     ok("a:: ~ b");
     ok("a::operator+");
     ok("a::operator()");
-    ok("a:: operator () ");
-    fail("a:: operator ( ) ");
+    ok("a:: operator ()");
+    fail("a:: operator ( )");
     ok("a::operator bool");
     fail("a::operator bool::c");
     fail("a::operator+::c");
@@ -43,7 +42,7 @@ void
 testFunctionParameters()
 {
     ok("f()");
-    ok("f  (  ) ");
+    ok("f  (  )");
     ok("f(void)");
     fail("f(void, void)");
     fail("f(int, void)");
@@ -294,6 +293,17 @@ testMainFunctionQualifiers()
     ok("f(int) && noexcept");
 }
 
+void
+testTemplateArguments()
+{
+    // type template parameter
+    ok("A::B<int>");
+    ok("A::B<int, 2>");
+    ok("A::B<int, true>");
+    ok("A::B<C, true>");
+    ok("A::B<Args..., true>");
+}
+
 void
 run()
 {
@@ -302,6 +312,7 @@ run()
     testParameterDeclarationSpecifiers();
     testParameterDeclarators();
     testMainFunctionQualifiers();
+    testTemplateArguments();
 }
 
 };

From d6d48393d22b9deff3d532aeae6b32523e035a86 Mon Sep 17 00:00:00 2001
From: alandefreitas <alandefreitas@gmail.com>
Date: Thu, 27 Feb 2025 19:34:18 -0300
Subject: [PATCH 3/4] javadoc visitor uses ref parser

#improvement
---
 src/lib/AST/ParseJavadoc.cpp | 332 ++++++++++++-----------------------
 1 file changed, 113 insertions(+), 219 deletions(-)

diff --git a/src/lib/AST/ParseJavadoc.cpp b/src/lib/AST/ParseJavadoc.cpp
index 081a9e2b3..aada63079 100644
--- a/src/lib/AST/ParseJavadoc.cpp
+++ b/src/lib/AST/ParseJavadoc.cpp
@@ -23,6 +23,8 @@
 #include <clang/AST/RawCommentList.h>
 #include <clang/Lex/Lexer.h>
 #include <clang/Basic/SourceLocation.h>
+#include "lib/AST/ParseRef.hpp"
+
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(disable: 5054) // C5054: operator '+': deprecated between enumerations of different types
@@ -1072,6 +1074,21 @@ std::string
 JavadocVisitor::
 fixReference(std::string& ref)
 {
+    auto peekNextIt = [&]() -> std::optional<std::string_view>
+    {
+        ++it_;
+        if (it_ == end_ ||
+            (*it_)->getCommentKind() != CommentKind::TextComment)
+        {
+            --it_;
+            return std::nullopt;
+        }
+        Comment const* c = *it_;
+        std::string_view text = static_cast<TextComment const*>(c)->getText();
+        --it_;
+        return text;
+    };
+
     // If the ref is only "operator", the next text comment
     // might contain a simple operator name/type, or a
     // full operator overload.
@@ -1080,249 +1097,126 @@ fixReference(std::string& ref)
     // we find an unbalanced '('.
     // Simply including the next text comment is enough
     // for the next step.
-    std::string_view trimmed = trim(ref);
-    bool const isNoSuffixOperator =
-        trimmed == "operator" ||
-        trimmed.ends_with("::operator");
-    if (isNoSuffixOperator)
-    {
-        ++it_;
-        if (it_ == end_)
-        {
-            return ref;
-        }
-        Comment const* c = *it_;
-        if (c->getCommentKind() == CommentKind::TextComment)
-        {
-            ref += static_cast<TextComment const*>(c)->getText();
-        }
-        else
+    ParsedRef v;
+    while (true)
+    {
+        // Attempt to parse ref
+        char const* first = ref.data();
+        char const* last = first + ref.size();
+        auto const pres = parse(first, last, v);
+        if (!pres)
         {
-            return ref;
-        }
-    }
-    static constexpr std::string_view idChars =
-        "abcdefghijklmnopqrstuvwxyz"
-        "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
-        "0123456789"
-        "_:";
-    bool const isNoFunctionOperator =
-        isNoSuffixOperator ||
-        [trimmed]{
-            if (contains_n(trimmed, '(', 1))
-            {
-                return false;
-            }
-            std::size_t pos = trimmed.rfind("::");
-            std::string_view last = trimmed;
-            if (pos != std::string::npos) {
-                last = trimmed.substr(pos + 2);
-            }
-            if (!last.starts_with("operator"))
-            {
-                return false;
-            }
-            last.remove_prefix(8);
-            if (last.empty())
+            // The ref could not be parsed, add content from next
+            // text comment to the ref
+            auto const nextTextOpt = peekNextIt();
+            if (!nextTextOpt)
             {
-                return true;
+                return {};
             }
-            return !contains(idChars, last.front());
-        }();
-
-    // Clang parses the copydoc command breaking
-    // before the complete overload information. For instance,
-    // `@copydoc operator()(unsigned char) const` will create
-    // a node with the text `operator()(unsigned` and another
-    // with `char) const`. We need to merge these nodes.
-    // If the ref contains an unbalanced '(', then it's
-    // a function, and we need to merge the next text comments
-    // until we find a balanced ')'.
-    bool const isFunction = contains(ref, '(');
-    if (isFunction)
-    {
-        while (std::ranges::count(ref, '(') != std::ranges::count(ref, ')'))
-        {
+            ref += *nextTextOpt;
             ++it_;
-            if (it_ == end_)
-            {
-                break;
-            }
-            Comment const* c = *it_;
-            if (c->getCommentKind() == CommentKind::TextComment)
-            {
-                ref += static_cast<TextComment const*>(c)->getText();
-            }
-            else
+            continue;
+        }
+
+        // The ref is fully parsed
+        if (pres.ptr != last)
+        {
+            // The ref didn't consume all the text, so we need to
+            // remove the leftover text from the ref and return it
+            auto leftover = std::string(pres.ptr, last - pres.ptr);
+            // If leftover is only whitespace, the ref might need
+            // the next text comment to complete it.
+            if (!isWhitespace(leftover))
             {
-                break;
+                ref.erase(pres.ptr - first);
+                return leftover;
             }
         }
-        if (rtrim(ref).ends_with(')'))
+
+        // The ref is fully parsed, but we might want to
+        // include the next text comment if it contains
+        // a valid continuation to the ref.
+        bool const mightHaveMoreQualifiers =
+            v.HasFunctionParameters &&
+            v.ExceptionSpec.Implicit &&
+            v.ExceptionSpec.Operand.empty();
+        if (mightHaveMoreQualifiers)
         {
-            static constexpr std::array<std::string_view, 5> qualifiers = {
-                "const",
-                "volatile",
-                "noexcept",
-                "&&",
-                "&",
-            };
-            auto isQualifiersOnly = [](std::string_view str)
+            llvm::SmallVector<std::string_view, 4> potentialQualifiers;
+            if (v.Kind == ReferenceKind::None)
             {
-                // Iterate all words between spaces and check if
-                // they are qualifiers
-                std::size_t pos = 0;
-                while (pos < str.size())
+                // "&&" or "&" not defined yet
+                if (!v.IsConst)
                 {
-                    std::size_t const start = str.find_first_not_of(' ', pos);
-                    if (start == std::string::npos)
-                    {
-                        break;
-                    }
-                    std::size_t const end = str.find_first_of(' ', start);
-                    std::string_view word = str.substr(start, end - start);
-                    if (std::ranges::find(qualifiers, word) == qualifiers.end())
-                    {
-                        return false;
-                    }
-                    pos = end;
+                    potentialQualifiers.push_back("const");
                 }
-                return true;
-            };
-            auto isWhitespaceOnly = [](std::string_view str)
-            {
-                return str.empty() || str.find_first_not_of(' ') == std::string::npos;
-            };
-
-            // peek next comment
-            std::string functionContinuation;
-            auto originalIt = it_;
-            ++it_;
-            while (
-                it_ != end_ &&
-                   (isWhitespaceOnly(functionContinuation) ||
-                    isQualifiersOnly(functionContinuation)))
-            {
-                Comment const* c = *it_;
-                if (c->getCommentKind() != CommentKind::TextComment)
+                if (!v.IsVolatile)
                 {
-                    break;
+                    potentialQualifiers.push_back("volatile");
                 }
-                functionContinuation += static_cast<TextComment const*>(c)->getText();
-                ++it_;
+                potentialQualifiers.push_back("&");
             }
-            if (isWhitespaceOnly(functionContinuation))
+            else if (
+                v.Kind == ReferenceKind::LValue &&
+                ref.ends_with('&'))
             {
-                it_ = originalIt;
+                // The second "&" might be in the next Text block
+                potentialQualifiers.push_back("&");
             }
-            else /* if (!functionContinuation.empty()) */
+            potentialQualifiers.push_back("noexcept");
+            auto const nextTextOpt = peekNextIt();
+            if (!nextTextOpt)
             {
-                --it_;
-                std::string_view suffix = functionContinuation;
-                std::string_view leftover = functionContinuation;
-                bool foundAny = false;
-                std::size_t totalRemoved = 0;
-                while (!suffix.empty())
-                {
-                    bool found = false;
-                    std::size_t const initialWhitespace = std::min(
-                        suffix.find_first_not_of(" "), suffix.size());
-                    for (auto const& q : qualifiers)
-                    {
-                        if (suffix.substr(initialWhitespace).starts_with(q))
-                        {
-                            std::size_t const toRemove = initialWhitespace + q.size();
-                            if (
-                                contains(idChars, q.back()) &&
-                                suffix.size() > toRemove &&
-                                contains(idChars, suffix[toRemove]))
-                            {
-                                // This is not a qualifier, but part of
-                                // an identifier
-                                continue;
-                            }
-                            suffix.remove_prefix(toRemove);
-                            totalRemoved += toRemove;
-                            found = true;
-                            foundAny = true;
-                            break;
-                        }
-                    }
-                    if (!found)
+                auto leftover = std::string(pres.ptr, last - pres.ptr);
+                ref.erase(pres.ptr - first);
+                return leftover;
+            }
+            std::string_view const nextText = *nextTextOpt;
+            std::string_view const trimmed = ltrim(nextText);
+            if (trimmed.empty() ||
+                std::ranges::any_of(
+                    potentialQualifiers,
+                    [&](std::string_view s)
                     {
-                        break;
-                    }
-                }
-                if (foundAny)
-                {
-                    leftover = leftover.substr(0, totalRemoved);
-                    ref += leftover;
-                    return std::string(suffix);
-                }
+                        return trimmed.starts_with(s);
+                    }))
+            {
+                ref += nextText;
+                ++it_;
+                continue;
             }
         }
-    }
 
-
-    // Clang refs can also contain invalid characters
-    // at the end, especially punctuation. We need to
-    // truncate the ref at the last valid identifier
-    // character.
-    // The last identifier character depends on the type
-    // of ref.
-    // - If it's an operator but not a function, then
-    //   we also consider operator chars as valid.
-    // - If it's a function, then we also consider ')'
-    //   as valid.
-    // - In all cases, we consider the identifier chars
-    //   as valid.
-    static constexpr std::string_view operatorChars =
-        "~!%^&*()-+=|[]{};:,.<>?/";
-    static constexpr std::string_view parenChars =
-        "()";
-    std::string leftover;
-    bool const isRegularIdentifier = !isFunction && !isNoFunctionOperator;
-    if (isRegularIdentifier)
-    {
-        auto const lastIdChar = ref.find_last_of(idChars);
-        auto const firstLeftoverChar = lastIdChar + 1;
-        if (firstLeftoverChar < ref.size())
-        {
-            leftover = std::string_view(ref).substr(lastIdChar + 1);
-            ref = ref.substr(0, lastIdChar + 1);
-        }
-    }
-    else if (isFunction)
-    {
-        auto reservedCharsets = {idChars, parenChars};
-        auto reservedChars = std::views::join(reservedCharsets);
-        auto const lastIdOrParen = find_last_of(ref, reservedChars);
-        auto const firstLeftoverChar =
-            lastIdOrParen == ref.end() ?
-                ref.end() :
-                std::next(lastIdOrParen);
-        if (firstLeftoverChar != ref.end())
+        // The ref might have more components
+        bool const mightHaveMoreComponents =
+            !v.HasFunctionParameters;
+        if (mightHaveMoreComponents)
         {
-            leftover = std::string_view(firstLeftoverChar, ref.end());
-            ref = ref.substr(0, std::distance(ref.begin(), firstLeftoverChar));
-        }
-    }
-    else /* if (isNoFunctionOperator) */
-    {
-        auto reservedCharsets = {idChars, operatorChars};
-        auto reservedChars = std::views::join(reservedCharsets);
-        auto const lastIdOrOperator = find_last_of(ref, reservedChars);
-        auto const firstLeftoverChar =
-            lastIdOrOperator == ref.end() ?
-                ref.end() :
-                std::next(lastIdOrOperator);
-        if (firstLeftoverChar != ref.end())
-        {
-            leftover = std::string_view(firstLeftoverChar, ref.end());
-            ref = ref.substr(0, std::distance(ref.begin(), firstLeftoverChar));
+            auto const nextTextOpt = peekNextIt();
+            if (!nextTextOpt)
+            {
+                auto leftover = std::string(pres.ptr, last - pres.ptr);
+                ref.erase(pres.ptr - first);
+                return leftover;
+            }
+            std::string_view const nextText = *nextTextOpt;
+            std::string_view const trimmed = ltrim(nextText);
+            static constexpr std::string_view idChars
+                   = "abcdefghijklmnopqrstuvwxyz"
+                     "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                     "0123456789"
+                     "_:";
+            if (trimmed.empty() ||
+                contains(idChars, trimmed.front()))
+            {
+                ref += nextText;
+                ++it_;
+                continue;
+            }
         }
+
+        return {};
     }
-    return leftover;
 }
 
 //------------------------------------------------

From 083338126433cd862667b3d97e325e5d149d9c0c Mon Sep 17 00:00:00 2001
From: alandefreitas <alandefreitas@gmail.com>
Date: Thu, 27 Feb 2025 19:34:47 -0300
Subject: [PATCH 4/4] corpus lookup matches template arguments

#feat
---
 mrdocs.rnc                                    |   5 +-
 src/lib/AST/ParseJavadoc.cpp                  |  10 +-
 src/lib/Lib/CorpusImpl.cpp                    |  67 ++-
 .../javadoc/copydoc/template-arguments.adoc   | 367 ++++++++++++++
 .../javadoc/copydoc/template-arguments.cpp    |  61 +++
 .../javadoc/copydoc/template-arguments.html   | 460 ++++++++++++++++++
 .../javadoc/copydoc/template-arguments.xml    | 167 +++++++
 .../golden-tests/javadoc/ref/punctuation.adoc |  75 +++
 .../golden-tests/javadoc/ref/punctuation.cpp  |   9 +
 .../golden-tests/javadoc/ref/punctuation.html |  91 ++++
 .../golden-tests/javadoc/ref/punctuation.xml  |  24 +
 11 files changed, 1311 insertions(+), 25 deletions(-)
 create mode 100644 test-files/golden-tests/javadoc/copydoc/template-arguments.adoc
 create mode 100644 test-files/golden-tests/javadoc/copydoc/template-arguments.cpp
 create mode 100644 test-files/golden-tests/javadoc/copydoc/template-arguments.html
 create mode 100644 test-files/golden-tests/javadoc/copydoc/template-arguments.xml
 create mode 100644 test-files/golden-tests/javadoc/ref/punctuation.adoc
 create mode 100644 test-files/golden-tests/javadoc/ref/punctuation.cpp
 create mode 100644 test-files/golden-tests/javadoc/ref/punctuation.html
 create mode 100644 test-files/golden-tests/javadoc/ref/punctuation.xml

diff --git a/mrdocs.rnc b/mrdocs.rnc
index 15a23e1c5..c10ff2d2c 100644
--- a/mrdocs.rnc
+++ b/mrdocs.rnc
@@ -37,7 +37,7 @@ grammar
             attribute is-anonymous { "1" }?,
             Javadoc?,
             element using-directive { ID } *,
-            Scope
+            Scope*
         }
 
     #---------------------------------------------
@@ -153,7 +153,8 @@ grammar
             Name,
             ID,
             Location *,
-            TypeInfo
+            TypeInfo,
+            Javadoc ?
         } |
         element typedef
         {
diff --git a/src/lib/AST/ParseJavadoc.cpp b/src/lib/AST/ParseJavadoc.cpp
index aada63079..b8bd81857 100644
--- a/src/lib/AST/ParseJavadoc.cpp
+++ b/src/lib/AST/ParseJavadoc.cpp
@@ -1089,14 +1089,6 @@ fixReference(std::string& ref)
         return text;
     };
 
-    // If the ref is only "operator", the next text comment
-    // might contain a simple operator name/type, or a
-    // full operator overload.
-    // In this case, we need to include the next text comments
-    // until we find this operator identifier/type or until
-    // we find an unbalanced '('.
-    // Simply including the next text comment is enough
-    // for the next step.
     ParsedRef v;
     while (true)
     {
@@ -1118,7 +1110,7 @@ fixReference(std::string& ref)
             continue;
         }
 
-        // The ref is fully parsed
+        // The ref is not fully parsed
         if (pres.ptr != last)
         {
             // The ref didn't consume all the text, so we need to
diff --git a/src/lib/Lib/CorpusImpl.cpp b/src/lib/Lib/CorpusImpl.cpp
index 11411e993..d7104dd0a 100644
--- a/src/lib/Lib/CorpusImpl.cpp
+++ b/src/lib/Lib/CorpusImpl.cpp
@@ -311,6 +311,33 @@ isDecayedEqual(
 {
     return isDecayedEqualImpl<false>(lhs, rhs, context, corpus);
 }
+
+bool
+isDecayedEqual(
+    Polymorphic<TArg> const& lhs,
+    Polymorphic<TArg> const& rhs,
+    Info const& context,
+    CorpusImpl const& corpus)
+{
+    if (lhs->Kind != rhs->Kind)
+    {
+        return false;
+    }
+    if (lhs->isType())
+    {
+        return isDecayedEqualImpl<true>(
+            get<TypeTArg>(lhs).Type,
+            get<TypeTArg>(rhs).Type,
+            context, corpus);
+    }
+    if (lhs->isNonType())
+    {
+        return
+            trim(get<NonTypeTArg>(lhs).Value.Written) ==
+            trim(get<NonTypeTArg>(rhs).Value.Written);
+    }
+    return false;
+}
 }
 
 Expected<std::reference_wrapper<Info const>>
@@ -353,7 +380,7 @@ lookupImpl(Self&& self, SymbolID const& contextId0, std::string_view name)
         }
         return std::cref(*info);
     }
-    Expected<ParsedRef> const expRef = parseRef(name);
+    auto const expRef = parse<ParsedRef>(name);
     if (!expRef)
     {
         return Unexpected(formatError("Failed to parse '{}'\n     {}", name, expRef.error().reason()));
@@ -494,7 +521,7 @@ lookupImpl(
     };
     auto const highestMatchLevel =
         !checkParameters || !ref.HasFunctionParameters ?
-            MatchLevel::TemplateArgsSize :
+            MatchLevel::TemplateArgs :
             MatchLevel::Qualifiers;
     auto matchLevel = MatchLevel::None;
     Info const* res = nullptr;
@@ -535,29 +562,41 @@ lookupImpl(
             }
             matchRes = MatchLevel::Name;
 
-            // Template arguments match
-            if constexpr (requires { M.Template; })
-            {
-                std::optional<TemplateInfo> const& templateInfo = M.Template;
-                if (component.TemplateArguments.empty())
+            // Template arguments size match
+            TemplateInfo const* templateInfo = [&]() -> TemplateInfo const* {
+                if constexpr (requires { M.Template; })
                 {
-                    MRDOCS_CHECK_OR(
-                        !templateInfo.has_value() ||
-                        templateInfo->Args.empty(), matchRes);
+                    std::optional<TemplateInfo> const& OTI = M.Template;
+                    MRDOCS_CHECK_OR(OTI, nullptr);
+                    TemplateInfo const& TI = *OTI;
+                    return &TI;
                 }
                 else
                 {
-                    MRDOCS_CHECK_OR(
-                        templateInfo.has_value() &&
-                        templateInfo->Args.size() == component.TemplateArguments.size(), matchRes);
+                    return nullptr;
                 }
+            }();
+            if (!templateInfo)
+            {
+                MRDOCS_CHECK_OR(!component.HasTemplateArguments, matchRes);
             }
             else
             {
-                MRDOCS_CHECK_OR(component.TemplateArguments.empty(), matchRes);
+                MRDOCS_CHECK_OR(templateInfo->Args.size() == component.TemplateArguments.size(), matchRes);
             }
             matchRes = MatchLevel::TemplateArgsSize;
 
+            if (templateInfo)
+            {
+                for (std::size_t i = 0; i < templateInfo->Args.size(); ++i)
+                {
+                    auto& lhsType = templateInfo->Args[i];
+                    auto& rhsType  = component.TemplateArguments[i];
+                    MRDOCS_CHECK_OR(isDecayedEqual(lhsType, rhsType, context, *this), matchRes);
+                }
+            }
+            matchRes = MatchLevel::TemplateArgs;
+
             // Function parameters size match
             MRDOCS_CHECK_OR(checkParameters && ref.HasFunctionParameters, matchRes);
             MRDOCS_CHECK_OR(MInfoTy::isFunction(), matchRes);
diff --git a/test-files/golden-tests/javadoc/copydoc/template-arguments.adoc b/test-files/golden-tests/javadoc/copydoc/template-arguments.adoc
new file mode 100644
index 000000000..7b14abf94
--- /dev/null
+++ b/test-files/golden-tests/javadoc/copydoc/template-arguments.adoc
@@ -0,0 +1,367 @@
+= Reference
+:mrdocs:
+
+[#index]
+== Global namespace
+
+
+=== Namespaces
+
+[cols=1]
+|===
+| Name 
+
+| <<A,`A`>> 
+
+|===
+
+[#A]
+== A
+
+
+=== Types
+
+[cols=2]
+|===
+| Name 
+| Description 
+
+| <<A-B-08,`B`>> 
+| Main class template for B&period;
+
+| <<A-B-09,`B&lt;int&gt;`>> 
+| Specialization of B for int&period;
+
+| <<A-B-0c,`B&lt;int, 2&gt;`>> 
+| Specialization of B for int with value 2&period;
+
+| <<A-C-05,`C`>> 
+| Main class template for C&period;
+
+| <<A-C-0f,`C&lt;D, true&gt;`>> 
+| Specialization of C for D with true&period;
+
+| <<A-C-0c,`C&lt;int, true&gt;`>> 
+| Specialization of C for int with true&period;
+
+| <<A-D,`D`>> 
+| Helper struct D&period;
+
+| <<A-BInt,`BInt`>> 
+| Specialization of B for int&period;
+
+| <<A-BInt2,`BInt2`>> 
+| Specialization of B for int with value 2&period;
+
+| <<A-B_t,`B&lowbar;t`>> 
+| Main class template for B&period;
+
+| <<A-CDTrue,`CDTrue`>> 
+| Specialization of C for D with true&period;
+
+| <<A-CIntTrue,`CIntTrue`>> 
+| Specialization of C for D with true&period;
+
+| <<A-C_t,`C&lowbar;t`>> 
+| Main class template for C&period;
+
+|===
+
+[#A-BInt]
+== <<A,A>>::BInt
+
+
+Specialization of B for int&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+using BInt = <<A-B-09,B>>&lt;int&gt;;
+----
+
+[#A-BInt2]
+== <<A,A>>::BInt2
+
+
+Specialization of B for int with value 2&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+using BInt2 = <<A-B-0c,B>>&lt;int, 2&gt;;
+----
+
+[#A-B_t]
+== <<A,A>>::B&lowbar;t
+
+
+Main class template for B&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+template&lt;
+    class T,
+    int I&gt;
+using B&lowbar;t = <<A-B-08,B>>&lt;T, I&gt;;
+----
+
+=== Template Parameters
+
+
+|===
+| Name | Description
+
+| *T*
+| The type parameter&period;
+
+|===
+
+[#A-CDTrue]
+== <<A,A>>::CDTrue
+
+
+Specialization of C for D with true&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+using CDTrue = <<A-C-0f,C>>&lt;<<A-D,D>>, true&gt;;
+----
+
+[#A-CIntTrue]
+== <<A,A>>::CIntTrue
+
+
+Specialization of C for D with true&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+using CIntTrue = <<A-C-0c,C>>&lt;int, true&gt;;
+----
+
+[#A-C_t]
+== <<A,A>>::C&lowbar;t
+
+
+Main class template for C&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+template&lt;
+    class T,
+    bool B&gt;
+using C&lowbar;t = <<A-C-05,C>>&lt;T, B&gt;;
+----
+
+=== Template Parameters
+
+
+|===
+| Name | Description
+
+| *T*
+| The type parameter&period;
+
+|===
+
+[#A-B-08]
+== <<A,A>>::B
+
+
+Main class template for B&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+template&lt;
+    class T,
+    int = 0&gt;
+struct B;
+----
+
+
+
+
+=== Template Parameters
+
+
+|===
+| Name | Description
+
+| *T*
+| The type parameter&period;
+
+| *int*
+| The integer parameter with a default value of 0&period;
+
+|===
+
+[#A-B-09]
+== <<A,A>>::B&lt;int&gt;
+
+
+Specialization of B for int&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+template&lt;&gt;
+struct <<A-B-08,B>>&lt;int&gt;;
+----
+
+
+
+
+[#A-B-0c]
+== <<A,A>>::B&lt;int, 2&gt;
+
+
+Specialization of B for int with value 2&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+template&lt;&gt;
+struct <<A-B-08,B>>&lt;int, 2&gt;;
+----
+
+
+
+
+[#A-C-05]
+== <<A,A>>::C
+
+
+Main class template for C&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+template&lt;
+    class T,
+    bool = false&gt;
+struct C;
+----
+
+
+
+
+=== Template Parameters
+
+
+|===
+| Name | Description
+
+| *T*
+| The type parameter&period;
+
+| *bool*
+| The boolean parameter with a default value of false&period;
+
+|===
+
+[#A-C-0f]
+== <<A,A>>::C&lt;<<A-D,D>>, true&gt;
+
+
+Specialization of C for D with true&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+template&lt;&gt;
+struct <<A-C-05,C>>&lt;<<A-D,D>>, true&gt;;
+----
+
+
+
+
+[#A-C-0c]
+== <<A,A>>::C&lt;int, true&gt;
+
+
+Specialization of C for int with true&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+template&lt;&gt;
+struct <<A-C-05,C>>&lt;int, true&gt;;
+----
+
+
+
+
+[#A-D]
+== <<A,A>>::D
+
+
+Helper struct D&period;
+
+=== Synopsis
+
+
+Declared in `&lt;template&hyphen;arguments&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+struct D;
+----
+
+
+
+
+
+
+[.small]#Created with https://www.mrdocs.com[MrDocs]#
diff --git a/test-files/golden-tests/javadoc/copydoc/template-arguments.cpp b/test-files/golden-tests/javadoc/copydoc/template-arguments.cpp
new file mode 100644
index 000000000..f749fddf9
--- /dev/null
+++ b/test-files/golden-tests/javadoc/copydoc/template-arguments.cpp
@@ -0,0 +1,61 @@
+namespace A {
+    /** Main class template for B.
+
+         @tparam T The type parameter.
+         @tparam int The integer parameter with a default value of 0.
+     */
+    template <class T, int = 0>
+    struct B;
+
+    /// @copydoc B
+    template <class T, int I>
+    using B_t = B<T, I>;
+
+    /** Specialization of B for int.
+     */
+    template <>
+    struct B<int> {};
+
+    /// @copydoc B<int>
+    using BInt = B<int>;
+
+    /** Specialization of B for int with value 2.
+     */
+    template <>
+    struct B<int, 2> {};
+
+    /// @copydoc B<int, 2>
+    using BInt2 = B<int, 2>;
+
+    /** Main class template for C.
+
+         @tparam T The type parameter.
+         @tparam bool The boolean parameter with a default value of false.
+     */
+    template <class T, bool = false>
+    struct C;
+
+    /// @copydoc C
+    template <class T, bool B>
+    using C_t = C<T, B>;
+
+    /** Specialization of C for int with true.
+     */
+    template <>
+    struct C<int, true> {};
+
+    /// @copydoc C<int, true>
+    using CIntTrue = C<int, true>;
+
+    /** Helper struct D.
+     */
+    struct D;
+
+    /** Specialization of C for D with true.
+     */
+    template <>
+    struct C<D, true> {};
+
+    /// @copydoc C<D, true>
+    using CDTrue = C<D, true>;
+}
\ No newline at end of file
diff --git a/test-files/golden-tests/javadoc/copydoc/template-arguments.html b/test-files/golden-tests/javadoc/copydoc/template-arguments.html
new file mode 100644
index 000000000..8a2176b3b
--- /dev/null
+++ b/test-files/golden-tests/javadoc/copydoc/template-arguments.html
@@ -0,0 +1,460 @@
+<html lang="en">
+<head>
+<title>Reference</title>
+</head>
+<body>
+<div>
+<h1>Reference</h1>
+<div>
+<div>
+<h2 id="index">Global namespace</h2>
+</div>
+<h2>Namespaces</h2>
+<table style="table-layout: fixed; width: 100%;">
+<thead>
+<tr>
+<th>Name</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><a href="#A"><code>A</code></a> </td>
+</tr>
+</tbody>
+</table>
+</div>
+<div>
+<div>
+<h2 id="A">A</h2>
+</div>
+<h2>Types</h2>
+<table style="table-layout: fixed; width: 100%;">
+<thead>
+<tr>
+<th>Name</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><a href="#A-B-08"><code>B</code></a> </td><td><span><span>Main class template for B.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-B-09"><code>B&lt;int&gt;</code></a> </td><td><span><span>Specialization of B for int.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-B-0c"><code>B&lt;int, 2&gt;</code></a> </td><td><span><span>Specialization of B for int with value 2.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-C-05"><code>C</code></a> </td><td><span><span>Main class template for C.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-C-0f"><code>C&lt;D, true&gt;</code></a> </td><td><span><span>Specialization of C for D with true.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-C-0c"><code>C&lt;int, true&gt;</code></a> </td><td><span><span>Specialization of C for int with true.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-D"><code>D</code></a> </td><td><span><span>Helper struct D.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-BInt"><code>BInt</code></a> </td><td><span><span>Specialization of B for int.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-BInt2"><code>BInt2</code></a> </td><td><span><span>Specialization of B for int with value 2.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-B_t"><code>B_t</code></a> </td><td><span><span>Main class template for B.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-CDTrue"><code>CDTrue</code></a> </td><td><span><span>Specialization of C for D with true.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-CIntTrue"><code>CIntTrue</code></a> </td><td><span><span>Specialization of C for D with true.</span></span>
+</td>
+</tr><tr>
+<td><a href="#A-C_t"><code>C_t</code></a> </td><td><span><span>Main class template for C.</span></span>
+</td>
+</tr>
+</tbody>
+</table>
+</div>
+<div>
+<div>
+<h2 id="A-BInt"><a href="#A">A</a>::BInt</h2>
+<div>
+<span><span>Specialization of B for int.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+using BInt = <a href="#A-B-09">B</a>&lt;int&gt;;
+</code>
+</pre>
+</div>
+</div>
+<div>
+<div>
+<h2 id="A-BInt2"><a href="#A">A</a>::BInt2</h2>
+<div>
+<span><span>Specialization of B for int with value 2.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+using BInt2 = <a href="#A-B-0c">B</a>&lt;int, 2&gt;;
+</code>
+</pre>
+</div>
+</div>
+<div>
+<div>
+<h2 id="A-B_t"><a href="#A">A</a>::B_t</h2>
+<div>
+<span><span>Main class template for B.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+template&lt;
+    class T,
+    int I&gt;
+using B_t = <a href="#A-B-08">B</a>&lt;T, I&gt;;
+</code>
+</pre>
+</div>
+<div>
+<h3>Template Parameters</h3>
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><strong>T</strong></td>
+<td><p><span>The type parameter.</span></p>
+</td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div>
+<div>
+<h2 id="A-CDTrue"><a href="#A">A</a>::CDTrue</h2>
+<div>
+<span><span>Specialization of C for D with true.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+using CDTrue = <a href="#A-C-0f">C</a>&lt;<a href="#A-D">D</a>, true&gt;;
+</code>
+</pre>
+</div>
+</div>
+<div>
+<div>
+<h2 id="A-CIntTrue"><a href="#A">A</a>::CIntTrue</h2>
+<div>
+<span><span>Specialization of C for D with true.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+using CIntTrue = <a href="#A-C-0c">C</a>&lt;int, true&gt;;
+</code>
+</pre>
+</div>
+</div>
+<div>
+<div>
+<h2 id="A-C_t"><a href="#A">A</a>::C_t</h2>
+<div>
+<span><span>Main class template for C.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+template&lt;
+    class T,
+    bool B&gt;
+using C_t = <a href="#A-C-05">C</a>&lt;T, B&gt;;
+</code>
+</pre>
+</div>
+<div>
+<h3>Template Parameters</h3>
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><strong>T</strong></td>
+<td><p><span>The type parameter.</span></p>
+</td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div>
+<div>
+<h2 id="A-B-08"><a href="#A">A</a>::B</h2>
+<div>
+<span><span>Main class template for B.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+template&lt;
+    class T,
+    int = 0&gt;
+struct B;
+</code>
+</pre>
+</div>
+
+
+<div>
+<h3>Template Parameters</h3>
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><strong>T</strong></td>
+<td><p><span>The type parameter.</span></p>
+</td>
+</tr>
+<tr>
+<td><strong>int</strong></td>
+<td><p><span>The integer parameter with a default value of 0.</span></p>
+</td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div>
+<div>
+<h2 id="A-B-09"><a href="#A">A</a>::B&lt;int&gt;</h2>
+<div>
+<span><span>Specialization of B for int.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+template&lt;&gt;
+struct <a href="#A-B-08">B</a>&lt;int&gt;;
+</code>
+</pre>
+</div>
+
+
+</div>
+<div>
+<div>
+<h2 id="A-B-0c"><a href="#A">A</a>::B&lt;int, 2&gt;</h2>
+<div>
+<span><span>Specialization of B for int with value 2.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+template&lt;&gt;
+struct <a href="#A-B-08">B</a>&lt;int, 2&gt;;
+</code>
+</pre>
+</div>
+
+
+</div>
+<div>
+<div>
+<h2 id="A-C-05"><a href="#A">A</a>::C</h2>
+<div>
+<span><span>Main class template for C.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+template&lt;
+    class T,
+    bool = false&gt;
+struct C;
+</code>
+</pre>
+</div>
+
+
+<div>
+<h3>Template Parameters</h3>
+<table>
+<thead>
+<tr>
+<th>Name</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><strong>T</strong></td>
+<td><p><span>The type parameter.</span></p>
+</td>
+</tr>
+<tr>
+<td><strong>bool</strong></td>
+<td><p><span>The boolean parameter with a default value of false.</span></p>
+</td>
+</tr>
+</tbody>
+</table>
+</div>
+</div>
+<div>
+<div>
+<h2 id="A-C-0f"><a href="#A">A</a>::C&lt;<a href="#A-D">D</a>, true&gt;</h2>
+<div>
+<span><span>Specialization of C for D with true.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+template&lt;&gt;
+struct <a href="#A-C-05">C</a>&lt;<a href="#A-D">D</a>, true&gt;;
+</code>
+</pre>
+</div>
+
+
+</div>
+<div>
+<div>
+<h2 id="A-C-0c"><a href="#A">A</a>::C&lt;int, true&gt;</h2>
+<div>
+<span><span>Specialization of C for int with true.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+template&lt;&gt;
+struct <a href="#A-C-05">C</a>&lt;int, true&gt;;
+</code>
+</pre>
+</div>
+
+
+</div>
+<div>
+<div>
+<h2 id="A-D"><a href="#A">A</a>::D</h2>
+<div>
+<span><span>Helper struct D.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;template-arguments.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+struct D;
+</code>
+</pre>
+</div>
+
+
+</div>
+
+</div>
+<div>
+<h4>Created with <a href="https://www.mrdocs.com">MrDocs</a></h4>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/test-files/golden-tests/javadoc/copydoc/template-arguments.xml b/test-files/golden-tests/javadoc/copydoc/template-arguments.xml
new file mode 100644
index 000000000..8b092f666
--- /dev/null
+++ b/test-files/golden-tests/javadoc/copydoc/template-arguments.xml
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<mrdocs xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:noNamespaceSchemaLocation="https://github.com/cppalliance/mrdocs/raw/develop/mrdocs.rnc">
+<namespace id="//////////////////////////8=">
+  <namespace name="A" id="jQQu/8mLNzRQvGtbkKMwwloVDpw=">
+    <namespace-alias name="BInt" id="knD7LwHco1Rc3UN13fDrTDK0Fg4=">
+      <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="20"/>
+      <type id="n2ekv7/9IraylJIibujZeExrML4=" name="B&lt;int&gt;"/>
+      <doc>
+        <brief>
+          <text>Specialization of B for int.</text>
+        </brief>
+      </doc>
+    </namespace-alias>
+    <namespace-alias name="BInt2" id="mAmfKzQQ8Mg78wrSV5C5vqhB38M=">
+      <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="28"/>
+      <type id="zWh7HJU4uEOW6SQu1/X6/fBkFq0=" name="B&lt;int, 2&gt;"/>
+      <doc>
+        <brief>
+          <text>Specialization of B for int with value 2.</text>
+        </brief>
+      </doc>
+    </namespace-alias>
+    <template>
+      <tparam name="T" class="type"/>
+      <tparam name="I" class="non-type" type="int"/>
+      <namespace-alias name="B_t" id="40FFJWRtg8LikFmMYkFhi3taM7w=">
+        <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="11"/>
+        <type id="jJtr0x+P2kzEfxAlp7Hjo5akJho=" name="B&lt;T, I&gt;"/>
+        <doc>
+          <brief>
+            <text>Main class template for B.</text>
+          </brief>
+          <tparam name="T">
+            <text>The type parameter.         </text>
+          </tparam>
+        </doc>
+      </namespace-alias>
+    </template>
+    <namespace-alias name="CDTrue" id="EFfBXjCvLk0J5w7aUT/GZ5D4Zww=">
+      <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="60"/>
+      <type id="+AYNfv3bBiQwa6z9A60U/F7dvXE=" name="C&lt;D, true&gt;"/>
+      <doc>
+        <brief>
+          <text>Specialization of C for D with true.</text>
+        </brief>
+      </doc>
+    </namespace-alias>
+    <namespace-alias name="CIntTrue" id="9GvIXSW1TfhlI8qPuV4xPDXT5+A=">
+      <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="48"/>
+      <type id="w5ssdgUOfKNpDbPFUfSYesxFAc8=" name="C&lt;int, true&gt;"/>
+      <doc>
+        <brief>
+          <text>Specialization of C for D with true.</text>
+        </brief>
+      </doc>
+    </namespace-alias>
+    <template>
+      <tparam name="T" class="type"/>
+      <tparam name="B" class="non-type" type="bool"/>
+      <namespace-alias name="C_t" id="N9KZXiVz/IMBW2CcGYh+8cvUeFQ=">
+        <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="39"/>
+        <type id="WjzMcZfRtncsiJEvL1/LzUes/GM=" name="C&lt;T, B&gt;"/>
+        <doc>
+          <brief>
+            <text>Main class template for C.</text>
+          </brief>
+          <tparam name="T">
+            <text>The type parameter.         </text>
+          </tparam>
+        </doc>
+      </namespace-alias>
+    </template>
+    <template>
+      <tparam name="T" class="type"/>
+      <tparam class="non-type" type="int" default="0"/>
+      <struct name="B" id="jJtr0x+P2kzEfxAlp7Hjo5akJho=">
+        <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="7"/>
+        <doc>
+          <brief>
+            <text>Main class template for B.</text>
+          </brief>
+          <tparam name="T">
+            <text>The type parameter.         </text>
+          </tparam>
+          <tparam name="int">
+            <text>The integer parameter with a default value of 0.</text>
+          </tparam>
+        </doc>
+      </struct>
+    </template>
+    <template class="explicit" id="jJtr0x+P2kzEfxAlp7Hjo5akJho=">
+      <targ class="type" type="int"/>
+      <struct name="B" id="n2ekv7/9IraylJIibujZeExrML4=">
+        <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="17" class="def"/>
+        <doc>
+          <brief>
+            <text>Specialization of B for int.</text>
+          </brief>
+        </doc>
+      </struct>
+    </template>
+    <template class="explicit" id="jJtr0x+P2kzEfxAlp7Hjo5akJho=">
+      <targ class="type" type="int"/>
+      <targ class="non-type" value="2"/>
+      <struct name="B" id="zWh7HJU4uEOW6SQu1/X6/fBkFq0=">
+        <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="25" class="def"/>
+        <doc>
+          <brief>
+            <text>Specialization of B for int with value 2.</text>
+          </brief>
+        </doc>
+      </struct>
+    </template>
+    <template>
+      <tparam name="T" class="type"/>
+      <tparam class="non-type" type="bool" default="false"/>
+      <struct name="C" id="WjzMcZfRtncsiJEvL1/LzUes/GM=">
+        <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="35"/>
+        <doc>
+          <brief>
+            <text>Main class template for C.</text>
+          </brief>
+          <tparam name="T">
+            <text>The type parameter.         </text>
+          </tparam>
+          <tparam name="bool">
+            <text>The boolean parameter with a default value of false.</text>
+          </tparam>
+        </doc>
+      </struct>
+    </template>
+    <template class="explicit" id="WjzMcZfRtncsiJEvL1/LzUes/GM=">
+      <targ class="type" type="D"/>
+      <targ class="non-type" value="true"/>
+      <struct name="C" id="+AYNfv3bBiQwa6z9A60U/F7dvXE=">
+        <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="57" class="def"/>
+        <doc>
+          <brief>
+            <text>Specialization of C for D with true.</text>
+          </brief>
+        </doc>
+      </struct>
+    </template>
+    <template class="explicit" id="WjzMcZfRtncsiJEvL1/LzUes/GM=">
+      <targ class="type" type="int"/>
+      <targ class="non-type" value="true"/>
+      <struct name="C" id="w5ssdgUOfKNpDbPFUfSYesxFAc8=">
+        <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="45" class="def"/>
+        <doc>
+          <brief>
+            <text>Specialization of C for int with true.</text>
+          </brief>
+        </doc>
+      </struct>
+    </template>
+    <struct name="D" id="/TldxA72JGXtufh5RbJgxLW2mwU=">
+      <file short-path="template-arguments.cpp" source-path="template-arguments.cpp" line="52"/>
+      <doc>
+        <brief>
+          <text>Helper struct D.</text>
+        </brief>
+      </doc>
+    </struct>
+  </namespace>
+</namespace>
+</mrdocs>
diff --git a/test-files/golden-tests/javadoc/ref/punctuation.adoc b/test-files/golden-tests/javadoc/ref/punctuation.adoc
new file mode 100644
index 000000000..421c60b5e
--- /dev/null
+++ b/test-files/golden-tests/javadoc/ref/punctuation.adoc
@@ -0,0 +1,75 @@
+= Reference
+:mrdocs:
+
+[#index]
+== Global namespace
+
+
+=== Functions
+
+[cols=2]
+|===
+| Name 
+| Description 
+
+| <<f0,`f0`>> 
+| 
+
+| <<f1,`f1`>> 
+| 
+
+| <<f2,`f2`>> 
+| See xref:#f0[f0], xref:#f1[f1]&period;
+
+|===
+
+[#f0]
+== f0
+
+
+=== Synopsis
+
+
+Declared in `&lt;punctuation&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+void
+f0();
+----
+
+[#f1]
+== f1
+
+
+=== Synopsis
+
+
+Declared in `&lt;punctuation&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+void
+f1();
+----
+
+[#f2]
+== f2
+
+
+See xref:#f0[f0], xref:#f1[f1]&period;
+
+=== Synopsis
+
+
+Declared in `&lt;punctuation&period;cpp&gt;`
+
+[source,cpp,subs="verbatim,replacements,macros,-callouts"]
+----
+void
+f2();
+----
+
+
+
+[.small]#Created with https://www.mrdocs.com[MrDocs]#
diff --git a/test-files/golden-tests/javadoc/ref/punctuation.cpp b/test-files/golden-tests/javadoc/ref/punctuation.cpp
new file mode 100644
index 000000000..934603a32
--- /dev/null
+++ b/test-files/golden-tests/javadoc/ref/punctuation.cpp
@@ -0,0 +1,9 @@
+void f0();
+
+void f1();
+
+/**
+    See @ref f0, @ref f1.
+*/
+void f2();
+
diff --git a/test-files/golden-tests/javadoc/ref/punctuation.html b/test-files/golden-tests/javadoc/ref/punctuation.html
new file mode 100644
index 000000000..114387e11
--- /dev/null
+++ b/test-files/golden-tests/javadoc/ref/punctuation.html
@@ -0,0 +1,91 @@
+<html lang="en">
+<head>
+<title>Reference</title>
+</head>
+<body>
+<div>
+<h1>Reference</h1>
+<div>
+<div>
+<h2 id="index">Global namespace</h2>
+</div>
+<h2>Functions</h2>
+<table style="table-layout: fixed; width: 100%;">
+<thead>
+<tr>
+<th>Name</th>
+<th>Description</th>
+</tr>
+</thead>
+<tbody>
+<tr>
+<td><a href="#f0"><code>f0</code></a> </td><td></td>
+</tr><tr>
+<td><a href="#f1"><code>f1</code></a> </td><td></td>
+</tr><tr>
+<td><a href="#f2"><code>f2</code></a> </td><td><span><span>See </span><a href="#f0"><code>f0</code></a> <span>, </span><a href="#f1"><code>f1</code></a> <span>.</span></span>
+</td>
+</tr>
+</tbody>
+</table>
+</div>
+<div>
+<div>
+<h2 id="f0">f0</h2>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;punctuation.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+void
+f0();
+</code>
+</pre>
+</div>
+</div>
+<div>
+<div>
+<h2 id="f1">f1</h2>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;punctuation.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+void
+f1();
+</code>
+</pre>
+</div>
+</div>
+<div>
+<div>
+<h2 id="f2">f2</h2>
+<div>
+<span><span>See </span><a href="#f0"><code>f0</code></a> <span>, </span><a href="#f1"><code>f1</code></a> <span>.</span></span>
+
+
+</div>
+</div>
+<div>
+<h3>Synopsis</h3>
+<div>
+Declared in <code>&lt;punctuation.cpp&gt;</code></div>
+<pre>
+<code class="source-code cpp">
+void
+f2();
+</code>
+</pre>
+</div>
+</div>
+
+</div>
+<div>
+<h4>Created with <a href="https://www.mrdocs.com">MrDocs</a></h4>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/test-files/golden-tests/javadoc/ref/punctuation.xml b/test-files/golden-tests/javadoc/ref/punctuation.xml
new file mode 100644
index 000000000..6b9f41ff6
--- /dev/null
+++ b/test-files/golden-tests/javadoc/ref/punctuation.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<mrdocs xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:noNamespaceSchemaLocation="https://github.com/cppalliance/mrdocs/raw/develop/mrdocs.rnc">
+<namespace id="//////////////////////////8=">
+  <function name="f0" id="e1UQQek5v3C9OClW5cGf57XvwQo=">
+    <file short-path="punctuation.cpp" source-path="punctuation.cpp" line="1"/>
+  </function>
+  <function name="f1" id="CnO51rIKTzfiVKHkR3TdPa0eo+8=">
+    <file short-path="punctuation.cpp" source-path="punctuation.cpp" line="3"/>
+  </function>
+  <function name="f2" id="0MJUv5yGFR9nXWFLeYc+rjOY+iM=">
+    <file short-path="punctuation.cpp" source-path="punctuation.cpp" line="8"/>
+    <doc>
+      <brief>
+        <text>See </text>
+        <reference id="e1UQQek5v3C9OClW5cGf57XvwQo=">f0</reference>
+        <text>, </text>
+        <reference id="CnO51rIKTzfiVKHkR3TdPa0eo+8=">f1</reference>
+        <text>.</text>
+      </brief>
+    </doc>
+  </function>
+</namespace>
+</mrdocs>