Better C++ Syntax Highlighting - Part 3: Namespaces

· Updated Jul 14, 2025

Namespaces are up next. Their declarations and references appear frequently in C++ code, and Clang exposes several node types for processing them.

Consider the following example:

cpp
namespace math {
namespace utility {
// ...
}
}
int main() {
using namespace math;
namespace utils = math::utility;
// ...
}

And corresponding AST:

text
|-NamespaceDecl 0x1cce3be79e8 <example.cpp:1:1, line:5:1> line:1:11 math
| `-NamespaceDecl 0x1cce3be7a78 <line:2:5, line:4:5> line:2:15 utility
`-FunctionDecl 0x1cce540bb68 <line:7:1, line:12:1> line:7:5 main 'int ()'
`-CompoundStmt 0x1cce540bdc0 <col:12, line:12:1>
|-DeclStmt 0x1cce540bd08 <line:8:5, col:25>
| `-UsingDirectiveDecl 0x1cce540bcb0 <col:5, col:21> col:21 Namespace 0x1cce3be79e8 'math'
`-DeclStmt 0x1cce540bda8 <line:9:5, col:36>
`-NamespaceAliasDecl 0x1cce540bd48 <col:5, col:29> col:15 utils
`-Namespace 0x1cce3be7a78 'utility'

To annotate namespaces, we’ll define visitor functions for three new node types:

cpp
bool VisitNamespaceDecl(clang::NamespaceDecl* node);
bool VisitNamespaceAliasDecl(clang::NamespaceAliasDecl* node);
bool VisitUsingDirectiveDecl(clang::UsingDirectiveDecl* node);

Namespace declarations

NamespaceDecl nodes represent standard namespace declarations. We’ll annotate these with a namespace-name tag:

cpp
bool Visitor::VisitNamespaceDecl(clang::NamespaceDecl* node) {
// Check to ensure this node originates from the file we are annotating
// ...
const std::string& name = node->getNameAsString();
const clang::SourceLocation& source_location = node->getLocation();
unsigned line = source_manager.getSpellingLineNumber(source_location);
unsigned column = source_manager.getSpellingColumnNumber(source_location);
m_annotator->insert_annotation("namespace-name", line, column, name.length());
return true;
}

With this visitor implemented, our tool produces the following output:

text
+
namespace [[namespace-name,math]] {
+
namespace [[namespace-name,utility]] {
 
// ...
 
}
 
}
 
 
int main() {
 
using namespace math;
 
namespace utils = math::utility;
 
 
// ...
 
}

Namespace aliases

For NamespaceAliasDecl nodes, which represent namespace aliases, we’ll annotate both the alias and the referenced namespaces. First, we annotate the alias:

cpp
std::string name = node->getNameAsString();
clang::SourceLocation location = node->getAliasLoc();
unsigned line = source_manager.getSpellingLineNumber(location);
unsigned column = source_manager.getSpellingColumnNumber(location);
m_annotator->insert_annotation("namespace-name", line, column, name.length());

The name and source location of the alias is retrieved from the NamespaceAliasDecl node itself. The location at which to insert annotations is retrieved via getAliasLoc().

Next, we annotate the namespace being aliased:

cpp
const clang::NamedDecl* aliased = node->getAliasedNamespace();
std::string name = aliased->getNameAsString();
clang::SourceLocation location = node->getTargetNameLoc();
unsigned line = source_manager.getSpellingLineNumber(location);
unsigned column = source_manager.getSpellingColumnNumber(location);
m_annotator->insert_annotation("namespace-name", line, column, name.length());

As this references an already existing namespace, its name can be retrieved from its declaration using the getAliasedNamespace() function. Its location is retrieved via getTargetNameLoc().

Both the alias and target namespaces are annotated with the namespace-name tag.

text
 
namespace math {
 
namespace utility {
 
// ...
 
}
 
}
 
 
int main() {
 
using namespace math;
+
namespace [[namespace-name,utils]] = math::[[namespace-name,utility]];
 
 
// ...
 
}

using namespace directives

The UsingDirectiveDecl node handles using namespace directives. This visitor follows the same pattern as before:

cpp
1
bool Visitor::VisitUsingDirectiveDecl(clang::UsingDirectiveDecl* node) {
2
// Check to ensure this node originates from the file we are annotating
3
// ...
4
5
const clang::SourceLocation& location = node->getLocation();
6
unsigned line = source_manager.getSpellingLineNumber(location);
7
unsigned column = source_manager.getSpellingColumnNumber(location);
8
9
const clang::NamespaceDecl* n = node->getNominatedNamespace();
10
std::string name = n->getNameAsString();
11
12
m_annotator->insert_annotation("namespace-name", line, column, name.length());
13
return true;
14
}

The name of the nominated namespace is retrieved from its declaration using getNominatedNamespace(), which returns a NamespaceDecl. As before, the namespace name is annotated with the namespace-name tag.

text
 
namespace math {
 
namespace utility {
 
// ...
 
}
 
}
 
 
int main() {
+
using namespace [[namespace-name,math]];
 
namespace utils = math::utility;
 
 
// ...
 
}

You’ll notice that in both examples, qualifiers these directives (such as math qualifier on the utility namespace) remain unannotated. We will handle these when we look at generic qualifier processing in a later post in this series.

Styling

The final step is to add a definition for the namespace-name CSS style:

css
.language-cpp .namespace-name {
color: rgb(181, 182, 227);
}
cpp
namespace math {
namespace utility {
// ...
}
}
int main() {
using namespace math;
namespace utils = math::utility;
// ...
}

We’ve added support for annotating namespace declarations, aliases, and using namespace directives. In the <LocalLink text={“next post”} to={“Better C++ Syntax Highlighting - Part 4: Functions”}>, we’ll take a closer look at annotating functions declarations, definitions, calls, and operators. Thanks for reading!

Series: Better C++ Syntax Highlighting - Part 3 of 10