[clang-tools-extra] feat: highlight for import, export keyword of CXX Module (PR #204511)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jul 13 23:52:32 PDT 2026
https://github.com/Decodetalkers updated https://github.com/llvm/llvm-project/pull/204511
>From 9c195097b811e0a43ee8259169c9a6d66b915ba5 Mon Sep 17 00:00:00 2001
From: ShootingStarDragons <ShootingStarDragons at protonmail.com>
Date: Thu, 18 Jun 2026 18:08:05 +0900
Subject: [PATCH 1/5] feat: highlight for keyword of import, export in CXX
module
This pr aims to add highlight for import and export keyword
And induced new SemanticHighlight `keyword`
---
.../clangd/SemanticHighlighting.cpp | 17 +++++++++++++++++
clang-tools-extra/clangd/SemanticHighlighting.h | 1 +
2 files changed, 18 insertions(+)
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index d1ed3ea9bc88a..540c6eb7e1c9e 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -582,6 +582,19 @@ class CollectExtraHighlightings
return true;
}
+ bool VisitImportDecl(const ImportDecl *D) {
+ H.addToken(D->getLocation(), HighlightingKind::Keyword);
+ for (const auto ModuleLoc : D->getIdentifierLocs()) {
+ H.addToken(ModuleLoc, HighlightingKind::Namespace)
+ .addModifier(HighlightingModifier::DependentName);
+ }
+ return true;
+ }
+ bool VisitExportDecl(const ExportDecl *D) {
+ H.addToken(D->getLocation(), HighlightingKind::Keyword);
+ return true;
+ }
+
bool VisitTagDecl(TagDecl *D) {
for (TemplateParameterList *TPL : D->getTemplateParameterLists())
H.addAngleBracketTokens(TPL->getLAngleLoc(), TPL->getRAngleLoc());
@@ -1117,6 +1130,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K) {
return OS << "Function";
case HighlightingKind::Method:
return OS << "Method";
+ case HighlightingKind::Keyword:
+ return OS << "Keyword";
case HighlightingKind::StaticMethod:
return OS << "StaticMethod";
case HighlightingKind::Field:
@@ -1320,6 +1335,8 @@ llvm::StringRef toSemanticTokenType(HighlightingKind Kind) {
return "function";
case HighlightingKind::Method:
return "method";
+ case HighlightingKind::Keyword:
+ return "keyword";
case HighlightingKind::StaticMethod:
// FIXME: better method with static modifier?
return "function";
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h
index 59d742b83ee52..3b1c12418becb 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.h
+++ b/clang-tools-extra/clangd/SemanticHighlighting.h
@@ -34,6 +34,7 @@ enum class HighlightingKind {
Parameter,
Function,
Method,
+ Keyword,
StaticMethod,
Field,
StaticField,
>From 7374d45d4a85e82dadf28440bab40a23a46f7475 Mon Sep 17 00:00:00 2001
From: ShootingStarDragons <ShootingStarDragons at protonmail.com>
Date: Sat, 20 Jun 2026 18:49:58 +0900
Subject: [PATCH 2/5] chore: add highlight for export in CXX Module
since I cannot fix the build for cxx module, so I cannot test import, so
I test export first
---
.../clangd/unittests/SemanticHighlightingTests.cpp | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
index f8b7c242be9ff..569760294e455 100644
--- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
@@ -1123,6 +1123,18 @@ sizeof...($TemplateParameter[[Elements]]);
)cpp"}},
~ScopeModifierMask);
+ checkHighlightings(R"cpp(
+ module;
+ export module highlight;
+ $Keyword[[export]] void $Function_def[[foo]]() {
+ }
+ )cpp",
+ {{"imp.h", R"cpp(
+ module;
+ export module ABC;
+ )cpp"}},
+ ~ScopeModifierMask);
+
// A separate test for macros in headers.
checkHighlightings(R"cpp(
#include "imp.h"
>From 71fdc88c0dd83d582048c4d80518149bee992eb7 Mon Sep 17 00:00:00 2001
From: ShootingStarDragons <ShootingStarDragons at protonmail.com>
Date: Mon, 22 Jun 2026 22:31:42 +0900
Subject: [PATCH 3/5] chore: remove keyword and reuse Modifier instead
---
clang-tools-extra/clangd/SemanticHighlighting.cpp | 11 +++--------
clang-tools-extra/clangd/SemanticHighlighting.h | 1 -
.../clangd/unittests/SemanticHighlightingTests.cpp | 4 ++--
3 files changed, 5 insertions(+), 11 deletions(-)
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index 540c6eb7e1c9e..543a72f638056 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -583,15 +583,14 @@ class CollectExtraHighlightings
}
bool VisitImportDecl(const ImportDecl *D) {
- H.addToken(D->getLocation(), HighlightingKind::Keyword);
+ H.addToken(D->getLocation(), HighlightingKind::Modifier);
for (const auto ModuleLoc : D->getIdentifierLocs()) {
- H.addToken(ModuleLoc, HighlightingKind::Namespace)
- .addModifier(HighlightingModifier::DependentName);
+ H.addToken(ModuleLoc, HighlightingKind::Namespace);
}
return true;
}
bool VisitExportDecl(const ExportDecl *D) {
- H.addToken(D->getLocation(), HighlightingKind::Keyword);
+ H.addToken(D->getLocation(), HighlightingKind::Modifier);
return true;
}
@@ -1130,8 +1129,6 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, HighlightingKind K) {
return OS << "Function";
case HighlightingKind::Method:
return OS << "Method";
- case HighlightingKind::Keyword:
- return OS << "Keyword";
case HighlightingKind::StaticMethod:
return OS << "StaticMethod";
case HighlightingKind::Field:
@@ -1335,8 +1332,6 @@ llvm::StringRef toSemanticTokenType(HighlightingKind Kind) {
return "function";
case HighlightingKind::Method:
return "method";
- case HighlightingKind::Keyword:
- return "keyword";
case HighlightingKind::StaticMethod:
// FIXME: better method with static modifier?
return "function";
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.h b/clang-tools-extra/clangd/SemanticHighlighting.h
index 3b1c12418becb..59d742b83ee52 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.h
+++ b/clang-tools-extra/clangd/SemanticHighlighting.h
@@ -34,7 +34,6 @@ enum class HighlightingKind {
Parameter,
Function,
Method,
- Keyword,
StaticMethod,
Field,
StaticField,
diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
index 569760294e455..2fdabb1462b46 100644
--- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
@@ -1126,10 +1126,10 @@ sizeof...($TemplateParameter[[Elements]]);
checkHighlightings(R"cpp(
module;
export module highlight;
- $Keyword[[export]] void $Function_def[[foo]]() {
+ $Modifier[[export]] void $Function_def[[foo]]() {
}
)cpp",
- {{"imp.h", R"cpp(
+ {{"imp.cxx", R"cpp(
module;
export module ABC;
)cpp"}},
>From 034380575ebcd1d3d907f645016a23c3aba886fe Mon Sep 17 00:00:00 2001
From: ShootingStarDragons <ShootingStarDragons at protonmail.com>
Date: Sat, 27 Jun 2026 22:39:20 +0900
Subject: [PATCH 4/5] chore: move sematichighlight to module
MockDirectoryCompilationDataBase can handle the task for module, so the
highttest can be finished there
---
.../unittests/PrerequisiteModulesTest.cpp | 90 +++++++++++++++++++
.../unittests/SemanticHighlightingTests.cpp | 12 ---
2 files changed, 90 insertions(+), 12 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
index f163e248c383a..a90b6b233419e 100644
--- a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
+++ b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
@@ -17,6 +17,7 @@
#include "ModulesBuilder.h"
#include "Preamble.h"
#include "ProjectModules.h"
+#include "SemanticHighlighting.h"
#include "TestTU.h"
#include "support/Path.h"
#include "support/ThreadsafeFS.h"
@@ -282,6 +283,50 @@ class PrerequisiteModulesTests : public ::testing::Test {
DiagnosticConsumer DiagConsumer;
};
+/// Annotates the input code with provided semantic highlightings. Results look
+/// something like:
+/// class $Class[[X]] {
+/// $Primitive[[int]] $Field[[a]] = 0;
+/// };
+std::string annotate(llvm::StringRef Input,
+ llvm::ArrayRef<HighlightingToken> Tokens) {
+ assert(llvm::is_sorted(
+ Tokens, [](const HighlightingToken &L, const HighlightingToken &R) {
+ return L.R.start < R.R.start;
+ }));
+
+ std::string Buf;
+ llvm::raw_string_ostream OS(Buf);
+ unsigned NextChar = 0;
+ for (auto &T : Tokens) {
+ unsigned StartOffset = llvm::cantFail(positionToOffset(Input, T.R.start));
+ unsigned EndOffset = llvm::cantFail(positionToOffset(Input, T.R.end));
+ assert(StartOffset <= EndOffset);
+ assert(NextChar <= StartOffset);
+
+ bool hasDef =
+ T.Modifiers & (1 << uint32_t(HighlightingModifier::Definition));
+ bool hasDecl =
+ T.Modifiers & (1 << uint32_t(HighlightingModifier::Declaration));
+ EXPECT_TRUE(!hasDef || hasDecl);
+
+ OS << Input.substr(NextChar, StartOffset - NextChar);
+ OS << '$' << T.Kind;
+ for (unsigned I = 0;
+ I <= static_cast<uint32_t>(HighlightingModifier::LastModifier); ++I) {
+ if (T.Modifiers & (1 << I)) {
+ // _decl_def is common and redundant, just print _def instead.
+ if (I != uint32_t(HighlightingModifier::Declaration) || !hasDef)
+ OS << '_' << static_cast<HighlightingModifier>(I);
+ }
+ }
+ OS << "[[" << Input.substr(StartOffset, EndOffset - StartOffset) << "]]";
+ NextChar = EndOffset;
+ }
+ OS << Input.substr(NextChar);
+ return std::move(OS.str());
+}
+
TEST_F(PrerequisiteModulesTests, NonModularTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
@@ -1669,6 +1714,51 @@ void use() {}
getFullPath("Consumer.cpp"), *NewCI));
}
+TEST_F(PrerequisiteModulesTests, ModuleSemanticHighlighting) {
+ MockDirectoryCompilationDatabase CDB(TestDir, FS);
+
+ llvm::StringRef AnnotatedCode = R"cpp(
+ module;
+ $Modifier[[import]] $Namespace[[M]];
+ export module highlight;
+ $Modifier[[export]] void $Function_def_globalScope[[foo]]() {
+ }
+)cpp";
+ uint32_t ModifierMask = -1;
+ Annotations UseCpp(AnnotatedCode);
+
+ CDB.addFile("M.cppm", R"cpp(
+export module M;
+export struct TypeFromModule {};
+)cpp");
+
+ CDB.addFile("Use.cpp", UseCpp.code());
+
+ ModulesBuilder Builder(CDB);
+
+ auto Inputs = getInputs("Use.cpp", CDB);
+ Inputs.ModulesManager = &Builder;
+ Inputs.Opts.SkipPreambleBuild = true;
+
+ auto CI = buildCompilerInvocation(Inputs, DiagConsumer);
+ ASSERT_TRUE(CI);
+
+ auto Preamble =
+ buildPreamble(getFullPath("Use.cpp"), *CI, Inputs, /*StoreInMemory=*/true,
+ /*PeambleCallback=*/nullptr);
+ ASSERT_TRUE(Preamble);
+ EXPECT_EQ(Preamble->Preamble.getBounds().Size, 0u);
+
+ auto AST = ParsedAST::build(getFullPath("Use.cpp"), Inputs, std::move(CI), {},
+ Preamble);
+ auto Actual = getSemanticHighlightings(AST.value(),
+ /*IncludeInactiveRegionTokens=*/true);
+ for (auto &Token : Actual)
+ Token.Modifiers &= ModifierMask;
+
+ EXPECT_EQ(AnnotatedCode, annotate(UseCpp.code(), Actual));
+}
+
} // namespace
} // namespace clang::clangd
diff --git a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
index 2fdabb1462b46..f8b7c242be9ff 100644
--- a/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SemanticHighlightingTests.cpp
@@ -1123,18 +1123,6 @@ sizeof...($TemplateParameter[[Elements]]);
)cpp"}},
~ScopeModifierMask);
- checkHighlightings(R"cpp(
- module;
- export module highlight;
- $Modifier[[export]] void $Function_def[[foo]]() {
- }
- )cpp",
- {{"imp.cxx", R"cpp(
- module;
- export module ABC;
- )cpp"}},
- ~ScopeModifierMask);
-
// A separate test for macros in headers.
checkHighlightings(R"cpp(
#include "imp.h"
>From c2bad3276008c0e4e240fcf5f9897c76da3d6893 Mon Sep 17 00:00:00 2001
From: ShootingStarDragons <ShootingStarDragons at protonmail.com>
Date: Tue, 14 Jul 2026 15:44:18 +0900
Subject: [PATCH 5/5] chore: do not use annotate from highlight unittest
just check if import is highlighted with right token
---
.../unittests/PrerequisiteModulesTest.cpp | 63 ++++---------------
1 file changed, 11 insertions(+), 52 deletions(-)
diff --git a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
index a90b6b233419e..f042747e95d30 100644
--- a/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
+++ b/clang-tools-extra/clangd/unittests/PrerequisiteModulesTest.cpp
@@ -283,50 +283,6 @@ class PrerequisiteModulesTests : public ::testing::Test {
DiagnosticConsumer DiagConsumer;
};
-/// Annotates the input code with provided semantic highlightings. Results look
-/// something like:
-/// class $Class[[X]] {
-/// $Primitive[[int]] $Field[[a]] = 0;
-/// };
-std::string annotate(llvm::StringRef Input,
- llvm::ArrayRef<HighlightingToken> Tokens) {
- assert(llvm::is_sorted(
- Tokens, [](const HighlightingToken &L, const HighlightingToken &R) {
- return L.R.start < R.R.start;
- }));
-
- std::string Buf;
- llvm::raw_string_ostream OS(Buf);
- unsigned NextChar = 0;
- for (auto &T : Tokens) {
- unsigned StartOffset = llvm::cantFail(positionToOffset(Input, T.R.start));
- unsigned EndOffset = llvm::cantFail(positionToOffset(Input, T.R.end));
- assert(StartOffset <= EndOffset);
- assert(NextChar <= StartOffset);
-
- bool hasDef =
- T.Modifiers & (1 << uint32_t(HighlightingModifier::Definition));
- bool hasDecl =
- T.Modifiers & (1 << uint32_t(HighlightingModifier::Declaration));
- EXPECT_TRUE(!hasDef || hasDecl);
-
- OS << Input.substr(NextChar, StartOffset - NextChar);
- OS << '$' << T.Kind;
- for (unsigned I = 0;
- I <= static_cast<uint32_t>(HighlightingModifier::LastModifier); ++I) {
- if (T.Modifiers & (1 << I)) {
- // _decl_def is common and redundant, just print _def instead.
- if (I != uint32_t(HighlightingModifier::Declaration) || !hasDef)
- OS << '_' << static_cast<HighlightingModifier>(I);
- }
- }
- OS << "[[" << Input.substr(StartOffset, EndOffset - StartOffset) << "]]";
- NextChar = EndOffset;
- }
- OS << Input.substr(NextChar);
- return std::move(OS.str());
-}
-
TEST_F(PrerequisiteModulesTests, NonModularTest) {
MockDirectoryCompilationDatabase CDB(TestDir, FS);
@@ -620,7 +576,8 @@ int use() { return a; }
ModulesBuilder Builder(CDB);
- auto UseInfo = Builder.buildPrerequisiteModulesFor(getFullPath("Use.cpp"), FS);
+ auto UseInfo =
+ Builder.buildPrerequisiteModulesFor(getFullPath("Use.cpp"), FS);
ASSERT_TRUE(UseInfo);
HeaderSearchOptions HSOpts;
@@ -1719,12 +1676,11 @@ TEST_F(PrerequisiteModulesTests, ModuleSemanticHighlighting) {
llvm::StringRef AnnotatedCode = R"cpp(
module;
- $Modifier[[import]] $Namespace[[M]];
+ $import[[import]] M;
export module highlight;
- $Modifier[[export]] void $Function_def_globalScope[[foo]]() {
+ $export[[export]] void foo) {
}
)cpp";
- uint32_t ModifierMask = -1;
Annotations UseCpp(AnnotatedCode);
CDB.addFile("M.cppm", R"cpp(
@@ -1753,10 +1709,13 @@ export struct TypeFromModule {};
Preamble);
auto Actual = getSemanticHighlightings(AST.value(),
/*IncludeInactiveRegionTokens=*/true);
- for (auto &Token : Actual)
- Token.Modifiers &= ModifierMask;
-
- EXPECT_EQ(AnnotatedCode, annotate(UseCpp.code(), Actual));
+ auto HasToken = [&](llvm::StringRef Name, HighlightingKind Kind) {
+ return llvm::any_of(Actual, [&](const HighlightingToken &T) {
+ return T.Kind == Kind && T.R == UseCpp.range(Name);
+ });
+ };
+ EXPECT_TRUE(HasToken("import", HighlightingKind::Modifier));
+ EXPECT_TRUE(HasToken("export", HighlightingKind::Modifier));
}
} // namespace
More information about the cfe-commits
mailing list