[clang-doc] Add a breadcrumb navigation bar (#173297)

This patch adds a breadcrumb navigation bar to the `<navbar>` element. Now, you can navigate between the different scopes of a record or namespace. This is done by keeping track of a Decl's parent Decl through its USR. That allows us to traverse the set of `Info`s through a directed graph during JSON generation to create `Context`s. A context is just a `Reference` that holds a relative path to a scope's file from a particular `Info`.
This commit is contained in:
Erick Velez 2026-01-07 13:35:35 -08:00 committed by GitHub
parent 4998280c3f
commit abee8a8e13
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 258 additions and 15 deletions

View File

@ -159,6 +159,8 @@ static llvm::Error parseRecord(const Record &R, unsigned ID,
return decodeRecord(R, I->Name, Blob);
case NAMESPACE_PATH:
return decodeRecord(R, I->Path, Blob);
case NAMESPACE_PARENT_USR:
return decodeRecord(R, I->ParentUSR, Blob);
default:
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"invalid field for NamespaceInfo");
@ -184,6 +186,8 @@ static llvm::Error parseRecord(const Record &R, unsigned ID,
return decodeRecord(R, I->IsTypeDef, Blob);
case RECORD_MANGLED_NAME:
return decodeRecord(R, I->MangledName, Blob);
case RECORD_PARENT_USR:
return decodeRecord(R, I->ParentUSR, Blob);
default:
return llvm::createStringError(llvm::inconvertibleErrorCode(),
"invalid field for RecordInfo");

View File

@ -174,6 +174,7 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
{NAMESPACE_USR, {"USR", &genSymbolIdAbbrev}},
{NAMESPACE_NAME, {"Name", &genStringAbbrev}},
{NAMESPACE_PATH, {"Path", &genStringAbbrev}},
{NAMESPACE_PARENT_USR, {"ParentUSR", &genSymbolIdAbbrev}},
{ENUM_USR, {"USR", &genSymbolIdAbbrev}},
{ENUM_NAME, {"Name", &genStringAbbrev}},
{ENUM_DEFLOCATION, {"DefLocation", &genLocationAbbrev}},
@ -190,6 +191,7 @@ static const llvm::IndexedMap<RecordIdDsc, RecordIdToIndexFunctor>
{RECORD_TAG_TYPE, {"TagType", &genIntAbbrev}},
{RECORD_IS_TYPE_DEF, {"IsTypeDef", &genBoolAbbrev}},
{RECORD_MANGLED_NAME, {"MangledName", &genStringAbbrev}},
{RECORD_PARENT_USR, {"ParentUSR", &genSymbolIdAbbrev}},
{BASE_RECORD_USR, {"USR", &genSymbolIdAbbrev}},
{BASE_RECORD_NAME, {"Name", &genStringAbbrev}},
{BASE_RECORD_PATH, {"Path", &genStringAbbrev}},
@ -270,12 +272,12 @@ static const std::vector<std::pair<BlockId, std::vector<RecordId>>>
{TYPEDEF_USR, TYPEDEF_NAME, TYPEDEF_DEFLOCATION, TYPEDEF_IS_USING}},
// Namespace Block
{BI_NAMESPACE_BLOCK_ID,
{NAMESPACE_USR, NAMESPACE_NAME, NAMESPACE_PATH}},
{NAMESPACE_USR, NAMESPACE_NAME, NAMESPACE_PATH, NAMESPACE_PARENT_USR}},
// Record Block
{BI_RECORD_BLOCK_ID,
{RECORD_USR, RECORD_NAME, RECORD_PATH, RECORD_DEFLOCATION,
RECORD_LOCATION, RECORD_TAG_TYPE, RECORD_IS_TYPE_DEF,
RECORD_MANGLED_NAME}},
RECORD_MANGLED_NAME, RECORD_PARENT_USR}},
// BaseRecord Block
{BI_BASE_RECORD_BLOCK_ID,
{BASE_RECORD_USR, BASE_RECORD_NAME, BASE_RECORD_PATH,
@ -570,6 +572,7 @@ void ClangDocBitcodeWriter::emitBlock(const NamespaceInfo &I) {
emitRecord(I.USR, NAMESPACE_USR);
emitRecord(I.Name, NAMESPACE_NAME);
emitRecord(I.Path, NAMESPACE_PATH);
emitRecord(I.ParentUSR, NAMESPACE_PARENT_USR);
for (const auto &N : I.Namespace)
emitBlock(N, FieldId::F_namespace);
for (const auto &CI : I.Description)
@ -624,6 +627,7 @@ void ClangDocBitcodeWriter::emitBlock(const RecordInfo &I) {
emitRecord(I.Name, RECORD_NAME);
emitRecord(I.Path, RECORD_PATH);
emitRecord(I.MangledName, RECORD_MANGLED_NAME);
emitRecord(I.ParentUSR, RECORD_PARENT_USR);
for (const auto &N : I.Namespace)
emitBlock(N, FieldId::F_namespace);
for (const auto &CI : I.Description)

View File

@ -108,6 +108,7 @@ enum RecordId {
NAMESPACE_USR,
NAMESPACE_NAME,
NAMESPACE_PATH,
NAMESPACE_PARENT_USR,
ENUM_USR,
ENUM_NAME,
ENUM_DEFLOCATION,
@ -124,6 +125,7 @@ enum RecordId {
RECORD_TAG_TYPE,
RECORD_IS_TYPE_DEF,
RECORD_MANGLED_NAME,
RECORD_PARENT_USR,
BASE_RECORD_USR,
BASE_RECORD_NAME,
BASE_RECORD_PATH,

View File

@ -278,6 +278,64 @@ static Object serializeComment(const CommentInfo &I, Object &Description) {
llvm_unreachable("Unknown comment kind encountered.");
}
/// Creates Contexts for namespaces and records to allow for navigation.
static void generateContext(const Info &I, Object &Obj) {
json::Value ContextArray = json::Array();
auto &ContextArrayRef = *ContextArray.getAsArray();
ContextArrayRef.reserve(I.Contexts.size());
std::string CurrentRelativePath;
bool PreviousRecord = false;
for (const auto &Current : I.Contexts) {
json::Value ContextVal = Object();
Object &Context = *ContextVal.getAsObject();
serializeReference(Current, Context);
if (ContextArrayRef.empty() && I.IT == InfoType::IT_record) {
if (Current.DocumentationFileName == "index") {
// If the record's immediate context is a namespace, then the
// "index.html" is in the same directory.
PreviousRecord = false;
Context["RelativePath"] = "./";
} else {
// If the immediate context is a record, then the file is one level
// above
PreviousRecord = true;
CurrentRelativePath += "../";
Context["RelativePath"] = CurrentRelativePath;
}
ContextArrayRef.push_back(ContextVal);
continue;
}
if (PreviousRecord && (Current.DocumentationFileName == "index")) {
// If the previous Context was a record then we already went up a level,
// so the current namespace index is in the same directory.
PreviousRecord = false;
} else if (Current.DocumentationFileName != "index") {
// If the current Context is a record but the previous wasn't a record,
// then the namespace index is located one level above.
PreviousRecord = true;
CurrentRelativePath += "../";
} else {
// The current Context is a namespace and so was the previous Context.
PreviousRecord = false;
CurrentRelativePath += "../";
// If this namespace is the global namespace, then its documentation
// name needs to be changed to link correctly.
if (Current.QualName == "GlobalNamespace" && Current.RelativePath != "./")
Context["DocumentationFileName"] =
SmallString<16>("GlobalNamespace/index");
}
Context["RelativePath"] = CurrentRelativePath;
ContextArrayRef.insert(ContextArrayRef.begin(), ContextVal);
}
ContextArrayRef.back().getAsObject()->insert({"End", true});
Obj["Contexts"] = ContextArray;
Obj["HasContexts"] = true;
}
static void
serializeCommonAttributes(const Info &I, json::Object &Obj,
const std::optional<StringRef> RepositoryUrl) {
@ -323,6 +381,9 @@ serializeCommonAttributes(const Info &I, json::Object &Obj,
Obj["Location"] =
serializeLocation(Symbol->DefLoc.value(), RepositoryUrl);
}
if (!I.Contexts.empty())
generateContext(I, Obj);
}
static void serializeReference(const Reference &Ref, Object &ReferenceObj) {
@ -335,7 +396,7 @@ static void serializeReference(const Reference &Ref, Object &ReferenceObj) {
// If the reference is a nested class it will be put into a folder named
// after the parent class. We can get that name from the path's stem.
if (Ref.Path != "GlobalNamespace")
if (Ref.Path != "GlobalNamespace" && !Ref.Path.empty())
ReferenceObj["PathStem"] = sys::path::stem(Ref.Path);
}
}
@ -730,6 +791,29 @@ static Error serializeIndex(const ClangDocContext &CDCtx, StringRef RootDir) {
return Error::success();
}
static void serializeContexts(Info *I,
StringMap<std::unique_ptr<Info>> &Infos) {
if (I->USR == GlobalNamespaceID)
return;
auto ParentUSR = I->ParentUSR;
while (true) {
auto &ParentInfo = Infos.at(llvm::toHex(ParentUSR));
if (ParentInfo && ParentInfo->USR == GlobalNamespaceID) {
Context GlobalRef(ParentInfo->USR, "Global Namespace",
InfoType::IT_namespace, "GlobalNamespace", "",
SmallString<16>("index"));
I->Contexts.push_back(GlobalRef);
return;
}
Context ParentRef(*ParentInfo);
I->Contexts.push_back(ParentRef);
ParentUSR = ParentInfo->ParentUSR;
}
}
Error JSONGenerator::generateDocumentation(
StringRef RootDir, llvm::StringMap<std::unique_ptr<doc::Info>> Infos,
const ClangDocContext &CDCtx, std::string DirName) {
@ -763,9 +847,12 @@ Error JSONGenerator::generateDocumentation(
if (FileErr)
return createFileError("cannot open file " + Group.getKey(), FileErr);
for (const auto &Info : Group.getValue())
for (const auto &Info : Group.getValue()) {
if (Info->IT == InfoType::IT_record || Info->IT == InfoType::IT_namespace)
serializeContexts(Info, Infos);
if (Error Err = generateDocForInfo(Info, InfoOS, CDCtx))
return Err;
}
}
return serializeIndex(CDCtx, RootDir);

View File

@ -273,6 +273,10 @@ void Info::mergeBase(Info &&Other) {
llvm::sort(Description);
auto Last = llvm::unique(Description);
Description.erase(Last, Description.end());
if (ParentUSR == EmptySID)
ParentUSR = Other.ParentUSR;
if (DocumentationFileName.empty())
DocumentationFileName = Other.DocumentationFileName;
}
bool Info::mergeable(const Info &Other) {

View File

@ -165,6 +165,16 @@ struct Reference {
SmallString<16> DocumentationFileName;
};
// A Context is a reference that holds a relative path from a certain Info's
// location.
struct Context : public Reference {
Context(SymbolID USR, StringRef Name, InfoType IT, StringRef QualName,
StringRef Path, SmallString<16> DocumentationFileName)
: Reference(USR, Name, IT, QualName, Path, DocumentationFileName) {}
explicit Context(const Info &I);
SmallString<128> RelativePath;
};
// Holds the children of a record or namespace.
struct ScopeChildren {
// Namespaces and Records are references because they will be properly
@ -356,13 +366,21 @@ struct Info {
// Unique identifier for the decl described by this Info.
SymbolID USR = SymbolID();
// Currently only used for namespaces and records.
SymbolID ParentUSR = SymbolID();
// InfoType of this particular Info.
InfoType IT = InfoType::IT_default;
// Comment description of this decl.
std::vector<CommentInfo> Description;
SmallVector<Context, 4> Contexts;
};
inline Context::Context(const Info &I)
: Reference(I.USR, I.Name, I.IT, I.Name, I.Path, I.DocumentationFileName) {}
// Info for namespaces.
struct NamespaceInfo : public Info {
NamespaceInfo(SymbolID USR = SymbolID(), StringRef Name = StringRef(),

View File

@ -697,10 +697,46 @@ static TemplateParamInfo convertTemplateArgToInfo(const clang::Decl *D,
return TemplateParamInfo(Str);
}
// Check if the DeclKind is one for which we support contextual relationships.
// There might be other ContextDecls, like blocks, that we currently don't
// handle at all.
static bool isSupportedContext(Decl::Kind DeclKind) {
switch (DeclKind) {
case Decl::Kind::Record:
case Decl::Kind::CXXRecord:
case Decl::Kind::ClassTemplateSpecialization:
case Decl::Kind::ClassTemplatePartialSpecialization:
case Decl::Kind::Namespace:
return true;
default:
return false;
}
}
static void findParent(Info &I, const Decl *D) {
assert(D && "Invalid Decl");
// Only walk up contexts if D is a record or namespace.
if (!isSupportedContext(D->getKind()))
return;
const DeclContext *ParentCtx = dyn_cast<DeclContext>(D)->getLexicalParent();
while (ParentCtx) {
if (isSupportedContext(ParentCtx->getDeclKind())) {
// Break when we reach the first record or namespace.
I.ParentUSR = getUSRForDecl(dyn_cast<Decl>(ParentCtx));
break;
}
ParentCtx = ParentCtx->getParent();
}
}
template <typename T>
static void populateInfo(Info &I, const T *D, const FullComment *C,
bool &IsInAnonymousNamespace) {
I.USR = getUSRForDecl(D);
findParent(I, D);
if (auto ConversionDecl = dyn_cast_or_null<CXXConversionDecl>(D);
ConversionDecl && ConversionDecl->getConversionType()
.getTypePtr()

View File

@ -112,7 +112,6 @@ body, html {
width: 100%;
top: 0;
left: 0;
height: 60px; /* Adjust as needed */
color: white;
display: flex;
align-items: center;
@ -255,6 +254,38 @@ body, html {
color:var(--text1)
}
.navbar-breadcrumb-container {
position: absolute;
top: 60px;
left: 0;
width: 100%;
background: var(--surface2);
padding: 0.5rem 1rem;
display: flex;
gap: 0.5rem;
border-bottom: 1px solid var(--text2);
box-sizing: border-box;
border-top: 1px solid var(--text2);
border-bottom: 1px solid var(--text2);
}
.navbar-breadcrumb-item {
padding: 0.25rem 0.75rem;
background: var(--surface1);
border: 1px solid var(--text2);
border-radius: 4px;
color: var(--text1);
font-size: 0.9rem;
white-space: nowrap;
}
.navbar-breadcrumb-item:hover {
background: var(--brand);
color: var(--text1-inverse);
border-color: var(--brand);
cursor: pointer;
}
.hero__container {
margin-top:1rem;
display:flex;
@ -317,9 +348,7 @@ body, html {
max-width: 2048px;
margin-left:auto;
margin-right:auto;
margin-top:0;
margin-bottom: 1rem;
padding:1rem 2rem
}
@media(max-width:768px) {
@ -404,9 +433,9 @@ body, html {
.sidebar {
width: 250px;
top: 60px;
left: 0;
height: 100%;
top: 60px;
bottom: 0;
position: fixed;
background-color: var(--surface1);
display: flex;
@ -414,6 +443,7 @@ body, html {
flex-direction: column;
overflow-y: auto;
scrollbar-width: thin;
flex-shrink: 0;
}
.sidebar h2 {
@ -445,8 +475,8 @@ body, html {
/* Content */
.content {
top: 60px;
background-color: var(--text1-inverse);
padding: 20px;
left: 250px;
position: relative;
width: calc(100% - 250px);

View File

@ -12,5 +12,12 @@
</li>
</ul>
</div>
{{#HasContexts}}
<div class="navbar-breadcrumb-container">
{{#Contexts}}
<a href="{{RelativePath}}{{DocumentationFileName}}.html"><div class="navbar-breadcrumb-item">{{Name}}</div></a>{{^End}}::{{/End}}
{{/Contexts}}
</div>
{{/HasContexts}}
</div>
</nav>

View File

@ -29,6 +29,9 @@ HTML-SHAPE: <a href="../index.html" class="navbar__link">Hom
HTML-SHAPE: </li>
HTML-SHAPE: </ul>
HTML-SHAPE: </div>
HTML-SHAPE: <div class="navbar-breadcrumb-container">
HTML-SHAPE: <a href="./index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>
HTML-SHAPE: </div>
HTML-SHAPE: </div>
HTML-SHAPE: </nav>
HTML-SHAPE: <main>
@ -136,6 +139,9 @@ HTML-CALC: <a href="../index.html" class="navbar__link">Home
HTML-CALC: </li>
HTML-CALC: </ul>
HTML-CALC: </div>
HTML-CALC: <div class="navbar-breadcrumb-container">
HTML-CALC: <a href="./index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>
HTML-CALC: </div>
HTML-CALC: </div>
HTML-CALC: </nav>
HTML-CALC: <main>
@ -347,6 +353,9 @@ HTML-RECTANGLE: <a href="../index.html" class="navbar__link"
HTML-RECTANGLE: </li>
HTML-RECTANGLE: </ul>
HTML-RECTANGLE: </div>
HTML-RECTANGLE: <div class="navbar-breadcrumb-container">
HTML-RECTANGLE: <a href="./index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>
HTML-RECTANGLE: </div>
HTML-RECTANGLE: </div>
HTML-RECTANGLE: </nav>
HTML-RECTANGLE: <main>
@ -458,6 +467,9 @@ HTML-CIRCLE: <a href="../index.html" class="navbar__link">Ho
HTML-CIRCLE: </li>
HTML-CIRCLE: </ul>
HTML-CIRCLE: </div>
HTML-CIRCLE: <div class="navbar-breadcrumb-container">
HTML-CIRCLE: <a href="./index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>
HTML-CIRCLE: </div>
HTML-CIRCLE: </div>
HTML-CIRCLE: </nav>
HTML-CIRCLE: <main>

View File

@ -38,6 +38,16 @@ private:
};
// CHECK: {
// CHECK-NEXT: "Contexts": [
// CHECK-NEXT: {
// CHECK-NEXT: "DocumentationFileName": "index",
// CHECK-NEXT: "End": true,
// CHECK-NEXT: "Name": "Global Namespace",
// CHECK-NEXT: "QualName": "GlobalNamespace",
// CHECK-NEXT: "RelativePath": "./",
// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "Description": {
// CHECK-NEXT: "BriefComments": [
// CHECK-NEXT: [
@ -156,6 +166,7 @@ private:
// CHECK-NEXT: "USR": "0000000000000000000000000000000000000000"
// CHECK-NEXT: }
// CHECK-NEXT: ],
// CHECK-NEXT: "HasContexts": true,
// CHECK-NEXT: "HasEnums": true,
// CHECK-NEXT: "HasFriends": true,
// CHECK-NEXT: "HasPrivateMembers": true,

View File

@ -1,5 +1,6 @@
// RUN: rm -rf %t && mkdir -p %t
// RUN: clang-doc --output=%t --format=json --executor=standalone %s
// RUN: clang-doc --output=%t --format=html --executor=standalone %s
// RUN: FileCheck %s < %t/json/nested/index.json --check-prefix=NESTED
// RUN: FileCheck %s < %t/json/nested/inner/index.json --check-prefix=INNER
@ -7,6 +8,7 @@ namespace nested {
int Global;
namespace inner {
int InnerGlobal;
namespace inner_inner {}
} // namespace inner
} // namespace nested
@ -17,7 +19,7 @@ namespace nested {
// NESTED-NEXT: "IsStatic": false,
// NESTED-NEXT: "Location": {
// NESTED-NEXT: "Filename": "{{.*}}nested-namespace.cpp",
// NESTED-NEXT: "LineNumber": 7
// NESTED-NEXT: "LineNumber": 8
// NESTED-NEXT: },
// NESTED-NEXT: "Name": "Global",
// NESTED-NEXT: "Namespace": [
@ -31,7 +33,7 @@ namespace nested {
// INNER-NEXT: "IsStatic": false,
// INNER-NEXT: "Location": {
// INNER-NEXT: "Filename": "{{.*}}nested-namespace.cpp",
// INNER-NEXT: "LineNumber": 9
// INNER-NEXT: "LineNumber": 10
// INNER-NEXT: },
// INNER-NEXT: "Name": "InnerGlobal",
// INNER-NEXT: "Namespace": [

View File

@ -63,6 +63,9 @@ class AnonClass {};
// MD-ANON-INDEX: ### anonFunction
// MD-ANON-INDEX: *void anonFunction()*
// HTML-ANON-INDEX: <div class="navbar-breadcrumb-container">
// HTML-ANON-INDEX: <a href="../GlobalNamespace/index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>
// HTML-ANON-INDEX: </div>
// HTML-ANON-INDEX: <h2>@nonymous_namespace</h2>
// HTML-ANON-INDEX: <h2>Inner Classes</h2>
// HTML-ANON-INDEX: <ul class="class-container">
@ -90,6 +93,10 @@ class ClassInPrimaryNamespace {};
// MD-PRIMARY-CLASS: # class ClassInPrimaryNamespace
// MD-PRIMARY-CLASS: Class in PrimaryNamespace
// HTML-PRIMARY-CLASS: <div class="navbar-breadcrumb-container">
// HTML-PRIMARY-CLASS: <a href="../GlobalNamespace/index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>::
// HTML-PRIMARY-CLASS: <a href="./index.html"><div class="navbar-breadcrumb-item">PrimaryNamespace</div></a>
// HTML-PRIMARY-CLASS: </div>
// HTML-PRIMARY-CLASS: <h1 class="hero__title-large">class ClassInPrimaryNamespace</h1>
// Nested namespace
@ -107,6 +114,11 @@ class ClassInNestedNamespace {};
// MD-NESTED-CLASS: # class ClassInNestedNamespace
// MD-NESTED-CLASS: Class in NestedNamespace
// HTML-NESTED-CLASS: <div class="navbar-breadcrumb-container">
// HTML-NESTED-CLASS: <a href="../../GlobalNamespace/index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>::
// HTML-NESTED-CLASS: <a href="../index.html"><div class="navbar-breadcrumb-item">PrimaryNamespace</div></a>::
// HTML-NESTED-CLASS: <a href="./index.html"><div class="navbar-breadcrumb-item">NestedNamespace</div></a>
// HTML-NESTED-CLASS: </div>
// HTML-NESTED-CLASS: <h1 class="hero__title-large">class ClassInNestedNamespace</h1>
} // namespace NestedNamespace
@ -119,6 +131,10 @@ class ClassInNestedNamespace {};
// MD-NESTED-INDEX: *void functionInNestedNamespace()*
// MD-NESTED-INDEX: Function in NestedNamespace
// HTML-NESTED-INDEX: <div class="navbar-breadcrumb-container">
// HTML-NESTED-INDEX: <a href="../../GlobalNamespace/index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>::
// HTML-NESTED-INDEX: <a href="../index.html"><div class="navbar-breadcrumb-item">PrimaryNamespace</div></a>
// HTML-NESTED-INDEX: </div>
// HTML-NESTED-INDEX: <h2>NestedNamespace</h2>
// HTML-NESTED-INDEX: <h2>Inner Classes</h2>
// HTML-NESTED-INDEX: <ul class="class-container">
@ -134,7 +150,7 @@ class ClassInNestedNamespace {};
// HTML-NESTED-INDEX: <p> Function in NestedNamespace</p>
// HTML-NESTED-INDEX: </div>
// HTML-NESTED-INDEX: </div>
// HTML-NESTED-INDEX: <p>Defined at line 98 of file {{.*}}namespace.cpp</p>
// HTML-NESTED-INDEX: <p>Defined at line 105 of file {{.*}}namespace.cpp</p>
// HTML-NESTED-INDEX: </div>
} // namespace PrimaryNamespace
@ -149,6 +165,9 @@ class ClassInNestedNamespace {};
// MD-PRIMARY-INDEX: *void functionInPrimaryNamespace()*
// MD-PRIMARY-INDEX: Function in PrimaryNamespace
// HTML-PRIMARY-INDEX: <div class="navbar-breadcrumb-container">
// HTML-PRIMARY-INDEX: <a href="../GlobalNamespace/index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>
// HTML-PRIMARY-INDEX: </div>
// HTML-PRIMARY-INDEX: <h2>PrimaryNamespace</h2>
// HTML-PRIMARY-INDEX-NOT: <h2 id="Namespaces">Namespaces</h2>
// HTML-PRIMARY-INDEX-NOT: <a href="NestedNamespace{{[\/]}}index.html">NestedNamespace</a>
@ -166,7 +185,7 @@ class ClassInNestedNamespace {};
// HTML-PRIMARY-INDEX: <p> Function in PrimaryNamespace</p>
// HTML-PRIMARY-INDEX: </div>
// HTML-PRIMARY-INDEX: </div>
// HTML-PRIMARY-INDEX: <p>Defined at line 81 of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp</p>
// HTML-PRIMARY-INDEX: <p>Defined at line 84 of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp</p>
// HTML-PRIMARY-INDEX: </div>
// AnotherNamespace
namespace AnotherNamespace {
@ -183,6 +202,10 @@ class ClassInAnotherNamespace {};
// MD-ANOTHER-CLASS: # class ClassInAnotherNamespace
// MD-ANOTHER-CLASS: Class in AnotherNamespace
// HTML-ANOTHER-CLASS: <div class="navbar-breadcrumb-container">
// HTML-ANOTHER-CLASS: <a href="../GlobalNamespace/index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>::
// HTML-ANOTHER-CLASS: <a href="./index.html"><div class="navbar-breadcrumb-item">AnotherNamespace</div></a>
// HTML-ANOTHER-CLASS: </div>
// HTML-ANOTHER-CLASS: <h1 class="hero__title-large">class ClassInAnotherNamespace</h1>
} // namespace AnotherNamespace
@ -196,6 +219,9 @@ class ClassInAnotherNamespace {};
// MD-ANOTHER-INDEX: *void functionInAnotherNamespace()*
// MD-ANOTHER-INDEX: Function in AnotherNamespace
// HTML-ANOTHER-INDEX: <div class="navbar-breadcrumb-container">
// HTML-ANOTHER-INDEX: <a href="../GlobalNamespace/index.html"><div class="navbar-breadcrumb-item">Global Namespace</div></a>
// HTML-ANOTHER-INDEX: </div>
// HTML-ANOTHER-INDEX: <h2>AnotherNamespace</h2>
// HTML-ANOTHER-INDEX: <h2>Inner Classes</h2>
// HTML-ANOTHER-INDEX: <ul class="class-container">
@ -211,7 +237,7 @@ class ClassInAnotherNamespace {};
// HTML-ANOTHER-INDEX: <p> Function in AnotherNamespace</p>
// HTML-ANOTHER-INDEX: </div>
// HTML-ANOTHER-INDEX: </div>
// HTML-ANOTHER-INDEX: <p>Defined at line 174 of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp</p>
// HTML-ANOTHER-INDEX: <p>Defined at line 193 of file {{.*}}clang-tools-extra{{[\/]}}test{{[\/]}}clang-doc{{[\/]}}namespace.cpp</p>
// HTML-ANOTHER-INDEX: </div>
// HTML-ANOTHER-INDEX: </div>