aboutsummaryrefslogtreecommitdiff
path: root/clang-tools-extra/clangd
diff options
context:
space:
mode:
Diffstat (limited to 'clang-tools-extra/clangd')
-rw-r--r--clang-tools-extra/clangd/AST.cpp96
-rw-r--r--clang-tools-extra/clangd/AST.h3
-rw-r--r--clang-tools-extra/clangd/CMakeLists.txt5
-rw-r--r--clang-tools-extra/clangd/CodeComplete.cpp22
-rw-r--r--clang-tools-extra/clangd/CodeCompletionStrings.cpp64
-rw-r--r--clang-tools-extra/clangd/Config.h3
-rw-r--r--clang-tools-extra/clangd/ConfigCompile.cpp6
-rw-r--r--clang-tools-extra/clangd/ConfigFragment.h2
-rw-r--r--clang-tools-extra/clangd/ConfigYAML.cpp4
-rw-r--r--clang-tools-extra/clangd/DumpAST.cpp41
-rw-r--r--clang-tools-extra/clangd/FindTarget.cpp123
-rw-r--r--clang-tools-extra/clangd/GlobalCompilationDatabase.cpp4
-rw-r--r--clang-tools-extra/clangd/Hover.cpp368
-rw-r--r--clang-tools-extra/clangd/Hover.h21
-rw-r--r--clang-tools-extra/clangd/IncludeFixer.cpp52
-rw-r--r--clang-tools-extra/clangd/InlayHints.cpp28
-rw-r--r--clang-tools-extra/clangd/ModulesBuilder.cpp17
-rw-r--r--clang-tools-extra/clangd/Protocol.cpp3
-rw-r--r--clang-tools-extra/clangd/Quality.cpp3
-rw-r--r--clang-tools-extra/clangd/ScanningProjectModules.cpp11
-rw-r--r--clang-tools-extra/clangd/Selection.cpp17
-rw-r--r--clang-tools-extra/clangd/SemanticHighlighting.cpp15
-rw-r--r--clang-tools-extra/clangd/SemanticSelection.cpp37
-rw-r--r--clang-tools-extra/clangd/SymbolDocumentation.cpp386
-rw-r--r--clang-tools-extra/clangd/SymbolDocumentation.h211
-rw-r--r--clang-tools-extra/clangd/XRefs.cpp19
-rw-r--r--clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp121
-rw-r--r--clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp9
-rw-r--r--clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp6
-rw-r--r--clang-tools-extra/clangd/support/DirectiveTree.cpp57
-rw-r--r--clang-tools-extra/clangd/support/DirectiveTree.h4
-rw-r--r--clang-tools-extra/clangd/support/Markup.cpp7
-rw-r--r--clang-tools-extra/clangd/test/modules_no_cdb.test66
-rw-r--r--clang-tools-extra/clangd/unittests/ASTTests.cpp7
-rw-r--r--clang-tools-extra/clangd/unittests/CMakeLists.txt1
-rw-r--r--clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp192
-rw-r--r--clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp4
-rw-r--r--clang-tools-extra/clangd/unittests/DumpASTTests.cpp15
-rw-r--r--clang-tools-extra/clangd/unittests/FindTargetTests.cpp10
-rw-r--r--clang-tools-extra/clangd/unittests/HoverTests.cpp635
-rw-r--r--clang-tools-extra/clangd/unittests/InlayHintTests.cpp9
-rw-r--r--clang-tools-extra/clangd/unittests/QualityTests.cpp14
-rw-r--r--clang-tools-extra/clangd/unittests/RenameTests.cpp23
-rw-r--r--clang-tools-extra/clangd/unittests/SelectionTests.cpp4
-rw-r--r--clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp39
-rw-r--r--clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp215
-rw-r--r--clang-tools-extra/clangd/unittests/support/MarkupTests.cpp8
47 files changed, 2572 insertions, 435 deletions
diff --git a/clang-tools-extra/clangd/AST.cpp b/clang-tools-extra/clangd/AST.cpp
index f2631e5..2f46ecc 100644
--- a/clang-tools-extra/clangd/AST.cpp
+++ b/clang-tools-extra/clangd/AST.cpp
@@ -102,54 +102,78 @@ getUsingNamespaceDirectives(const DeclContext *DestContext,
// ancestor is redundant, therefore we stop at lowest common ancestor.
// In addition to that stops early whenever IsVisible returns true. This can be
// used to implement support for "using namespace" decls.
-std::string
-getQualification(ASTContext &Context, const DeclContext *DestContext,
- const DeclContext *SourceContext,
- llvm::function_ref<bool(NestedNameSpecifier *)> IsVisible) {
- std::vector<const NestedNameSpecifier *> Parents;
- bool ReachedNS = false;
+std::string getQualification(ASTContext &Context,
+ const DeclContext *DestContext,
+ const DeclContext *SourceContext,
+ llvm::function_ref<bool(const Decl *)> IsVisible) {
+ std::vector<const Decl *> Parents;
+ [[maybe_unused]] bool ReachedNS = false;
for (const DeclContext *CurContext = SourceContext; CurContext;
CurContext = CurContext->getLookupParent()) {
// Stop once we reach a common ancestor.
if (CurContext->Encloses(DestContext))
break;
- NestedNameSpecifier *NNS = nullptr;
+ const Decl *CurD;
if (auto *TD = llvm::dyn_cast<TagDecl>(CurContext)) {
// There can't be any more tag parents after hitting a namespace.
assert(!ReachedNS);
- (void)ReachedNS;
- NNS = NestedNameSpecifier::Create(Context, nullptr, TD->getTypeForDecl());
+ CurD = TD;
} else if (auto *NSD = llvm::dyn_cast<NamespaceDecl>(CurContext)) {
ReachedNS = true;
- NNS = NestedNameSpecifier::Create(Context, nullptr, NSD);
// Anonymous and inline namespace names are not spelled while qualifying
// a name, so skip those.
if (NSD->isAnonymousNamespace() || NSD->isInlineNamespace())
continue;
+ CurD = NSD;
} else {
// Other types of contexts cannot be spelled in code, just skip over
// them.
continue;
}
// Stop if this namespace is already visible at DestContext.
- if (IsVisible(NNS))
+ if (IsVisible(CurD))
break;
- Parents.push_back(NNS);
+ Parents.push_back(CurD);
+ }
+
+ // Go over the declarations in reverse order, since we stored inner-most
+ // parent first.
+ NestedNameSpecifier Qualifier = std::nullopt;
+ bool IsFirst = true;
+ for (const auto *CurD : llvm::reverse(Parents)) {
+ if (auto *TD = llvm::dyn_cast<TagDecl>(CurD)) {
+ QualType T;
+ if (const auto *RD = dyn_cast<CXXRecordDecl>(TD);
+ ClassTemplateDecl *CTD = RD->getDescribedClassTemplate()) {
+ ArrayRef<TemplateArgument> Args;
+ if (const auto *SD = dyn_cast<ClassTemplateSpecializationDecl>(RD))
+ Args = SD->getTemplateArgs().asArray();
+ else
+ Args = CTD->getTemplateParameters()->getInjectedTemplateArgs(Context);
+ T = Context.getTemplateSpecializationType(
+ ElaboratedTypeKeyword::None,
+ Context.getQualifiedTemplateName(
+ Qualifier, /*TemplateKeyword=*/!IsFirst, TemplateName(CTD)),
+ Args, /*CanonicalArgs=*/{}, Context.getCanonicalTagType(RD));
+ } else {
+ T = Context.getTagType(ElaboratedTypeKeyword::None, Qualifier, TD,
+ /*OwnsTag=*/false);
+ }
+ Qualifier = NestedNameSpecifier(T.getTypePtr());
+ } else {
+ Qualifier =
+ NestedNameSpecifier(Context, cast<NamespaceDecl>(CurD), Qualifier);
+ }
+ IsFirst = false;
}
+ if (!Qualifier)
+ return "";
- // Go over name-specifiers in reverse order to create necessary qualification,
- // since we stored inner-most parent first.
std::string Result;
llvm::raw_string_ostream OS(Result);
- for (const auto *Parent : llvm::reverse(Parents)) {
- if (Parent != *Parents.rbegin() && Parent->isDependent() &&
- Parent->getAsRecordDecl() &&
- Parent->getAsRecordDecl()->getDescribedClassTemplate())
- OS << "template ";
- Parent->print(OS, Context.getPrintingPolicy());
- }
+ Qualifier.print(OS, Context.getPrintingPolicy());
return OS.str();
}
@@ -187,6 +211,7 @@ std::string printQualifiedName(const NamedDecl &ND) {
// include them, but at query time it's hard to find all the inline
// namespaces to query: the preamble doesn't have a dedicated list.
Policy.SuppressUnwrittenScope = true;
+ Policy.SuppressScope = true;
// (unnamed struct), not (unnamed struct at /path/to/foo.cc:42:1).
// In clangd, context is usually available and paths are mostly noise.
Policy.AnonymousTagLocations = false;
@@ -213,8 +238,7 @@ std::string printUsingNamespaceName(const ASTContext &Ctx,
std::string Name;
llvm::raw_string_ostream Out(Name);
- if (auto *Qual = D.getQualifier())
- Qual->print(Out, PP);
+ D.getQualifier().print(Out, PP);
D.getNominatedNamespaceAsWritten()->printName(Out);
return Out.str();
}
@@ -229,8 +253,7 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND) {
// Handle 'using namespace'. They all have the same name - <using-directive>.
if (auto *UD = llvm::dyn_cast<UsingDirectiveDecl>(&ND)) {
Out << "using namespace ";
- if (auto *Qual = UD->getQualifier())
- Qual->print(Out, PP);
+ UD->getQualifier().print(Out, PP);
UD->getNominatedNamespaceAsWritten()->printName(Out);
return Out.str();
}
@@ -250,8 +273,7 @@ std::string printName(const ASTContext &Ctx, const NamedDecl &ND) {
}
// Print nested name qualifier if it was written in the source code.
- if (auto *Qualifier = getQualifierLoc(ND).getNestedNameSpecifier())
- Qualifier->print(Out, PP);
+ getQualifierLoc(ND).getNestedNameSpecifier().print(Out, PP);
// Print the name itself.
ND.getDeclName().print(Out, PP);
// Print template arguments.
@@ -391,12 +413,13 @@ preferredIncludeDirective(llvm::StringRef FileName, const LangOptions &LangOpts,
}
std::string printType(const QualType QT, const DeclContext &CurContext,
- const llvm::StringRef Placeholder) {
+ const llvm::StringRef Placeholder, bool FullyQualify) {
std::string Result;
llvm::raw_string_ostream OS(Result);
PrintingPolicy PP(CurContext.getParentASTContext().getPrintingPolicy());
PP.SuppressTagKeyword = true;
PP.SuppressUnwrittenScope = true;
+ PP.FullyQualifiedName = FullyQualify;
class PrintCB : public PrintingCallbacks {
public:
@@ -439,6 +462,7 @@ QualType declaredType(const TypeDecl *D) {
if (const auto *CTSD = llvm::dyn_cast<ClassTemplateSpecializationDecl>(D))
if (const auto *Args = CTSD->getTemplateArgsAsWritten())
return Context.getTemplateSpecializationType(
+ ElaboratedTypeKeyword::None,
TemplateName(CTSD->getSpecializedTemplate()), Args->arguments(),
/*CanonicalArgs=*/{});
return Context.getTypeDeclType(D);
@@ -664,13 +688,10 @@ std::string getQualification(ASTContext &Context,
auto VisibleNamespaceDecls =
getUsingNamespaceDirectives(DestContext, InsertionPoint);
return getQualification(
- Context, DestContext, ND->getDeclContext(),
- [&](NestedNameSpecifier *NNS) {
- const NamespaceDecl *NS =
- dyn_cast_if_present<NamespaceDecl>(NNS->getAsNamespace());
- if (!NS)
+ Context, DestContext, ND->getDeclContext(), [&](const Decl *D) {
+ if (D->getKind() != Decl::Namespace)
return false;
- NS = NS->getCanonicalDecl();
+ const auto *NS = cast<NamespaceDecl>(D)->getCanonicalDecl();
return llvm::any_of(VisibleNamespaceDecls,
[NS](const NamespaceDecl *NSD) {
return NSD->getCanonicalDecl() == NS;
@@ -687,12 +708,11 @@ std::string getQualification(ASTContext &Context,
(void)NS;
}
return getQualification(
- Context, DestContext, ND->getDeclContext(),
- [&](NestedNameSpecifier *NNS) {
+ Context, DestContext, ND->getDeclContext(), [&](const Decl *D) {
return llvm::any_of(VisibleNamespaces, [&](llvm::StringRef Namespace) {
std::string NS;
llvm::raw_string_ostream OS(NS);
- NNS->print(OS, Context.getPrintingPolicy());
+ D->print(OS, Context.getPrintingPolicy());
return OS.str() == Namespace;
});
});
@@ -965,7 +985,7 @@ resolveForwardingParameters(const FunctionDecl *D, unsigned MaxDepth) {
// Recurse on pack parameters
size_t Depth = 0;
const FunctionDecl *CurrentFunction = D;
- llvm::SmallSet<const FunctionTemplateDecl *, 4> SeenTemplates;
+ llvm::SmallPtrSet<const FunctionTemplateDecl *, 4> SeenTemplates;
if (const auto *Template = D->getPrimaryTemplate()) {
SeenTemplates.insert(Template);
}
diff --git a/clang-tools-extra/clangd/AST.h b/clang-tools-extra/clangd/AST.h
index fb0722d..1538d12 100644
--- a/clang-tools-extra/clangd/AST.h
+++ b/clang-tools-extra/clangd/AST.h
@@ -135,7 +135,8 @@ preferredIncludeDirective(llvm::StringRef FileName, const LangOptions &LangOpts,
/// Returns a QualType as string. The result doesn't contain unwritten scopes
/// like anonymous/inline namespace.
std::string printType(const QualType QT, const DeclContext &CurContext,
- llvm::StringRef Placeholder = "");
+ llvm::StringRef Placeholder = "",
+ bool FullyQualify = false);
/// Indicates if \p D is a template instantiation implicitly generated by the
/// compiler, e.g.
diff --git a/clang-tools-extra/clangd/CMakeLists.txt b/clang-tools-extra/clangd/CMakeLists.txt
index a1e9da4..fb3f053 100644
--- a/clang-tools-extra/clangd/CMakeLists.txt
+++ b/clang-tools-extra/clangd/CMakeLists.txt
@@ -6,7 +6,7 @@ add_subdirectory(support)
# Configure the Features.inc file.
if (NOT DEFINED CLANGD_BUILD_XPC)
- if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
+ if("${CMAKE_SYSTEM_NAME}" MATCHES "Darwin")
set(CLANGD_BUILD_XPC_DEFAULT ON)
else ()
set(CLANGD_BUILD_XPC_DEFAULT OFF)
@@ -108,6 +108,7 @@ add_clang_library(clangDaemon STATIC
SemanticHighlighting.cpp
SemanticSelection.cpp
SourceCode.cpp
+ SymbolDocumentation.cpp
SystemIncludeExtractor.cpp
TidyProvider.cpp
TUScheduler.cpp
@@ -192,7 +193,7 @@ if(CLANGD_TIDY_CHECKS)
endif()
add_subdirectory(refactor/tweaks)
-if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
+if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux")
# FIXME: Make fuzzer not use linux-specific APIs, build it everywhere.
add_subdirectory(fuzzer)
endif()
diff --git a/clang-tools-extra/clangd/CodeComplete.cpp b/clang-tools-extra/clangd/CodeComplete.cpp
index 9c17b4c..c6deed3 100644
--- a/clang-tools-extra/clangd/CodeComplete.cpp
+++ b/clang-tools-extra/clangd/CodeComplete.cpp
@@ -97,6 +97,9 @@ toCompletionItemKind(index::SymbolKind Kind,
const llvm::StringRef *Signature = nullptr) {
using SK = index::SymbolKind;
switch (Kind) {
+ // FIXME: for backwards compatibility, the include directive kind is treated
+ // the same as Unknown
+ case SK::IncludeDirective:
case SK::Unknown:
return CompletionItemKind::Missing;
case SK::Module:
@@ -1466,19 +1469,15 @@ bool allowIndex(CodeCompletionContext &CC) {
auto Scope = CC.getCXXScopeSpecifier();
if (!Scope)
return true;
- NestedNameSpecifier *NameSpec = (*Scope)->getScopeRep();
- if (!NameSpec)
- return true;
// We only query the index when qualifier is a namespace.
// If it's a class, we rely solely on sema completions.
- switch (NameSpec->getKind()) {
- case NestedNameSpecifier::Global:
- case NestedNameSpecifier::Namespace:
+ switch ((*Scope)->getScopeRep().getKind()) {
+ case NestedNameSpecifier::Kind::Null:
+ case NestedNameSpecifier::Kind::Global:
+ case NestedNameSpecifier::Kind::Namespace:
return true;
- case NestedNameSpecifier::Super:
- case NestedNameSpecifier::TypeSpec:
- // Unresolved inside a template.
- case NestedNameSpecifier::Identifier:
+ case NestedNameSpecifier::Kind::MicrosoftSuper:
+ case NestedNameSpecifier::Kind::Type:
return false;
}
llvm_unreachable("invalid NestedNameSpecifier kind");
@@ -2434,6 +2433,9 @@ CompletionItem CodeCompletion::render(const CodeCompleteOptions &Opts) const {
}
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const CodeCompletion &C) {
+ OS << "Signature: " << "\"" << C.Signature << "\", "
+ << "SnippetSuffix: " << "\"" << C.SnippetSuffix << "\""
+ << ", Rendered:";
// For now just lean on CompletionItem.
return OS << C.render(CodeCompleteOptions());
}
diff --git a/clang-tools-extra/clangd/CodeCompletionStrings.cpp b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
index 9b4442b..9c4241b 100644
--- a/clang-tools-extra/clangd/CodeCompletionStrings.cpp
+++ b/clang-tools-extra/clangd/CodeCompletionStrings.cpp
@@ -7,13 +7,18 @@
//===----------------------------------------------------------------------===//
#include "CodeCompletionStrings.h"
+#include "Config.h"
+#include "SymbolDocumentation.h"
#include "clang-c/Index.h"
#include "clang/AST/ASTContext.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/Decl.h"
#include "clang/AST/RawCommentList.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Sema/CodeCompleteConsumer.h"
#include "llvm/Support/Compiler.h"
#include "llvm/Support/JSON.h"
+#include "llvm/Support/raw_ostream.h"
#include <limits>
#include <utility>
@@ -100,16 +105,55 @@ std::string getDeclComment(const ASTContext &Ctx, const NamedDecl &Decl) {
// the comments for namespaces.
return "";
}
- const RawComment *RC = getCompletionComment(Ctx, &Decl);
- if (!RC)
- return "";
- // Sanity check that the comment does not come from the PCH. We choose to not
- // write them into PCH, because they are racy and slow to load.
- assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
- std::string Doc =
- RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
- if (!looksLikeDocComment(Doc))
- return "";
+
+ const RawComment *RC = nullptr;
+ const Config &Cfg = Config::current();
+
+ std::string Doc;
+
+ if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen &&
+ isa<ParmVarDecl, TemplateTypeParmDecl>(Decl)) {
+ // Parameters are documented in their declaration context (function or
+ // template function).
+ const NamedDecl *ND = dyn_cast<NamedDecl>(Decl.getDeclContext());
+ if (!ND)
+ return "";
+
+ RC = getCompletionComment(Ctx, ND);
+ if (!RC)
+ return "";
+
+ // Sanity check that the comment does not come from the PCH. We choose to
+ // not write them into PCH, because they are racy and slow to load.
+ assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
+
+ comments::FullComment *FC = RC->parse(Ctx, /*PP=*/nullptr, ND);
+ if (!FC)
+ return "";
+
+ SymbolDocCommentVisitor V(FC, Ctx.getLangOpts().CommentOpts);
+ std::string RawDoc;
+ llvm::raw_string_ostream OS(RawDoc);
+
+ if (auto *PVD = dyn_cast<ParmVarDecl>(&Decl))
+ V.parameterDocToString(PVD->getName(), OS);
+ else
+ V.templateTypeParmDocToString(
+ cast<TemplateTypeParmDecl>(&Decl)->getName(), OS);
+
+ Doc = StringRef(RawDoc).trim().str();
+ } else {
+ RC = getCompletionComment(Ctx, &Decl);
+ if (!RC)
+ return "";
+ // Sanity check that the comment does not come from the PCH. We choose to
+ // not write them into PCH, because they are racy and slow to load.
+ assert(!Ctx.getSourceManager().isLoadedSourceLocation(RC->getBeginLoc()));
+ Doc = RC->getFormattedText(Ctx.getSourceManager(), Ctx.getDiagnostics());
+ if (!looksLikeDocComment(Doc))
+ return "";
+ }
+
// Clang requires source to be UTF-8, but doesn't enforce this in comments.
if (!llvm::json::isUTF8(Doc))
Doc = llvm::json::fixUTF8(Doc);
diff --git a/clang-tools-extra/clangd/Config.h b/clang-tools-extra/clangd/Config.h
index 2e3e0a4..01997ce 100644
--- a/clang-tools-extra/clangd/Config.h
+++ b/clang-tools-extra/clangd/Config.h
@@ -174,6 +174,9 @@ struct Config {
struct {
/// Whether hover show a.k.a type.
bool ShowAKA = true;
+ /// Limit the number of characters returned when hovering a macro;
+ /// 0 is no limit.
+ uint32_t MacroContentsLimit = 2048;
} Hover;
struct {
diff --git a/clang-tools-extra/clangd/ConfigCompile.cpp b/clang-tools-extra/clangd/ConfigCompile.cpp
index 5dda6dd..962a48b 100644
--- a/clang-tools-extra/clangd/ConfigCompile.cpp
+++ b/clang-tools-extra/clangd/ConfigCompile.cpp
@@ -727,6 +727,12 @@ struct FragmentCompiler {
C.Hover.ShowAKA = ShowAKA;
});
}
+ if (F.MacroContentsLimit) {
+ Out.Apply.push_back(
+ [Limit(**F.MacroContentsLimit)](const Params &, Config &C) {
+ C.Hover.MacroContentsLimit = Limit;
+ });
+ }
}
void compile(Fragment::InlayHintsBlock &&F) {
diff --git a/clang-tools-extra/clangd/ConfigFragment.h b/clang-tools-extra/clangd/ConfigFragment.h
index 0f11f37..2afeb36 100644
--- a/clang-tools-extra/clangd/ConfigFragment.h
+++ b/clang-tools-extra/clangd/ConfigFragment.h
@@ -361,6 +361,8 @@ struct Fragment {
struct HoverBlock {
/// Whether hover show a.k.a type.
std::optional<Located<bool>> ShowAKA;
+ /// Limit the number of characters returned when hovering a macro.
+ std::optional<Located<uint32_t>> MacroContentsLimit;
};
HoverBlock Hover;
diff --git a/clang-tools-extra/clangd/ConfigYAML.cpp b/clang-tools-extra/clangd/ConfigYAML.cpp
index 289b7e8..392cf19 100644
--- a/clang-tools-extra/clangd/ConfigYAML.cpp
+++ b/clang-tools-extra/clangd/ConfigYAML.cpp
@@ -264,6 +264,10 @@ private:
if (auto ShowAKA = boolValue(N, "ShowAKA"))
F.ShowAKA = *ShowAKA;
});
+ Dict.handle("MacroContentsLimit", [&](Node &N) {
+ if (auto MacroContentsLimit = uint32Value(N, "MacroContentsLimit"))
+ F.MacroContentsLimit = *MacroContentsLimit;
+ });
Dict.parse(N);
}
diff --git a/clang-tools-extra/clangd/DumpAST.cpp b/clang-tools-extra/clangd/DumpAST.cpp
index c6075e7..9a8d41d 100644
--- a/clang-tools-extra/clangd/DumpAST.cpp
+++ b/clang-tools-extra/clangd/DumpAST.cpp
@@ -147,17 +147,17 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
}
llvm_unreachable("Unhandled ArgKind enum");
}
- std::string getKind(const NestedNameSpecifierLoc &NNSL) {
- assert(NNSL.getNestedNameSpecifier());
- switch (NNSL.getNestedNameSpecifier()->getKind()) {
+ std::string getKind(NestedNameSpecifierLoc NNSL) {
+ switch (NNSL.getNestedNameSpecifier().getKind()) {
+ case NestedNameSpecifier::Kind::Null:
+ llvm_unreachable("unexpected null nested name specifier");
#define NNS_KIND(X) \
- case NestedNameSpecifier::X: \
+ case NestedNameSpecifier::Kind::X: \
return #X
- NNS_KIND(Identifier);
NNS_KIND(Namespace);
- NNS_KIND(TypeSpec);
+ NNS_KIND(Type);
NNS_KIND(Global);
- NNS_KIND(Super);
+ NNS_KIND(MicrosoftSuper);
#undef NNS_KIND
}
llvm_unreachable("Unhandled SpecifierKind enum");
@@ -261,7 +261,7 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
return TL.getType().getLocalQualifiers().getAsString(
Ctx.getPrintingPolicy());
if (const auto *TT = dyn_cast<TagType>(TL.getTypePtr()))
- return getDetail(TT->getDecl());
+ return getDetail(TT->getOriginalDecl());
if (const auto *DT = dyn_cast<DeducedType>(TL.getTypePtr()))
if (DT->isDeduced())
return DT->getDeducedType().getAsString(Ctx.getPrintingPolicy());
@@ -273,16 +273,11 @@ class DumpVisitor : public RecursiveASTVisitor<DumpVisitor> {
return getDetail(TT->getDecl());
return "";
}
- std::string getDetail(const NestedNameSpecifierLoc &NNSL) {
- const auto &NNS = *NNSL.getNestedNameSpecifier();
- switch (NNS.getKind()) {
- case NestedNameSpecifier::Identifier:
- return NNS.getAsIdentifier()->getName().str() + "::";
- case NestedNameSpecifier::Namespace:
- return NNS.getAsNamespace()->getNameAsString() + "::";
- default:
+ std::string getDetail(NestedNameSpecifierLoc NNSL) {
+ NestedNameSpecifier NNS = NNSL.getNestedNameSpecifier();
+ if (NNS.getKind() != NestedNameSpecifier::Kind::Namespace)
return "";
- }
+ return NNS.getAsNamespaceAndPrefix().Namespace->getNameAsString() + "::";
}
std::string getDetail(const CXXCtorInitializer *CCI) {
if (FieldDecl *FD = CCI->getAnyMember())
@@ -346,8 +341,10 @@ public:
return !D || isInjectedClassName(D) ||
traverseNode("declaration", D, [&] { Base::TraverseDecl(D); });
}
- bool TraverseTypeLoc(TypeLoc TL) {
- return !TL || traverseNode("type", TL, [&] { Base::TraverseTypeLoc(TL); });
+ bool TraverseTypeLoc(TypeLoc TL, bool TraverseQualifier = true) {
+ return !TL || traverseNode("type", TL, [&] {
+ Base::TraverseTypeLoc(TL, TraverseQualifier);
+ });
}
bool TraverseTemplateName(const TemplateName &TN) {
return traverseNode("template name", TN,
@@ -389,11 +386,11 @@ public:
// This means we'd never see 'int' in 'const int'! Work around that here.
// (The reason for the behavior is to avoid traversing the nested Type twice,
// but we ignore TraverseType anyway).
- bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QTL) {
+ bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QTL, bool TraverseQualifier) {
return TraverseTypeLoc(QTL.getUnqualifiedLoc());
}
// Uninteresting parts of the AST that don't have locations within them.
- bool TraverseNestedNameSpecifier(NestedNameSpecifier *) { return true; }
+ bool TraverseNestedNameSpecifier(NestedNameSpecifier) { return true; }
bool TraverseType(QualType) { return true; }
// OpaqueValueExpr blocks traversal, we must explicitly traverse it.
@@ -420,7 +417,7 @@ ASTNode dumpAST(const DynTypedNode &N, const syntax::TokenBuffer &Tokens,
V.TraverseNestedNameSpecifierLoc(
*const_cast<NestedNameSpecifierLoc *>(NNSL));
else if (const auto *NNS = N.get<NestedNameSpecifier>())
- V.TraverseNestedNameSpecifier(const_cast<NestedNameSpecifier *>(NNS));
+ V.TraverseNestedNameSpecifier(*NNS);
else if (const auto *TL = N.get<TypeLoc>())
V.TraverseTypeLoc(*const_cast<TypeLoc *>(TL));
else if (const auto *QT = N.get<QualType>())
diff --git a/clang-tools-extra/clangd/FindTarget.cpp b/clang-tools-extra/clangd/FindTarget.cpp
index b108957..32018d1 100644
--- a/clang-tools-extra/clangd/FindTarget.cpp
+++ b/clang-tools-extra/clangd/FindTarget.cpp
@@ -366,19 +366,11 @@ public:
Visitor(TargetFinder &Outer, RelSet Flags) : Outer(Outer), Flags(Flags) {}
void VisitTagType(const TagType *TT) {
- Outer.add(TT->getAsTagDecl(), Flags);
- }
-
- void VisitElaboratedType(const ElaboratedType *ET) {
- Outer.add(ET->desugar(), Flags);
+ Outer.add(cast<TagType>(TT)->getOriginalDecl(), Flags);
}
void VisitUsingType(const UsingType *ET) {
- Outer.add(ET->getFoundDecl(), Flags);
- }
-
- void VisitInjectedClassNameType(const InjectedClassNameType *ICNT) {
- Outer.add(ICNT->getDecl(), Flags);
+ Outer.add(ET->getDecl(), Flags);
}
void VisitDecltypeType(const DecltypeType *DTT) {
@@ -483,30 +475,27 @@ public:
Visitor(*this, Flags).Visit(T.getTypePtr());
}
- void add(const NestedNameSpecifier *NNS, RelSet Flags) {
+ void add(NestedNameSpecifier NNS, RelSet Flags) {
if (!NNS)
return;
- debug(*NNS, Flags);
- switch (NNS->getKind()) {
- case NestedNameSpecifier::Namespace:
- add(NNS->getAsNamespace(), Flags);
- return;
- case NestedNameSpecifier::Identifier:
- if (Resolver) {
- add(Resolver->resolveNestedNameSpecifierToType(NNS), Flags);
- }
+ debug(NNS, Flags);
+ switch (NNS.getKind()) {
+ case NestedNameSpecifier::Kind::Namespace:
+ add(NNS.getAsNamespaceAndPrefix().Namespace, Flags);
return;
- case NestedNameSpecifier::TypeSpec:
- add(QualType(NNS->getAsType(), 0), Flags);
+ case NestedNameSpecifier::Kind::Type:
+ add(QualType(NNS.getAsType(), 0), Flags);
return;
- case NestedNameSpecifier::Global:
+ case NestedNameSpecifier::Kind::Global:
// This should be TUDecl, but we can't get a pointer to it!
return;
- case NestedNameSpecifier::Super:
- add(NNS->getAsRecordDecl(), Flags);
+ case NestedNameSpecifier::Kind::MicrosoftSuper:
+ add(NNS.getAsMicrosoftSuper(), Flags);
return;
+ case NestedNameSpecifier::Kind::Null:
+ llvm_unreachable("unexpected null nested name specifier");
}
- llvm_unreachable("unhandled NestedNameSpecifier::SpecifierKind");
+ llvm_unreachable("unhandled NestedNameSpecifier::Kind");
}
void add(const CXXCtorInitializer *CCI, RelSet Flags) {
@@ -555,7 +544,7 @@ allTargetDecls(const DynTypedNode &N, const HeuristicResolver *Resolver) {
else if (const NestedNameSpecifierLoc *NNSL = N.get<NestedNameSpecifierLoc>())
Finder.add(NNSL->getNestedNameSpecifier(), Flags);
else if (const NestedNameSpecifier *NNS = N.get<NestedNameSpecifier>())
- Finder.add(NNS, Flags);
+ Finder.add(*NNS, Flags);
else if (const TypeLoc *TL = N.get<TypeLoc>())
Finder.add(TL->getType(), Flags);
else if (const QualType *QT = N.get<QualType>())
@@ -861,32 +850,25 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) {
const HeuristicResolver *Resolver;
llvm::SmallVector<ReferenceLoc> Refs;
- void VisitElaboratedTypeLoc(ElaboratedTypeLoc L) {
- // We only know about qualifier, rest if filled by inner locations.
- size_t InitialSize = Refs.size();
- Visit(L.getNamedTypeLoc().getUnqualifiedLoc());
- size_t NewSize = Refs.size();
- // Add qualifier for the newly-added refs.
- for (unsigned I = InitialSize; I < NewSize; ++I) {
- ReferenceLoc *Ref = &Refs[I];
- // Fill in the qualifier.
- assert(!Ref->Qualifier.hasQualifier() && "qualifier already set");
- Ref->Qualifier = L.getQualifierLoc();
- }
+ void VisitUnresolvedUsingTypeLoc(UnresolvedUsingTypeLoc L) {
+ Refs.push_back(ReferenceLoc{L.getQualifierLoc(),
+ L.getLocalSourceRange().getBegin(),
+ /*IsDecl=*/false,
+ {L.getDecl()}});
}
void VisitUsingTypeLoc(UsingTypeLoc L) {
- Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
+ Refs.push_back(ReferenceLoc{L.getQualifierLoc(),
L.getLocalSourceRange().getBegin(),
/*IsDecl=*/false,
- {L.getFoundDecl()}});
+ {L.getDecl()}});
}
void VisitTagTypeLoc(TagTypeLoc L) {
- Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
+ Refs.push_back(ReferenceLoc{L.getQualifierLoc(),
L.getNameLoc(),
/*IsDecl=*/false,
- {L.getDecl()}});
+ {L.getOriginalDecl()}});
}
void VisitTemplateTypeParmTypeLoc(TemplateTypeParmTypeLoc L) {
@@ -906,25 +888,18 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) {
// 2. 'vector<int>' with mask 'Underlying'.
// we want to return only #1 in this case.
Refs.push_back(ReferenceLoc{
- NestedNameSpecifierLoc(), L.getTemplateNameLoc(), /*IsDecl=*/false,
+ L.getQualifierLoc(), L.getTemplateNameLoc(), /*IsDecl=*/false,
explicitReferenceTargets(DynTypedNode::create(L.getType()),
DeclRelation::Alias, Resolver)});
}
void VisitDeducedTemplateSpecializationTypeLoc(
DeducedTemplateSpecializationTypeLoc L) {
Refs.push_back(ReferenceLoc{
- NestedNameSpecifierLoc(), L.getNameLoc(), /*IsDecl=*/false,
+ L.getQualifierLoc(), L.getNameLoc(), /*IsDecl=*/false,
explicitReferenceTargets(DynTypedNode::create(L.getType()),
DeclRelation::Alias, Resolver)});
}
- void VisitInjectedClassNameTypeLoc(InjectedClassNameTypeLoc TL) {
- Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
- TL.getNameLoc(),
- /*IsDecl=*/false,
- {TL.getDecl()}});
- }
-
void VisitDependentTemplateSpecializationTypeLoc(
DependentTemplateSpecializationTypeLoc L) {
Refs.push_back(
@@ -943,12 +918,12 @@ refInTypeLoc(TypeLoc L, const HeuristicResolver *Resolver) {
}
void VisitTypedefTypeLoc(TypedefTypeLoc L) {
- if (shouldSkipTypedef(L.getTypedefNameDecl()))
+ if (shouldSkipTypedef(L.getDecl()))
return;
- Refs.push_back(ReferenceLoc{NestedNameSpecifierLoc(),
+ Refs.push_back(ReferenceLoc{L.getQualifierLoc(),
L.getNameLoc(),
/*IsDecl=*/false,
- {L.getTypedefNameDecl()}});
+ {L.getDecl()}});
}
void VisitObjCInterfaceTypeLoc(ObjCInterfaceTypeLoc L) {
@@ -980,17 +955,6 @@ public:
return true;
}
- bool TraverseElaboratedTypeLoc(ElaboratedTypeLoc L) {
- // ElaboratedTypeLoc will reports information for its inner type loc.
- // Otherwise we loose information about inner types loc's qualifier.
- TypeLoc Inner = L.getNamedTypeLoc().getUnqualifiedLoc();
- if (L.getBeginLoc() == Inner.getBeginLoc())
- return RecursiveASTVisitor::TraverseTypeLoc(Inner);
- else
- TypeLocsToSkip.insert(Inner.getBeginLoc());
- return RecursiveASTVisitor::TraverseElaboratedTypeLoc(L);
- }
-
bool VisitStmt(Stmt *S) {
visitNode(DynTypedNode::create(*S));
return true;
@@ -1051,7 +1015,7 @@ public:
return true;
visitNode(DynTypedNode::create(L));
// Inner type is missing information about its qualifier, skip it.
- if (auto TL = L.getTypeLoc())
+ if (auto TL = L.getAsTypeLoc())
TypeLocsToSkip.insert(TL.getBeginLoc());
return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(L);
}
@@ -1092,12 +1056,21 @@ private:
if (auto *S = N.get<Stmt>())
return refInStmt(S, Resolver);
if (auto *NNSL = N.get<NestedNameSpecifierLoc>()) {
- // (!) 'DeclRelation::Alias' ensures we do not loose namespace aliases.
- return {ReferenceLoc{
- NNSL->getPrefix(), NNSL->getLocalBeginLoc(), false,
- explicitReferenceTargets(
- DynTypedNode::create(*NNSL->getNestedNameSpecifier()),
- DeclRelation::Alias, Resolver)}};
+ // (!) 'DeclRelation::Alias' ensures we do not lose namespace aliases.
+ NestedNameSpecifierLoc Qualifier;
+ SourceLocation NameLoc;
+ if (auto TL = NNSL->getAsTypeLoc()) {
+ Qualifier = TL.getPrefix();
+ NameLoc = TL.getNonPrefixBeginLoc();
+ } else {
+ Qualifier = NNSL->getAsNamespaceAndPrefix().Prefix;
+ NameLoc = NNSL->getLocalBeginLoc();
+ }
+ return {
+ ReferenceLoc{Qualifier, NameLoc, false,
+ explicitReferenceTargets(
+ DynTypedNode::create(NNSL->getNestedNameSpecifier()),
+ DeclRelation::Alias, Resolver)}};
}
if (const TypeLoc *TL = N.get<TypeLoc>())
return refInTypeLoc(*TL, Resolver);
@@ -1210,8 +1183,8 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, ReferenceLoc R) {
OS << "}";
if (R.Qualifier) {
OS << ", qualifier = '";
- R.Qualifier.getNestedNameSpecifier()->print(OS,
- PrintingPolicy(LangOptions()));
+ R.Qualifier.getNestedNameSpecifier().print(OS,
+ PrintingPolicy(LangOptions()));
OS << "'";
}
if (R.IsDecl)
diff --git a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
index 7c0eb96..c6afd0b 100644
--- a/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
+++ b/clang-tools-extra/clangd/GlobalCompilationDatabase.cpp
@@ -833,6 +833,10 @@ bool OverlayCDB::setCompileCommand(PathRef File,
std::unique_ptr<ProjectModules>
OverlayCDB::getProjectModules(PathRef File) const {
auto MDB = DelegatingCDB::getProjectModules(File);
+ if (!MDB) {
+ log("Failed to get compilation Database for {0}", File);
+ return {};
+ }
MDB->setCommandMangler([&Mangler = Mangler](tooling::CompileCommand &Command,
PathRef CommandPath) {
Mangler(Command, CommandPath);
diff --git a/clang-tools-extra/clangd/Hover.cpp b/clang-tools-extra/clangd/Hover.cpp
index 1e0718d..9eec322 100644
--- a/clang-tools-extra/clangd/Hover.cpp
+++ b/clang-tools-extra/clangd/Hover.cpp
@@ -18,6 +18,7 @@
#include "Protocol.h"
#include "Selection.h"
#include "SourceCode.h"
+#include "SymbolDocumentation.h"
#include "clang-include-cleaner/Analysis.h"
#include "clang-include-cleaner/IncludeSpeller.h"
#include "clang-include-cleaner/Types.h"
@@ -41,6 +42,7 @@
#include "clang/AST/Type.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/LLVM.h"
+#include "clang/Basic/LangOptions.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/Specifiers.h"
@@ -170,13 +172,14 @@ HoverInfo::PrintedType printType(QualType QT, ASTContext &ASTCtx,
QT = QT->castAs<DecltypeType>()->getUnderlyingType();
HoverInfo::PrintedType Result;
llvm::raw_string_ostream OS(Result.Type);
- // Special case: if the outer type is a tag type without qualifiers, then
- // include the tag for extra clarity.
- // This isn't very idiomatic, so don't attempt it for complex cases, including
- // pointers/references, template specializations, etc.
+ // Special case: if the outer type is a canonical tag type, then include the
+ // tag for extra clarity. This isn't very idiomatic, so don't attempt it for
+ // complex cases, including pointers/references, template specializations,
+ // etc.
if (!QT.isNull() && !QT.hasQualifiers() && PP.SuppressTagKeyword) {
- if (auto *TT = llvm::dyn_cast<TagType>(QT.getTypePtr()))
- OS << TT->getDecl()->getKindName() << " ";
+ if (auto *TT = llvm::dyn_cast<TagType>(QT.getTypePtr());
+ TT && TT->isCanonicalUnqualified())
+ OS << TT->getOriginalDecl()->getKindName() << " ";
}
QT.print(OS, PP);
@@ -451,8 +454,7 @@ std::optional<std::string> printExprValue(const Expr *E,
Constant.Val.getInt().getSignificantBits() <= 64) {
// Compare to int64_t to avoid bit-width match requirements.
int64_t Val = Constant.Val.getInt().getExtValue();
- for (const EnumConstantDecl *ECD :
- T->castAs<EnumType>()->getDecl()->enumerators())
+ for (const EnumConstantDecl *ECD : T->castAsEnumDecl()->enumerators())
if (ECD->getInitVal() == Val)
return llvm::formatv("{0} ({1})", ECD->getNameAsString(),
printHex(Constant.Val.getInt()))
@@ -627,6 +629,9 @@ HoverInfo getHoverContents(const NamedDecl *D, const PrintingPolicy &PP,
HI.Name = printName(Ctx, *D);
const auto *CommentD = getDeclForComment(D);
HI.Documentation = getDeclComment(Ctx, *CommentD);
+ // save the language options to be able to create the comment::CommandTraits
+ // to parse the documentation
+ HI.CommentOpts = D->getASTContext().getLangOpts().CommentOpts;
enhanceFromIndex(HI, *CommentD, Index);
if (HI.Documentation.empty())
HI.Documentation = synthesizeDocumentation(D);
@@ -789,7 +794,9 @@ HoverInfo getHoverContents(const DefinedMacro &Macro, const syntax::Token &Tok,
for (const auto &ExpandedTok : Expansion->Expanded) {
ExpansionText += ExpandedTok.text(SM);
ExpansionText += " ";
- if (ExpansionText.size() > 2048) {
+ const Config &Cfg = Config::current();
+ const size_t Limit = static_cast<size_t>(Cfg.Hover.MacroContentsLimit);
+ if (Limit && ExpansionText.size() > Limit) {
ExpansionText.clear();
break;
}
@@ -826,7 +833,7 @@ std::optional<HoverInfo> getThisExprHoverContents(const CXXThisExpr *CTE,
ASTContext &ASTCtx,
const PrintingPolicy &PP) {
QualType OriginThisType = CTE->getType()->getPointeeType();
- QualType ClassType = declaredType(OriginThisType->getAsTagDecl());
+ QualType ClassType = declaredType(OriginThisType->castAsTagDecl());
// For partial specialization class, origin `this` pointee type will be
// parsed as `InjectedClassNameType`, which will ouput template arguments
// like "type-parameter-0-0". So we retrieve user written class type in this
@@ -967,10 +974,11 @@ void addLayoutInfo(const NamedDecl &ND, HoverInfo &HI) {
const auto &Ctx = ND.getASTContext();
if (auto *RD = llvm::dyn_cast<RecordDecl>(&ND)) {
- if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RD->getTypeForDecl()))
+ CanQualType RT = Ctx.getCanonicalTagType(RD);
+ if (auto Size = Ctx.getTypeSizeInCharsIfKnown(RT))
HI.Size = Size->getQuantity() * 8;
if (!RD->isDependentType() && RD->isCompleteDefinition())
- HI.Align = Ctx.getTypeAlign(RD->getTypeForDecl());
+ HI.Align = Ctx.getTypeAlign(RT);
return;
}
@@ -1267,10 +1275,10 @@ std::optional<HoverInfo> getHover(ParsedAST &AST, Position Pos,
HoverCountMetric.record(1, "include");
HoverInfo HI;
HI.Name = std::string(llvm::sys::path::filename(Inc.Resolved));
- // FIXME: We don't have a fitting value for Kind.
HI.Definition =
URIForFile::canonicalize(Inc.Resolved, AST.tuPath()).file().str();
HI.DefinitionLanguage = "";
+ HI.Kind = index::SymbolKind::IncludeDirective;
maybeAddUsedSymbols(AST, HI, Inc);
return HI;
}
@@ -1388,9 +1396,241 @@ static std::string formatOffset(uint64_t OffsetInBits) {
return Offset;
}
-markup::Document HoverInfo::present() const {
+void HoverInfo::calleeArgInfoToMarkupParagraph(markup::Paragraph &P) const {
+ assert(CallPassType);
+ std::string Buffer;
+ llvm::raw_string_ostream OS(Buffer);
+ OS << "Passed ";
+ if (CallPassType->PassBy != HoverInfo::PassType::Value) {
+ OS << "by ";
+ if (CallPassType->PassBy == HoverInfo::PassType::ConstRef)
+ OS << "const ";
+ OS << "reference ";
+ }
+ if (CalleeArgInfo->Name)
+ OS << "as " << CalleeArgInfo->Name;
+ else if (CallPassType->PassBy == HoverInfo::PassType::Value)
+ OS << "by value";
+ if (CallPassType->Converted && CalleeArgInfo->Type)
+ OS << " (converted to " << CalleeArgInfo->Type->Type << ")";
+ P.appendText(OS.str());
+}
+
+void HoverInfo::usedSymbolNamesToMarkup(markup::Document &Output) const {
+ markup::Paragraph &P = Output.addParagraph();
+ P.appendText("provides ");
+
+ const std::vector<std::string>::size_type SymbolNamesLimit = 5;
+ auto Front = llvm::ArrayRef(UsedSymbolNames).take_front(SymbolNamesLimit);
+
+ llvm::interleave(
+ Front, [&](llvm::StringRef Sym) { P.appendCode(Sym); },
+ [&] { P.appendText(", "); });
+ if (UsedSymbolNames.size() > Front.size()) {
+ P.appendText(" and ");
+ P.appendText(std::to_string(UsedSymbolNames.size() - Front.size()));
+ P.appendText(" more");
+ }
+}
+
+void HoverInfo::providerToMarkupParagraph(markup::Document &Output) const {
+ markup::Paragraph &DI = Output.addParagraph();
+ DI.appendText("provided by");
+ DI.appendSpace();
+ DI.appendCode(Provider);
+}
+
+void HoverInfo::definitionScopeToMarkup(markup::Document &Output) const {
+ std::string Buffer;
+
+ // Append scope comment, dropping trailing "::".
+ // Note that we don't print anything for global namespace, to not annoy
+ // non-c++ projects or projects that are not making use of namespaces.
+ if (!LocalScope.empty()) {
+ // Container name, e.g. class, method, function.
+ // We might want to propagate some info about container type to print
+ // function foo, class X, method X::bar, etc.
+ Buffer += "// In " + llvm::StringRef(LocalScope).rtrim(':').str() + '\n';
+ } else if (NamespaceScope && !NamespaceScope->empty()) {
+ Buffer += "// In namespace " +
+ llvm::StringRef(*NamespaceScope).rtrim(':').str() + '\n';
+ }
+
+ if (!AccessSpecifier.empty()) {
+ Buffer += AccessSpecifier + ": ";
+ }
+
+ Buffer += Definition;
+
+ Output.addCodeBlock(Buffer, DefinitionLanguage);
+}
+
+void HoverInfo::valueToMarkupParagraph(markup::Paragraph &P) const {
+ P.appendText("Value = ");
+ P.appendCode(*Value);
+}
+
+void HoverInfo::offsetToMarkupParagraph(markup::Paragraph &P) const {
+ P.appendText("Offset: " + formatOffset(*Offset));
+}
+
+void HoverInfo::sizeToMarkupParagraph(markup::Paragraph &P) const {
+ P.appendText("Size: " + formatSize(*Size));
+ if (Padding && *Padding != 0) {
+ P.appendText(llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str());
+ }
+ if (Align)
+ P.appendText(", alignment " + formatSize(*Align));
+}
+
+markup::Document HoverInfo::presentDoxygen() const {
+
markup::Document Output;
+ // Header contains a text of the form:
+ // variable `var`
+ //
+ // class `X`
+ //
+ // function `foo`
+ //
+ // expression
+ //
+ // Note that we are making use of a level-3 heading because VSCode renders
+ // level 1 and 2 headers in a huge font, see
+ // https://github.com/microsoft/vscode/issues/88417 for details.
+ markup::Paragraph &Header = Output.addHeading(3);
+ if (Kind != index::SymbolKind::Unknown &&
+ Kind != index::SymbolKind::IncludeDirective)
+ Header.appendText(index::getSymbolKindString(Kind)).appendSpace();
+ assert(!Name.empty() && "hover triggered on a nameless symbol");
+
+ if (Kind == index::SymbolKind::IncludeDirective) {
+ Header.appendCode(Name);
+
+ if (!Definition.empty())
+ Output.addParagraph().appendCode(Definition);
+
+ if (!UsedSymbolNames.empty()) {
+ Output.addRuler();
+ usedSymbolNamesToMarkup(Output);
+ }
+
+ return Output;
+ }
+
+ if (!Definition.empty()) {
+ Output.addRuler();
+ definitionScopeToMarkup(Output);
+ } else {
+ Header.appendCode(Name);
+ }
+
+ if (!Provider.empty()) {
+ providerToMarkupParagraph(Output);
+ }
+
+ // Put a linebreak after header to increase readability.
+ Output.addRuler();
+
+ SymbolDocCommentVisitor SymbolDoc(Documentation, CommentOpts);
+
+ if (SymbolDoc.hasBriefCommand()) {
+ SymbolDoc.briefToMarkup(Output.addParagraph());
+ Output.addRuler();
+ }
+
+ // For functions we display signature in a list form, e.g.:
+ // Template Parameters:
+ // - `typename T` - description
+ // Parameters:
+ // - `bool param1` - description
+ // - `int param2 = 5` - description
+ // Returns
+ // `type` - description
+ if (TemplateParameters && !TemplateParameters->empty()) {
+ Output.addParagraph().appendBoldText("Template Parameters:");
+ markup::BulletList &L = Output.addBulletList();
+ for (const auto &Param : *TemplateParameters) {
+ markup::Paragraph &P = L.addItem().addParagraph();
+ P.appendCode(llvm::to_string(Param));
+ if (SymbolDoc.isTemplateTypeParmDocumented(llvm::to_string(Param.Name))) {
+ P.appendText(" - ");
+ SymbolDoc.templateTypeParmDocToMarkup(llvm::to_string(Param.Name), P);
+ }
+ }
+ Output.addRuler();
+ }
+
+ if (Parameters && !Parameters->empty()) {
+ Output.addParagraph().appendBoldText("Parameters:");
+ markup::BulletList &L = Output.addBulletList();
+ for (const auto &Param : *Parameters) {
+ markup::Paragraph &P = L.addItem().addParagraph();
+ P.appendCode(llvm::to_string(Param));
+
+ if (SymbolDoc.isParameterDocumented(llvm::to_string(Param.Name))) {
+ P.appendText(" - ");
+ SymbolDoc.parameterDocToMarkup(llvm::to_string(Param.Name), P);
+ }
+ }
+ Output.addRuler();
+ }
+
+ // Print Types on their own lines to reduce chances of getting line-wrapped by
+ // editor, as they might be long.
+ if (ReturnType &&
+ ((ReturnType->Type != "void" && !ReturnType->AKA.has_value()) ||
+ (ReturnType->AKA.has_value() && ReturnType->AKA != "void"))) {
+ Output.addParagraph().appendBoldText("Returns:");
+ markup::Paragraph &P = Output.addParagraph();
+ P.appendCode(llvm::to_string(*ReturnType));
+
+ if (SymbolDoc.hasReturnCommand()) {
+ P.appendText(" - ");
+ SymbolDoc.returnToMarkup(P);
+ }
+ Output.addRuler();
+ }
+
+ // add specially handled doxygen commands.
+ SymbolDoc.warningsToMarkup(Output);
+ SymbolDoc.notesToMarkup(Output);
+
+ // add any other documentation.
+ SymbolDoc.docToMarkup(Output);
+
+ Output.addRuler();
+
+ // Don't print Type after Parameters or ReturnType as this will just duplicate
+ // the information
+ if (Type && !ReturnType && !Parameters)
+ Output.addParagraph().appendText("Type: ").appendCode(
+ llvm::to_string(*Type));
+ if (Value) {
+ valueToMarkupParagraph(Output.addParagraph());
+ }
+
+ if (Offset)
+ offsetToMarkupParagraph(Output.addParagraph());
+ if (Size) {
+ sizeToMarkupParagraph(Output.addParagraph());
+ }
+
+ if (CalleeArgInfo) {
+ calleeArgInfoToMarkupParagraph(Output.addParagraph());
+ }
+
+ if (!UsedSymbolNames.empty()) {
+ Output.addRuler();
+ usedSymbolNamesToMarkup(Output);
+ }
+
+ return Output;
+}
+
+markup::Document HoverInfo::presentDefault() const {
+ markup::Document Output;
// Header contains a text of the form:
// variable `var`
//
@@ -1404,17 +1644,14 @@ markup::Document HoverInfo::present() const {
// level 1 and 2 headers in a huge font, see
// https://github.com/microsoft/vscode/issues/88417 for details.
markup::Paragraph &Header = Output.addHeading(3);
- if (Kind != index::SymbolKind::Unknown)
+ if (Kind != index::SymbolKind::Unknown &&
+ Kind != index::SymbolKind::IncludeDirective)
Header.appendText(index::getSymbolKindString(Kind)).appendSpace();
assert(!Name.empty() && "hover triggered on a nameless symbol");
Header.appendCode(Name);
if (!Provider.empty()) {
- markup::Paragraph &DI = Output.addParagraph();
- DI.appendText("provided by");
- DI.appendSpace();
- DI.appendCode(Provider);
- Output.addRuler();
+ providerToMarkupParagraph(Output);
}
// Put a linebreak after header to increase readability.
@@ -1432,7 +1669,7 @@ markup::Document HoverInfo::present() const {
}
if (Parameters && !Parameters->empty()) {
- Output.addParagraph().appendText("Parameters: ");
+ Output.addParagraph().appendText("Parameters:");
markup::BulletList &L = Output.addBulletList();
for (const auto &Param : *Parameters)
L.addItem().addParagraph().appendCode(llvm::to_string(Param));
@@ -1445,41 +1682,17 @@ markup::Document HoverInfo::present() const {
llvm::to_string(*Type));
if (Value) {
- markup::Paragraph &P = Output.addParagraph();
- P.appendText("Value = ");
- P.appendCode(*Value);
+ valueToMarkupParagraph(Output.addParagraph());
}
if (Offset)
- Output.addParagraph().appendText("Offset: " + formatOffset(*Offset));
+ offsetToMarkupParagraph(Output.addParagraph());
if (Size) {
- auto &P = Output.addParagraph().appendText("Size: " + formatSize(*Size));
- if (Padding && *Padding != 0) {
- P.appendText(
- llvm::formatv(" (+{0} padding)", formatSize(*Padding)).str());
- }
- if (Align)
- P.appendText(", alignment " + formatSize(*Align));
+ sizeToMarkupParagraph(Output.addParagraph());
}
if (CalleeArgInfo) {
- assert(CallPassType);
- std::string Buffer;
- llvm::raw_string_ostream OS(Buffer);
- OS << "Passed ";
- if (CallPassType->PassBy != HoverInfo::PassType::Value) {
- OS << "by ";
- if (CallPassType->PassBy == HoverInfo::PassType::ConstRef)
- OS << "const ";
- OS << "reference ";
- }
- if (CalleeArgInfo->Name)
- OS << "as " << CalleeArgInfo->Name;
- else if (CallPassType->PassBy == HoverInfo::PassType::Value)
- OS << "by value";
- if (CallPassType->Converted && CalleeArgInfo->Type)
- OS << " (converted to " << CalleeArgInfo->Type->Type << ")";
- Output.addParagraph().appendText(OS.str());
+ calleeArgInfoToMarkupParagraph(Output.addParagraph());
}
if (!Documentation.empty())
@@ -1487,49 +1700,12 @@ markup::Document HoverInfo::present() const {
if (!Definition.empty()) {
Output.addRuler();
- std::string Buffer;
-
- if (!Definition.empty()) {
- // Append scope comment, dropping trailing "::".
- // Note that we don't print anything for global namespace, to not annoy
- // non-c++ projects or projects that are not making use of namespaces.
- if (!LocalScope.empty()) {
- // Container name, e.g. class, method, function.
- // We might want to propagate some info about container type to print
- // function foo, class X, method X::bar, etc.
- Buffer +=
- "// In " + llvm::StringRef(LocalScope).rtrim(':').str() + '\n';
- } else if (NamespaceScope && !NamespaceScope->empty()) {
- Buffer += "// In namespace " +
- llvm::StringRef(*NamespaceScope).rtrim(':').str() + '\n';
- }
-
- if (!AccessSpecifier.empty()) {
- Buffer += AccessSpecifier + ": ";
- }
-
- Buffer += Definition;
- }
-
- Output.addCodeBlock(Buffer, DefinitionLanguage);
+ definitionScopeToMarkup(Output);
}
if (!UsedSymbolNames.empty()) {
Output.addRuler();
- markup::Paragraph &P = Output.addParagraph();
- P.appendText("provides ");
-
- const std::vector<std::string>::size_type SymbolNamesLimit = 5;
- auto Front = llvm::ArrayRef(UsedSymbolNames).take_front(SymbolNamesLimit);
-
- llvm::interleave(
- Front, [&](llvm::StringRef Sym) { P.appendCode(Sym); },
- [&] { P.appendText(", "); });
- if (UsedSymbolNames.size() > Front.size()) {
- P.appendText(" and ");
- P.appendText(std::to_string(UsedSymbolNames.size() - Front.size()));
- P.appendText(" more");
- }
+ usedSymbolNamesToMarkup(Output);
}
return Output;
@@ -1538,21 +1714,19 @@ markup::Document HoverInfo::present() const {
std::string HoverInfo::present(MarkupKind Kind) const {
if (Kind == MarkupKind::Markdown) {
const Config &Cfg = Config::current();
- if ((Cfg.Documentation.CommentFormat ==
- Config::CommentFormatPolicy::Markdown) ||
- (Cfg.Documentation.CommentFormat ==
- Config::CommentFormatPolicy::Doxygen))
- // If the user prefers Markdown, we use the present() method to generate
- // the Markdown output.
- return present().asMarkdown();
+ if (Cfg.Documentation.CommentFormat ==
+ Config::CommentFormatPolicy::Markdown)
+ return presentDefault().asMarkdown();
+ if (Cfg.Documentation.CommentFormat == Config::CommentFormatPolicy::Doxygen)
+ return presentDoxygen().asMarkdown();
if (Cfg.Documentation.CommentFormat ==
Config::CommentFormatPolicy::PlainText)
// If the user prefers plain text, we use the present() method to generate
// the plain text output.
- return present().asEscapedMarkdown();
+ return presentDefault().asEscapedMarkdown();
}
- return present().asPlainText();
+ return presentDefault().asPlainText();
}
// If the backtick at `Offset` starts a probable quoted range, return the range
diff --git a/clang-tools-extra/clangd/Hover.h b/clang-tools-extra/clangd/Hover.h
index 2f65431..614180a 100644
--- a/clang-tools-extra/clangd/Hover.h
+++ b/clang-tools-extra/clangd/Hover.h
@@ -74,6 +74,8 @@ struct HoverInfo {
std::optional<Range> SymRange;
index::SymbolKind Kind = index::SymbolKind::Unknown;
std::string Documentation;
+ // required to create a comments::CommandTraits object without the ASTContext
+ CommentOptions CommentOpts;
/// Source code containing the definition of the symbol.
std::string Definition;
const char *DefinitionLanguage = "cpp";
@@ -118,10 +120,23 @@ struct HoverInfo {
// alphabetical order.
std::vector<std::string> UsedSymbolNames;
- /// Produce a user-readable information.
- markup::Document present() const;
-
+ /// Produce a user-readable information based on the specified markup kind.
std::string present(MarkupKind Kind) const;
+
+private:
+ void usedSymbolNamesToMarkup(markup::Document &Output) const;
+ void providerToMarkupParagraph(markup::Document &Output) const;
+ void definitionScopeToMarkup(markup::Document &Output) const;
+ void calleeArgInfoToMarkupParagraph(markup::Paragraph &P) const;
+ void valueToMarkupParagraph(markup::Paragraph &P) const;
+ void offsetToMarkupParagraph(markup::Paragraph &P) const;
+ void sizeToMarkupParagraph(markup::Paragraph &P) const;
+
+ /// Parse and render the hover information as Doxygen documentation.
+ markup::Document presentDoxygen() const;
+
+ /// Render the hover information as a default documentation.
+ markup::Document presentDefault() const;
};
inline bool operator==(const HoverInfo::PrintedType &LHS,
diff --git a/clang-tools-extra/clangd/IncludeFixer.cpp b/clang-tools-extra/clangd/IncludeFixer.cpp
index 50bc2bd..c27d960 100644
--- a/clang-tools-extra/clangd/IncludeFixer.cpp
+++ b/clang-tools-extra/clangd/IncludeFixer.cpp
@@ -173,7 +173,7 @@ std::vector<Fix> IncludeFixer::fix(DiagnosticsEngine::Level DiagLevel,
// `enum x : int;' is not formally an incomplete type.
// We may need a full definition anyway.
if (auto * ET = llvm::dyn_cast<EnumType>(T))
- if (!ET->getDecl()->getDefinition())
+ if (!ET->getOriginalDecl()->getDefinition())
return fixIncompleteType(*T);
}
}
@@ -400,35 +400,35 @@ std::optional<CheapUnresolvedName> extractUnresolvedNameCheaply(
CheapUnresolvedName Result;
Result.Name = Unresolved.getAsString();
if (SS && SS->isNotEmpty()) { // "::" or "ns::"
- if (auto *Nested = SS->getScopeRep()) {
- if (Nested->getKind() == NestedNameSpecifier::Global) {
- Result.ResolvedScope = "";
- } else if (const NamespaceBaseDecl *NSB = Nested->getAsNamespace()) {
- if (const auto *NS = dyn_cast<NamespaceDecl>(NSB)) {
- std::string SpecifiedNS = printNamespaceScope(*NS);
- std::optional<std::string> Spelling = getSpelledSpecifier(*SS, SM);
-
- // Check the specifier spelled in the source.
- // If the resolved scope doesn't end with the spelled scope, the
- // resolved scope may come from a sema typo correction. For example,
- // sema assumes that "clangd::" is a typo of "clang::" and uses
- // "clang::" as the specified scope in:
- // namespace clang { clangd::X; }
- // In this case, we use the "typo" specifier as extra scope instead
- // of using the scope assumed by sema.
- if (!Spelling || llvm::StringRef(SpecifiedNS).ends_with(*Spelling)) {
- Result.ResolvedScope = std::move(SpecifiedNS);
- } else {
- Result.UnresolvedScope = std::move(*Spelling);
- }
+ NestedNameSpecifier Nested = SS->getScopeRep();
+ if (Nested.getKind() == NestedNameSpecifier::Kind::Global) {
+ Result.ResolvedScope = "";
+ } else if (Nested.getKind() == NestedNameSpecifier::Kind::Namespace) {
+ const NamespaceBaseDecl *NSB = Nested.getAsNamespaceAndPrefix().Namespace;
+ if (const auto *NS = dyn_cast<NamespaceDecl>(NSB)) {
+ std::string SpecifiedNS = printNamespaceScope(*NS);
+ std::optional<std::string> Spelling = getSpelledSpecifier(*SS, SM);
+
+ // Check the specifier spelled in the source.
+ // If the resolved scope doesn't end with the spelled scope, the
+ // resolved scope may come from a sema typo correction. For example,
+ // sema assumes that "clangd::" is a typo of "clang::" and uses
+ // "clang::" as the specified scope in:
+ // namespace clang { clangd::X; }
+ // In this case, we use the "typo" specifier as extra scope instead
+ // of using the scope assumed by sema.
+ if (!Spelling || llvm::StringRef(SpecifiedNS).ends_with(*Spelling)) {
+ Result.ResolvedScope = std::move(SpecifiedNS);
} else {
- Result.ResolvedScope = printNamespaceScope(*cast<NamespaceAliasDecl>(NSB)->getNamespace());
+ Result.UnresolvedScope = std::move(*Spelling);
}
} else {
- // We don't fix symbols in scopes that are not top-level e.g. class
- // members, as we don't collect includes for them.
- return std::nullopt;
+ Result.ResolvedScope = printNamespaceScope(*cast<NamespaceAliasDecl>(NSB)->getNamespace());
}
+ } else {
+ // We don't fix symbols in scopes that are not top-level e.g. class
+ // members, as we don't collect includes for them.
+ return std::nullopt;
}
}
diff --git a/clang-tools-extra/clangd/InlayHints.cpp b/clang-tools-extra/clangd/InlayHints.cpp
index 197c62c..cd479e1 100644
--- a/clang-tools-extra/clangd/InlayHints.cpp
+++ b/clang-tools-extra/clangd/InlayHints.cpp
@@ -55,18 +55,24 @@ void stripLeadingUnderscores(StringRef &Name) { Name = Name.ltrim('_'); }
// getDeclForType() returns the decl responsible for Type's spelling.
// This is the inverse of ASTContext::getTypeDeclType().
-template <typename Ty, typename = decltype(((Ty *)nullptr)->getDecl())>
-const NamedDecl *getDeclForTypeImpl(const Ty *T) {
- return T->getDecl();
-}
-const NamedDecl *getDeclForTypeImpl(const void *T) { return nullptr; }
const NamedDecl *getDeclForType(const Type *T) {
switch (T->getTypeClass()) {
-#define ABSTRACT_TYPE(TY, BASE)
-#define TYPE(TY, BASE) \
- case Type::TY: \
- return getDeclForTypeImpl(llvm::cast<TY##Type>(T));
-#include "clang/AST/TypeNodes.inc"
+ case Type::Enum:
+ case Type::Record:
+ case Type::InjectedClassName:
+ return cast<TagType>(T)->getOriginalDecl();
+ case Type::TemplateSpecialization:
+ return cast<TemplateSpecializationType>(T)
+ ->getTemplateName()
+ .getAsTemplateDecl(/*IgnoreDeduced=*/true);
+ case Type::Typedef:
+ return cast<TypedefType>(T)->getDecl();
+ case Type::UnresolvedUsing:
+ return cast<UnresolvedUsingType>(T)->getDecl();
+ case Type::Using:
+ return cast<UsingType>(T)->getDecl();
+ default:
+ return nullptr;
}
llvm_unreachable("Unknown TypeClass enum");
}
@@ -81,8 +87,6 @@ llvm::StringRef getSimpleName(const NamedDecl &D) {
return getSimpleName(D.getDeclName());
}
llvm::StringRef getSimpleName(QualType T) {
- if (const auto *ET = llvm::dyn_cast<ElaboratedType>(T))
- return getSimpleName(ET->getNamedType());
if (const auto *BT = llvm::dyn_cast<BuiltinType>(T)) {
PrintingPolicy PP(LangOptions{});
PP.adjustForCPlusPlus();
diff --git a/clang-tools-extra/clangd/ModulesBuilder.cpp b/clang-tools-extra/clangd/ModulesBuilder.cpp
index 56d00c2..64f22b9 100644
--- a/clang-tools-extra/clangd/ModulesBuilder.cpp
+++ b/clang-tools-extra/clangd/ModulesBuilder.cpp
@@ -14,6 +14,8 @@
#include "clang/Serialization/ASTReader.h"
#include "clang/Serialization/ModuleCache.h"
#include "llvm/ADT/ScopeExit.h"
+#include "llvm/Support/CommandLine.h"
+
#include <queue>
namespace clang {
@@ -21,6 +23,12 @@ namespace clangd {
namespace {
+llvm::cl::opt<bool> DebugModulesBuilder(
+ "debug-modules-builder",
+ llvm::cl::desc("Don't remove clangd's built module files for debugging. "
+ "Remember to remove them later after debugging."),
+ llvm::cl::init(false));
+
// Create a path to store module files. Generally it should be:
//
// {TEMP_DIRS}/clangd/module_files/{hashed-file-name}-%%-%%-%%-%%-%%-%%/.
@@ -122,7 +130,7 @@ struct ModuleFile {
}
~ModuleFile() {
- if (!ModuleFilePath.empty())
+ if (!ModuleFilePath.empty() && !DebugModulesBuilder)
llvm::sys::fs::remove(ModuleFilePath);
}
@@ -315,7 +323,7 @@ buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName,
Cmds += Arg;
}
- clangd::vlog("Failed to compile {0} with command: {1}.", ModuleUnitFileName,
+ clangd::vlog("Failed to compile {0} with command: {1}", ModuleUnitFileName,
Cmds);
std::string BuiltModuleFilesStr = BuiltModuleFiles.getAsString();
@@ -325,7 +333,10 @@ buildModuleFile(llvm::StringRef ModuleName, PathRef ModuleUnitFileName,
return llvm::createStringError(
llvm::formatv("Failed to compile {0}. Use '--log=verbose' to view "
- "detailed failure reasons.",
+ "detailed failure reasons. It is helpful to use "
+ "'--debug-modules-builder' flag to keep the clangd's "
+ "built module files to reproduce the failure for "
+ "debugging. Remember to remove them after debugging.",
ModuleUnitFileName));
}
diff --git a/clang-tools-extra/clangd/Protocol.cpp b/clang-tools-extra/clangd/Protocol.cpp
index 2c858e2..560b8e0 100644
--- a/clang-tools-extra/clangd/Protocol.cpp
+++ b/clang-tools-extra/clangd/Protocol.cpp
@@ -297,6 +297,9 @@ SymbolKind adjustKindToCapability(SymbolKind Kind,
SymbolKind indexSymbolKindToSymbolKind(index::SymbolKind Kind) {
switch (Kind) {
+ // FIXME: for backwards compatibility, the include directive kind is treated
+ // the same as Unknown
+ case index::SymbolKind::IncludeDirective:
case index::SymbolKind::Unknown:
return SymbolKind::Variable;
case index::SymbolKind::Module:
diff --git a/clang-tools-extra/clangd/Quality.cpp b/clang-tools-extra/clangd/Quality.cpp
index 3f630b0..dc4afe1 100644
--- a/clang-tools-extra/clangd/Quality.cpp
+++ b/clang-tools-extra/clangd/Quality.cpp
@@ -143,6 +143,9 @@ categorize(const index::SymbolInfo &D) {
case index::SymbolKind::Parameter:
case index::SymbolKind::NonTypeTemplateParm:
return SymbolQualitySignals::Variable;
+ // FIXME: for backwards compatibility, the include directive kind is treated
+ // the same as Unknown
+ case index::SymbolKind::IncludeDirective:
case index::SymbolKind::Using:
case index::SymbolKind::Module:
case index::SymbolKind::Unknown:
diff --git a/clang-tools-extra/clangd/ScanningProjectModules.cpp b/clang-tools-extra/clangd/ScanningProjectModules.cpp
index e561513..672e996 100644
--- a/clang-tools-extra/clangd/ScanningProjectModules.cpp
+++ b/clang-tools-extra/clangd/ScanningProjectModules.cpp
@@ -122,8 +122,17 @@ ModuleDependencyScanner::scan(PathRef FilePath,
ModuleDependencyInfo Result;
if (ScanningResult->Provides) {
- ModuleNameToSource[ScanningResult->Provides->ModuleName] = FilePath;
Result.ModuleName = ScanningResult->Provides->ModuleName;
+
+ auto [Iter, Inserted] = ModuleNameToSource.try_emplace(
+ ScanningResult->Provides->ModuleName, FilePath);
+
+ if (!Inserted && Iter->second != FilePath) {
+ elog("Detected multiple source files ({0}, {1}) declaring the same "
+ "module: '{2}'. "
+ "Now clangd may find the wrong source in such case.",
+ Iter->second, FilePath, ScanningResult->Provides->ModuleName);
+ }
}
for (auto &Required : ScanningResult->Requires)
diff --git a/clang-tools-extra/clangd/Selection.cpp b/clang-tools-extra/clangd/Selection.cpp
index 277cb87..06165df 100644
--- a/clang-tools-extra/clangd/Selection.cpp
+++ b/clang-tools-extra/clangd/Selection.cpp
@@ -62,7 +62,8 @@ void recordMetrics(const SelectionTree &S, const LangOptions &Lang) {
}
// Return the range covering a node and all its children.
-SourceRange getSourceRange(const DynTypedNode &N) {
+SourceRange getSourceRange(const DynTypedNode &N,
+ bool IncludeQualifier = false) {
// MemberExprs to implicitly access anonymous fields should not claim any
// tokens for themselves. Given:
// struct A { struct { int b; }; };
@@ -80,7 +81,7 @@ SourceRange getSourceRange(const DynTypedNode &N) {
? getSourceRange(DynTypedNode::create(*ME->getBase()))
: SourceRange();
}
- return N.getSourceRange();
+ return N.getSourceRange(IncludeQualifier);
}
// An IntervalSet maintains a set of disjoint subranges of an array.
@@ -643,8 +644,9 @@ public:
}
return traverseNode(X, [&] { return Base::TraverseDecl(X); });
}
- bool TraverseTypeLoc(TypeLoc X) {
- return traverseNode(&X, [&] { return Base::TraverseTypeLoc(X); });
+ bool TraverseTypeLoc(TypeLoc X, bool TraverseQualifier = true) {
+ return traverseNode(
+ &X, [&] { return Base::TraverseTypeLoc(X, TraverseQualifier); });
}
bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc &X) {
return traverseNode(&X,
@@ -690,7 +692,8 @@ public:
// This means we'd never see 'int' in 'const int'! Work around that here.
// (The reason for the behavior is to avoid traversing the nested Type twice,
// but we ignore TraverseType anyway).
- bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QX) {
+ bool TraverseQualifiedTypeLoc(QualifiedTypeLoc QX,
+ bool TraverseQualifier = true) {
return traverseNode<TypeLoc>(
&QX, [&] { return TraverseTypeLoc(QX.getUnqualifiedLoc()); });
}
@@ -698,7 +701,7 @@ public:
return traverseNode(&PL, [&] { return Base::TraverseObjCProtocolLoc(PL); });
}
// Uninteresting parts of the AST that don't have locations within them.
- bool TraverseNestedNameSpecifier(NestedNameSpecifier *) { return true; }
+ bool TraverseNestedNameSpecifier(NestedNameSpecifier) { return true; }
bool TraverseType(QualType) { return true; }
// The DeclStmt for the loop variable claims to cover the whole range
@@ -798,7 +801,7 @@ private:
// An optimization for a common case: nodes outside macro expansions that
// don't intersect the selection may be recursively skipped.
bool canSafelySkipNode(const DynTypedNode &N) {
- SourceRange S = getSourceRange(N);
+ SourceRange S = getSourceRange(N, /*IncludeQualifier=*/true);
if (auto *TL = N.get<TypeLoc>()) {
// FIXME: TypeLoc::getBeginLoc()/getEndLoc() are pretty fragile
// heuristics. We should consider only pruning critical TypeLoc nodes, to
diff --git a/clang-tools-extra/clangd/SemanticHighlighting.cpp b/clang-tools-extra/clangd/SemanticHighlighting.cpp
index e6d5cf7..2b151b1 100644
--- a/clang-tools-extra/clangd/SemanticHighlighting.cpp
+++ b/clang-tools-extra/clangd/SemanticHighlighting.cpp
@@ -1127,21 +1127,6 @@ public:
return RecursiveASTVisitor::TraverseTemplateArgumentLoc(L);
}
- // findExplicitReferences will walk nested-name-specifiers and
- // find anything that can be resolved to a Decl. However, non-leaf
- // components of nested-name-specifiers which are dependent names
- // (kind "Identifier") cannot be resolved to a decl, so we visit
- // them here.
- bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc Q) {
- if (NestedNameSpecifier *NNS = Q.getNestedNameSpecifier()) {
- if (NNS->getKind() == NestedNameSpecifier::Identifier)
- H.addToken(Q.getLocalBeginLoc(), HighlightingKind::Type)
- .addModifier(HighlightingModifier::DependentName)
- .addModifier(HighlightingModifier::ClassScope);
- }
- return RecursiveASTVisitor::TraverseNestedNameSpecifierLoc(Q);
- }
-
private:
HighlightingsBuilder &H;
};
diff --git a/clang-tools-extra/clangd/SemanticSelection.cpp b/clang-tools-extra/clangd/SemanticSelection.cpp
index dd7116e..3353121 100644
--- a/clang-tools-extra/clangd/SemanticSelection.cpp
+++ b/clang-tools-extra/clangd/SemanticSelection.cpp
@@ -175,9 +175,8 @@ llvm::Expected<std::vector<FoldingRange>> getFoldingRanges(ParsedAST &AST) {
return collectFoldingRanges(SyntaxTree, TM);
}
-// FIXME( usaxena95): Collect PP conditional regions, includes and other code
-// regions (e.g. public/private/protected sections of classes, control flow
-// statement bodies).
+// FIXME( usaxena95): Collect includes and other code regions (e.g.
+// public/private/protected sections of classes, control flow statement bodies).
// Related issue: https://github.com/clangd/clangd/issues/310
llvm::Expected<std::vector<FoldingRange>>
getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
@@ -186,12 +185,6 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
auto DirectiveStructure = DirectiveTree::parse(OrigStream);
chooseConditionalBranches(DirectiveStructure, OrigStream);
- // FIXME: Provide ranges in the disabled-PP regions as well.
- auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream);
-
- auto ParseableStream = cook(Preprocessed, genericLangOpts());
- pairBrackets(ParseableStream);
-
std::vector<FoldingRange> Result;
auto AddFoldingRange = [&](Position Start, Position End,
llvm::StringLiteral Kind) {
@@ -220,7 +213,32 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
auto EndPosition = [&](const Token &T) {
return offsetToPosition(Code, EndOffset(T));
};
+
+ // Preprocessor directives
+ auto PPRanges = pairDirectiveRanges(DirectiveStructure, OrigStream);
+ for (const auto &R : PPRanges) {
+ auto BTok = OrigStream.tokens()[R.Begin];
+ auto ETok = OrigStream.tokens()[R.End];
+ if (ETok.Kind == tok::eof)
+ continue;
+ if (BTok.Line >= ETok.Line)
+ continue;
+
+ Position Start = EndPosition(BTok);
+ Position End = StartPosition(ETok);
+ if (LineFoldingOnly)
+ End.line--;
+ AddFoldingRange(Start, End, FoldingRange::REGION_KIND);
+ }
+
+ // FIXME: Provide ranges in the disabled-PP regions as well.
+ auto Preprocessed = DirectiveStructure.stripDirectives(OrigStream);
+
+ auto ParseableStream = cook(Preprocessed, genericLangOpts());
+ pairBrackets(ParseableStream);
+
auto Tokens = ParseableStream.tokens();
+
// Brackets.
for (const auto &Tok : Tokens) {
if (auto *Paired = Tok.pair()) {
@@ -240,6 +258,7 @@ getFoldingRanges(const std::string &Code, bool LineFoldingOnly) {
return OriginalToken(T).Length >= 2 &&
Code.substr(StartOffset(T), 2) == "/*";
};
+
// Multi-line comments.
for (auto *T = Tokens.begin(); T != Tokens.end();) {
if (T->Kind != tok::comment) {
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.cpp b/clang-tools-extra/clangd/SymbolDocumentation.cpp
new file mode 100644
index 0000000..9ae1ef3
--- /dev/null
+++ b/clang-tools-extra/clangd/SymbolDocumentation.cpp
@@ -0,0 +1,386 @@
+//===--- SymbolDocumentation.cpp ==-------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "SymbolDocumentation.h"
+
+#include "support/Markup.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/CommentCommandTraits.h"
+#include "clang/AST/CommentVisitor.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/StringRef.h"
+
+namespace clang {
+namespace clangd {
+namespace {
+
+std::string commandMarkerAsString(comments::CommandMarkerKind CommandMarker) {
+ switch (CommandMarker) {
+ case comments::CommandMarkerKind::CMK_At:
+ return "@";
+ case comments::CommandMarkerKind::CMK_Backslash:
+ return "\\";
+ }
+ llvm_unreachable("Unknown command marker kind");
+}
+
+void commandToMarkup(markup::Paragraph &Out, StringRef Command,
+ comments::CommandMarkerKind CommandMarker,
+ StringRef Args) {
+ Out.appendBoldText(commandMarkerAsString(CommandMarker) + Command.str());
+ Out.appendSpace();
+ if (!Args.empty()) {
+ Out.appendEmphasizedText(Args.str());
+ }
+}
+} // namespace
+
+class ParagraphToMarkupDocument
+ : public comments::ConstCommentVisitor<ParagraphToMarkupDocument> {
+public:
+ ParagraphToMarkupDocument(markup::Paragraph &Out,
+ const comments::CommandTraits &Traits)
+ : Out(Out), Traits(Traits) {}
+
+ void visitParagraphComment(const comments::ParagraphComment *C) {
+ if (!C)
+ return;
+
+ for (const auto *Child = C->child_begin(); Child != C->child_end();
+ ++Child) {
+ visit(*Child);
+ }
+ }
+
+ void visitTextComment(const comments::TextComment *C) {
+ // Always trim leading space after a newline.
+ StringRef Text = C->getText();
+ if (LastChunkEndsWithNewline && C->getText().starts_with(' '))
+ Text = Text.drop_front();
+
+ LastChunkEndsWithNewline = C->hasTrailingNewline();
+ Out.appendText(Text.str() + (LastChunkEndsWithNewline ? "\n" : ""));
+ }
+
+ void visitInlineCommandComment(const comments::InlineCommandComment *C) {
+
+ if (C->getNumArgs() > 0) {
+ std::string ArgText;
+ for (unsigned I = 0; I < C->getNumArgs(); ++I) {
+ if (!ArgText.empty())
+ ArgText += " ";
+ ArgText += C->getArgText(I);
+ }
+
+ switch (C->getRenderKind()) {
+ case comments::InlineCommandRenderKind::Monospaced:
+ Out.appendCode(ArgText);
+ break;
+ case comments::InlineCommandRenderKind::Bold:
+ Out.appendBoldText(ArgText);
+ break;
+ case comments::InlineCommandRenderKind::Emphasized:
+ Out.appendEmphasizedText(ArgText);
+ break;
+ default:
+ commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(),
+ ArgText);
+ break;
+ }
+ } else {
+ if (C->getCommandName(Traits) == "n") {
+ // \n is a special case, it is used to create a new line.
+ Out.appendText(" \n");
+ LastChunkEndsWithNewline = true;
+ return;
+ }
+
+ commandToMarkup(Out, C->getCommandName(Traits), C->getCommandMarker(),
+ "");
+ }
+ }
+
+ void visitHTMLStartTagComment(const comments::HTMLStartTagComment *STC) {
+ std::string TagText = "<" + STC->getTagName().str();
+
+ for (unsigned I = 0; I < STC->getNumAttrs(); ++I) {
+ const comments::HTMLStartTagComment::Attribute &Attr = STC->getAttr(I);
+ TagText += " " + Attr.Name.str() + "=\"" + Attr.Value.str() + "\"";
+ }
+
+ if (STC->isSelfClosing())
+ TagText += " /";
+ TagText += ">";
+
+ LastChunkEndsWithNewline = STC->hasTrailingNewline();
+ Out.appendText(TagText + (LastChunkEndsWithNewline ? "\n" : ""));
+ }
+
+ void visitHTMLEndTagComment(const comments::HTMLEndTagComment *ETC) {
+ LastChunkEndsWithNewline = ETC->hasTrailingNewline();
+ Out.appendText("</" + ETC->getTagName().str() + ">" +
+ (LastChunkEndsWithNewline ? "\n" : ""));
+ }
+
+private:
+ markup::Paragraph &Out;
+ const comments::CommandTraits &Traits;
+
+ /// If true, the next leading space after a new line is trimmed.
+ /// Initially set it to true, to always trim the first text line.
+ bool LastChunkEndsWithNewline = true;
+};
+
+class ParagraphToString
+ : public comments::ConstCommentVisitor<ParagraphToString> {
+public:
+ ParagraphToString(llvm::raw_string_ostream &Out,
+ const comments::CommandTraits &Traits)
+ : Out(Out), Traits(Traits) {}
+
+ void visitParagraphComment(const comments::ParagraphComment *C) {
+ if (!C)
+ return;
+
+ for (const auto *Child = C->child_begin(); Child != C->child_end();
+ ++Child) {
+ visit(*Child);
+ }
+ }
+
+ void visitTextComment(const comments::TextComment *C) { Out << C->getText(); }
+
+ void visitInlineCommandComment(const comments::InlineCommandComment *C) {
+ Out << commandMarkerAsString(C->getCommandMarker());
+ Out << C->getCommandName(Traits);
+ if (C->getNumArgs() > 0) {
+ for (unsigned I = 0; I < C->getNumArgs(); ++I)
+ Out << " " << C->getArgText(I);
+ }
+ Out << " ";
+ }
+
+ void visitHTMLStartTagComment(const comments::HTMLStartTagComment *STC) {
+ Out << "<" << STC->getTagName().str();
+
+ for (unsigned I = 0; I < STC->getNumAttrs(); ++I) {
+ const comments::HTMLStartTagComment::Attribute &Attr = STC->getAttr(I);
+ Out << " " << Attr.Name.str();
+ if (!Attr.Value.str().empty())
+ Out << "=\"" << Attr.Value.str() << "\"";
+ }
+
+ if (STC->isSelfClosing())
+ Out << " /";
+ Out << ">";
+
+ Out << (STC->hasTrailingNewline() ? "\n" : "");
+ }
+
+ void visitHTMLEndTagComment(const comments::HTMLEndTagComment *ETC) {
+ Out << "</" << ETC->getTagName().str() << ">"
+ << (ETC->hasTrailingNewline() ? "\n" : "");
+ }
+
+private:
+ llvm::raw_string_ostream &Out;
+ const comments::CommandTraits &Traits;
+};
+
+class BlockCommentToMarkupDocument
+ : public comments::ConstCommentVisitor<BlockCommentToMarkupDocument> {
+public:
+ BlockCommentToMarkupDocument(markup::Document &Out,
+ const comments::CommandTraits &Traits)
+ : Out(Out), Traits(Traits) {}
+
+ void visitBlockCommandComment(const comments::BlockCommandComment *B) {
+
+ switch (B->getCommandID()) {
+ case comments::CommandTraits::KCI_arg:
+ case comments::CommandTraits::KCI_li:
+ // \li and \arg are special cases, they are used to create a list item.
+ // In markdown it is a bullet list.
+ ParagraphToMarkupDocument(Out.addBulletList().addItem().addParagraph(),
+ Traits)
+ .visit(B->getParagraph());
+ break;
+ default: {
+ // Some commands have arguments, like \throws.
+ // The arguments are not part of the paragraph.
+ // We need reconstruct them here.
+ std::string ArgText;
+ for (unsigned I = 0; I < B->getNumArgs(); ++I) {
+ if (!ArgText.empty())
+ ArgText += " ";
+ ArgText += B->getArgText(I);
+ }
+ auto &P = Out.addParagraph();
+ commandToMarkup(P, B->getCommandName(Traits), B->getCommandMarker(),
+ ArgText);
+ if (B->getParagraph() && !B->getParagraph()->isWhitespace()) {
+ // For commands with arguments, the paragraph starts after the first
+ // space. Therefore we need to append a space manually in this case.
+ if (!ArgText.empty())
+ P.appendSpace();
+ ParagraphToMarkupDocument(P, Traits).visit(B->getParagraph());
+ }
+ }
+ }
+ }
+
+ void visitVerbatimBlockComment(const comments::VerbatimBlockComment *VB) {
+ commandToMarkup(Out.addParagraph(), VB->getCommandName(Traits),
+ VB->getCommandMarker(), "");
+
+ std::string VerbatimText;
+
+ for (const auto *LI = VB->child_begin(); LI != VB->child_end(); ++LI) {
+ if (const auto *Line = cast<comments::VerbatimBlockLineComment>(*LI)) {
+ VerbatimText += Line->getText().str() + "\n";
+ }
+ }
+
+ Out.addCodeBlock(VerbatimText, "");
+
+ commandToMarkup(Out.addParagraph(), VB->getCloseName(),
+ VB->getCommandMarker(), "");
+ }
+
+ void visitVerbatimLineComment(const comments::VerbatimLineComment *VL) {
+ auto &P = Out.addParagraph();
+ commandToMarkup(P, VL->getCommandName(Traits), VL->getCommandMarker(), "");
+ P.appendSpace().appendCode(VL->getText().str(), true).appendSpace();
+ }
+
+private:
+ markup::Document &Out;
+ const comments::CommandTraits &Traits;
+ StringRef CommentEscapeMarker;
+};
+
+void SymbolDocCommentVisitor::visitBlockCommandComment(
+ const comments::BlockCommandComment *B) {
+ switch (B->getCommandID()) {
+ case comments::CommandTraits::KCI_brief: {
+ if (!BriefParagraph) {
+ BriefParagraph = B->getParagraph();
+ return;
+ }
+ break;
+ }
+ case comments::CommandTraits::KCI_return:
+ case comments::CommandTraits::KCI_returns:
+ if (!ReturnParagraph) {
+ ReturnParagraph = B->getParagraph();
+ return;
+ }
+ break;
+ case comments::CommandTraits::KCI_retval:
+ RetvalParagraphs.push_back(B->getParagraph());
+ return;
+ case comments::CommandTraits::KCI_warning:
+ WarningParagraphs.push_back(B->getParagraph());
+ return;
+ case comments::CommandTraits::KCI_note:
+ NoteParagraphs.push_back(B->getParagraph());
+ return;
+ default:
+ break;
+ }
+
+ // For all other commands, we store them in the UnhandledCommands map.
+ // This allows us to keep the order of the comments.
+ UnhandledCommands[CommentPartIndex] = B;
+ CommentPartIndex++;
+}
+
+void SymbolDocCommentVisitor::paragraphsToMarkup(
+ markup::Document &Out,
+ const llvm::SmallVectorImpl<const comments::ParagraphComment *> &Paragraphs)
+ const {
+ if (Paragraphs.empty())
+ return;
+
+ for (const auto *P : Paragraphs) {
+ ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P);
+ }
+}
+
+void SymbolDocCommentVisitor::briefToMarkup(markup::Paragraph &Out) const {
+ if (!BriefParagraph)
+ return;
+ ParagraphToMarkupDocument(Out, Traits).visit(BriefParagraph);
+}
+
+void SymbolDocCommentVisitor::returnToMarkup(markup::Paragraph &Out) const {
+ if (!ReturnParagraph)
+ return;
+ ParagraphToMarkupDocument(Out, Traits).visit(ReturnParagraph);
+}
+
+void SymbolDocCommentVisitor::notesToMarkup(markup::Document &Out) const {
+ paragraphsToMarkup(Out, NoteParagraphs);
+}
+
+void SymbolDocCommentVisitor::warningsToMarkup(markup::Document &Out) const {
+ paragraphsToMarkup(Out, WarningParagraphs);
+}
+
+void SymbolDocCommentVisitor::parameterDocToMarkup(
+ StringRef ParamName, markup::Paragraph &Out) const {
+ if (ParamName.empty())
+ return;
+
+ if (const auto *P = Parameters.lookup(ParamName)) {
+ ParagraphToMarkupDocument(Out, Traits).visit(P->getParagraph());
+ }
+}
+
+void SymbolDocCommentVisitor::parameterDocToString(
+ StringRef ParamName, llvm::raw_string_ostream &Out) const {
+ if (ParamName.empty())
+ return;
+
+ if (const auto *P = Parameters.lookup(ParamName)) {
+ ParagraphToString(Out, Traits).visit(P->getParagraph());
+ }
+}
+
+void SymbolDocCommentVisitor::docToMarkup(markup::Document &Out) const {
+ for (unsigned I = 0; I < CommentPartIndex; ++I) {
+ if (const auto *BC = UnhandledCommands.lookup(I)) {
+ BlockCommentToMarkupDocument(Out, Traits).visit(BC);
+ } else if (const auto *P = FreeParagraphs.lookup(I)) {
+ ParagraphToMarkupDocument(Out.addParagraph(), Traits).visit(P);
+ }
+ }
+}
+
+void SymbolDocCommentVisitor::templateTypeParmDocToMarkup(
+ StringRef TemplateParamName, markup::Paragraph &Out) const {
+ if (TemplateParamName.empty())
+ return;
+
+ if (const auto *TP = TemplateParameters.lookup(TemplateParamName)) {
+ ParagraphToMarkupDocument(Out, Traits).visit(TP->getParagraph());
+ }
+}
+
+void SymbolDocCommentVisitor::templateTypeParmDocToString(
+ StringRef TemplateParamName, llvm::raw_string_ostream &Out) const {
+ if (TemplateParamName.empty())
+ return;
+
+ if (const auto *P = TemplateParameters.lookup(TemplateParamName)) {
+ ParagraphToString(Out, Traits).visit(P->getParagraph());
+ }
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/SymbolDocumentation.h b/clang-tools-extra/clangd/SymbolDocumentation.h
new file mode 100644
index 0000000..b0d3428
--- /dev/null
+++ b/clang-tools-extra/clangd/SymbolDocumentation.h
@@ -0,0 +1,211 @@
+//===--- SymbolDocumentation.h ==---------------------------------*- C++-*-===//
+//
+// Part of the LLVM Project, 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Class to parse doxygen comments into a flat structure for consumption
+// in e.g. Hover and Code Completion
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
+
+#include "support/Markup.h"
+#include "clang/AST/Comment.h"
+#include "clang/AST/CommentLexer.h"
+#include "clang/AST/CommentParser.h"
+#include "clang/AST/CommentSema.h"
+#include "clang/AST/CommentVisitor.h"
+#include "clang/Basic/SourceManager.h"
+#include "llvm/Support/raw_ostream.h"
+#include <string>
+
+namespace clang {
+namespace clangd {
+
+class SymbolDocCommentVisitor
+ : public comments::ConstCommentVisitor<SymbolDocCommentVisitor> {
+public:
+ SymbolDocCommentVisitor(comments::FullComment *FC,
+ const CommentOptions &CommentOpts)
+ : Traits(Allocator, CommentOpts), Allocator() {
+ if (!FC)
+ return;
+
+ for (auto *Block : FC->getBlocks()) {
+ visit(Block);
+ }
+ }
+
+ SymbolDocCommentVisitor(llvm::StringRef Documentation,
+ const CommentOptions &CommentOpts)
+ : Traits(Allocator, CommentOpts), Allocator() {
+
+ if (Documentation.empty())
+ return;
+
+ CommentWithMarkers.reserve(Documentation.size() +
+ Documentation.count('\n') * 3);
+
+ // The comment lexer expects doxygen markers, so add them back.
+ // We need to use the /// style doxygen markers because the comment could
+ // contain the closing the closing tag "*/" of a C Style "/** */" comment
+ // which would break the parsing if we would just enclose the comment text
+ // with "/** */".
+ CommentWithMarkers = "///";
+ bool NewLine = true;
+ for (char C : Documentation) {
+ if (C == '\n') {
+ CommentWithMarkers += "\n///";
+ NewLine = true;
+ } else {
+ if (NewLine && (C == '<')) {
+ // A comment line starting with '///<' is treated as a doxygen
+ // comment. Therefore add a space to separate the '<' from the comment
+ // marker. This allows to parse html tags at the beginning of a line
+ // and the escape marker prevents adding the artificial space in the
+ // markup documentation. The extra space will not be rendered, since
+ // we render it as markdown.
+ CommentWithMarkers += ' ';
+ }
+ CommentWithMarkers += C;
+ NewLine = false;
+ }
+ }
+ SourceManagerForFile SourceMgrForFile("mock_file.cpp", CommentWithMarkers);
+
+ SourceManager &SourceMgr = SourceMgrForFile.get();
+ // The doxygen Sema requires a Diagostics consumer, since it reports
+ // warnings e.g. when parameters are not documented correctly. These
+ // warnings are not relevant for us, so we can ignore them.
+ SourceMgr.getDiagnostics().setClient(new IgnoringDiagConsumer);
+
+ comments::Sema S(Allocator, SourceMgr, SourceMgr.getDiagnostics(), Traits,
+ /*PP=*/nullptr);
+ comments::Lexer L(Allocator, SourceMgr.getDiagnostics(), Traits,
+ SourceMgr.getLocForStartOfFile(SourceMgr.getMainFileID()),
+ CommentWithMarkers.data(),
+ CommentWithMarkers.data() + CommentWithMarkers.size());
+ comments::Parser P(L, S, Allocator, SourceMgr, SourceMgr.getDiagnostics(),
+ Traits);
+ comments::FullComment *FC = P.parseFullComment();
+
+ if (!FC)
+ return;
+
+ for (auto *Block : FC->getBlocks()) {
+ visit(Block);
+ }
+ }
+
+ bool isParameterDocumented(StringRef ParamName) const {
+ return Parameters.contains(ParamName);
+ }
+
+ bool isTemplateTypeParmDocumented(StringRef ParamName) const {
+ return TemplateParameters.contains(ParamName);
+ }
+
+ bool hasBriefCommand() const { return BriefParagraph; }
+
+ bool hasReturnCommand() const { return ReturnParagraph; }
+
+ bool hasRetvalCommands() const { return !RetvalParagraphs.empty(); }
+
+ bool hasNoteCommands() const { return !NoteParagraphs.empty(); }
+
+ bool hasWarningCommands() const { return !WarningParagraphs.empty(); }
+
+ /// Converts all unhandled comment commands to a markup document.
+ void docToMarkup(markup::Document &Out) const;
+ /// Converts the "brief" command(s) to a markup document.
+ void briefToMarkup(markup::Paragraph &Out) const;
+ /// Converts the "return" command(s) to a markup document.
+ void returnToMarkup(markup::Paragraph &Out) const;
+ /// Converts the "note" command(s) to a markup document.
+ void notesToMarkup(markup::Document &Out) const;
+ /// Converts the "warning" command(s) to a markup document.
+ void warningsToMarkup(markup::Document &Out) const;
+
+ void visitBlockCommandComment(const comments::BlockCommandComment *B);
+
+ void templateTypeParmDocToMarkup(StringRef TemplateParamName,
+ markup::Paragraph &Out) const;
+
+ void templateTypeParmDocToString(StringRef TemplateParamName,
+ llvm::raw_string_ostream &Out) const;
+
+ void parameterDocToMarkup(StringRef ParamName, markup::Paragraph &Out) const;
+
+ void parameterDocToString(StringRef ParamName,
+ llvm::raw_string_ostream &Out) const;
+
+ void visitParagraphComment(const comments::ParagraphComment *P) {
+ FreeParagraphs[CommentPartIndex] = P;
+ CommentPartIndex++;
+ }
+
+ void visitParamCommandComment(const comments::ParamCommandComment *P) {
+ Parameters[P->getParamNameAsWritten()] = P;
+ }
+
+ void visitTParamCommandComment(const comments::TParamCommandComment *TP) {
+ TemplateParameters[TP->getParamNameAsWritten()] = std::move(TP);
+ }
+
+private:
+ comments::CommandTraits Traits;
+ llvm::BumpPtrAllocator Allocator;
+ std::string CommentWithMarkers;
+
+ /// Index to keep track of the order of the comments.
+ /// We want to rearange some commands like \\param.
+ /// This index allows us to keep the order of the other comment parts.
+ unsigned CommentPartIndex = 0;
+
+ /// Paragraph of the "brief" command.
+ const comments::ParagraphComment *BriefParagraph = nullptr;
+
+ /// Paragraph of the "return" command.
+ const comments::ParagraphComment *ReturnParagraph = nullptr;
+
+ /// Paragraph(s) of the "note" command(s)
+ llvm::SmallVector<const comments::ParagraphComment *> RetvalParagraphs;
+
+ /// Paragraph(s) of the "note" command(s)
+ llvm::SmallVector<const comments::ParagraphComment *> NoteParagraphs;
+
+ /// Paragraph(s) of the "warning" command(s)
+ llvm::SmallVector<const comments::ParagraphComment *> WarningParagraphs;
+
+ /// All the paragraphs we don't have any special handling for,
+ /// e.g. "details".
+ llvm::SmallDenseMap<unsigned, const comments::BlockCommandComment *>
+ UnhandledCommands;
+
+ /// Parsed paragaph(s) of the "param" comamnd(s)
+ llvm::SmallDenseMap<StringRef, const comments::ParamCommandComment *>
+ Parameters;
+
+ /// Parsed paragaph(s) of the "tparam" comamnd(s)
+ llvm::SmallDenseMap<StringRef, const comments::TParamCommandComment *>
+ TemplateParameters;
+
+ /// All "free" text paragraphs.
+ llvm::SmallDenseMap<unsigned, const comments::ParagraphComment *>
+ FreeParagraphs;
+
+ void paragraphsToMarkup(
+ markup::Document &Out,
+ const llvm::SmallVectorImpl<const comments::ParagraphComment *>
+ &Paragraphs) const;
+};
+
+} // namespace clangd
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANGD_SYMBOLDOCUMENTATION_H
diff --git a/clang-tools-extra/clangd/XRefs.cpp b/clang-tools-extra/clangd/XRefs.cpp
index 83a8b72..e1c50f9 100644
--- a/clang-tools-extra/clangd/XRefs.cpp
+++ b/clang-tools-extra/clangd/XRefs.cpp
@@ -1106,11 +1106,11 @@ public:
return true;
}
bool VisitBreakStmt(BreakStmt *B) {
- found(Break, B->getBreakLoc());
+ found(Break, B->getKwLoc());
return true;
}
bool VisitContinueStmt(ContinueStmt *C) {
- found(Continue, C->getContinueLoc());
+ found(Continue, C->getKwLoc());
return true;
}
bool VisitSwitchCase(SwitchCase *C) {
@@ -1876,7 +1876,7 @@ static void fillSubTypes(const SymbolID &ID,
});
}
-using RecursionProtectionSet = llvm::SmallSet<const CXXRecordDecl *, 4>;
+using RecursionProtectionSet = llvm::SmallPtrSet<const CXXRecordDecl *, 4>;
// Extracts parents from AST and populates the type hierarchy item.
static void fillSuperTypes(const CXXRecordDecl &CXXRD, llvm::StringRef TUPath,
@@ -1965,7 +1965,8 @@ std::vector<const CXXRecordDecl *> findRecordTypeAt(ParsedAST &AST,
// Return the type most associated with an AST node.
// This isn't precisely defined: we want "go to type" to do something useful.
-static QualType typeForNode(const SelectionTree::Node *N) {
+static QualType typeForNode(const ASTContext &Ctx,
+ const SelectionTree::Node *N) {
// If we're looking at a namespace qualifier, walk up to what it's qualifying.
// (If we're pointing at a *class* inside a NNS, N will be a TypeLoc).
while (N && N->ASTNode.get<NestedNameSpecifierLoc>())
@@ -1999,10 +2000,13 @@ static QualType typeForNode(const SelectionTree::Node *N) {
if (const Decl *D = N->ASTNode.get<Decl>()) {
struct Visitor : ConstDeclVisitor<Visitor, QualType> {
+ const ASTContext &Ctx;
+ Visitor(const ASTContext &Ctx) : Ctx(Ctx) {}
+
QualType VisitValueDecl(const ValueDecl *D) { return D->getType(); }
// Declaration of a type => that type.
QualType VisitTypeDecl(const TypeDecl *D) {
- return QualType(D->getTypeForDecl(), 0);
+ return Ctx.getTypeDeclType(D);
}
// Exception: alias declaration => the underlying type, not the alias.
QualType VisitTypedefNameDecl(const TypedefNameDecl *D) {
@@ -2012,7 +2016,7 @@ static QualType typeForNode(const SelectionTree::Node *N) {
QualType VisitTemplateDecl(const TemplateDecl *D) {
return Visit(D->getTemplatedDecl());
}
- } V;
+ } V(Ctx);
return V.Visit(D);
}
@@ -2156,7 +2160,8 @@ std::vector<LocatedSymbol> findType(ParsedAST &AST, Position Pos,
// unique_ptr<unique_ptr<T>>. Let's *not* remove them, because it gives you some
// information about the type you may have not known before
// (since unique_ptr<unique_ptr<T>> != unique_ptr<T>).
- for (const QualType& Type : unwrapFindType(typeForNode(N), AST.getHeuristicResolver()))
+ for (const QualType &Type : unwrapFindType(
+ typeForNode(AST.getASTContext(), N), AST.getHeuristicResolver()))
llvm::copy(locateSymbolForType(AST, Type, Index),
std::back_inserter(LocatedSymbols));
diff --git a/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp b/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp
index 67fc451..f65c74f 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/AddUsing.cpp
@@ -115,13 +115,6 @@ private:
const SourceManager &SM;
};
-bool isFullyQualified(const NestedNameSpecifier *NNS) {
- if (!NNS)
- return false;
- return NNS->getKind() == NestedNameSpecifier::Global ||
- isFullyQualified(NNS->getPrefix());
-}
-
struct InsertionPointData {
// Location to insert the "using" statement. If invalid then the statement
// should not be inserted at all (it already exists).
@@ -167,18 +160,20 @@ findInsertionPoint(const Tweak::Selection &Inputs,
for (auto &U : Usings) {
// Only "upgrade" to fully qualified is all relevant using decls are fully
// qualified. Otherwise trust what the user typed.
- if (!isFullyQualified(U->getQualifier()))
+ if (!U->getQualifier().isFullyQualified())
AlwaysFullyQualify = false;
if (SM.isBeforeInTranslationUnit(Inputs.Cursor, U->getUsingLoc()))
// "Usings" is sorted, so we're done.
break;
- if (const auto *Namespace = dyn_cast_if_present<NamespaceDecl>(
- U->getQualifier()->getAsNamespace())) {
+ if (NestedNameSpecifier Qualifier = U->getQualifier();
+ Qualifier.getKind() == NestedNameSpecifier::Kind::Namespace) {
+ const auto *Namespace =
+ U->getQualifier().getAsNamespaceAndPrefix().Namespace;
if (Namespace->getCanonicalDecl() ==
QualifierToRemove.getNestedNameSpecifier()
- ->getAsNamespace()
- ->getCanonicalDecl() &&
+ .getAsNamespaceAndPrefix()
+ .Namespace->getCanonicalDecl() &&
U->getName() == Name) {
return InsertionPointData();
}
@@ -232,8 +227,9 @@ findInsertionPoint(const Tweak::Selection &Inputs,
}
bool isNamespaceForbidden(const Tweak::Selection &Inputs,
- const NestedNameSpecifier &Namespace) {
- const auto *NS = dyn_cast<NamespaceDecl>(Namespace.getAsNamespace());
+ NestedNameSpecifier Namespace) {
+ const auto *NS =
+ dyn_cast<NamespaceDecl>(Namespace.getAsNamespaceAndPrefix().Namespace);
if (!NS)
return true;
std::string NamespaceStr = printNamespaceScope(*NS);
@@ -247,11 +243,11 @@ bool isNamespaceForbidden(const Tweak::Selection &Inputs,
return false;
}
-std::string getNNSLAsString(NestedNameSpecifierLoc &NNSL,
+std::string getNNSLAsString(NestedNameSpecifierLoc NNSL,
const PrintingPolicy &Policy) {
std::string Out;
llvm::raw_string_ostream OutStream(Out);
- NNSL.getNestedNameSpecifier()->print(OutStream, Policy);
+ NNSL.getNestedNameSpecifier().print(OutStream, Policy);
return OutStream.str();
}
@@ -276,16 +272,15 @@ bool AddUsing::prepare(const Selection &Inputs) {
continue;
}
if (auto *T = Node->ASTNode.get<TypeLoc>()) {
- if (T->getAs<ElaboratedTypeLoc>()) {
+ // Find the outermost TypeLoc.
+ if (Node->Parent->ASTNode.get<NestedNameSpecifierLoc>())
+ continue;
+ if (isa<TagType, TemplateSpecializationType, TypedefType, UsingType,
+ UnresolvedUsingType>(T->getTypePtr()))
break;
- }
- if (Node->Parent->ASTNode.get<TypeLoc>() ||
- Node->Parent->ASTNode.get<NestedNameSpecifierLoc>()) {
- // Node is TypeLoc, but it's parent is either TypeLoc or
- // NestedNameSpecifier. In both cases, we want to go up, to find
- // the outermost TypeLoc.
+ // Find the outermost TypeLoc.
+ if (Node->Parent->ASTNode.get<TypeLoc>())
continue;
- }
}
break;
}
@@ -307,32 +302,70 @@ bool AddUsing::prepare(const Selection &Inputs) {
MustInsertAfterLoc = D->getDecl()->getBeginLoc();
}
} else if (auto *T = Node->ASTNode.get<TypeLoc>()) {
- if (auto E = T->getAs<ElaboratedTypeLoc>()) {
- QualifierToRemove = E.getQualifierLoc();
-
- SpelledNameRange = E.getSourceRange();
- if (auto T = E.getNamedTypeLoc().getAs<TemplateSpecializationTypeLoc>()) {
- // Remove the template arguments from the name.
- SpelledNameRange.setEnd(T.getLAngleLoc().getLocWithOffset(-1));
- }
-
- if (const auto *ET = E.getTypePtr()) {
- if (const auto *TDT =
- dyn_cast<TypedefType>(ET->getNamedType().getTypePtr())) {
- MustInsertAfterLoc = TDT->getDecl()->getBeginLoc();
- } else if (auto *TD = ET->getAsTagDecl()) {
- MustInsertAfterLoc = TD->getBeginLoc();
- }
- }
+ switch (T->getTypeLocClass()) {
+ case TypeLoc::TemplateSpecialization: {
+ auto TL = T->castAs<TemplateSpecializationTypeLoc>();
+ QualifierToRemove = TL.getQualifierLoc();
+ if (!QualifierToRemove)
+ break;
+ SpelledNameRange = TL.getTemplateNameLoc();
+ if (auto *TD = TL.getTypePtr()->getTemplateName().getAsTemplateDecl(
+ /*IgnoreDeduced=*/true))
+ MustInsertAfterLoc = TD->getBeginLoc();
+ break;
+ }
+ case TypeLoc::Enum:
+ case TypeLoc::Record:
+ case TypeLoc::InjectedClassName: {
+ auto TL = T->castAs<TagTypeLoc>();
+ QualifierToRemove = TL.getQualifierLoc();
+ if (!QualifierToRemove)
+ break;
+ SpelledNameRange = TL.getNameLoc();
+ MustInsertAfterLoc = TL.getOriginalDecl()->getBeginLoc();
+ break;
+ }
+ case TypeLoc::Typedef: {
+ auto TL = T->castAs<TypedefTypeLoc>();
+ QualifierToRemove = TL.getQualifierLoc();
+ if (!QualifierToRemove)
+ break;
+ SpelledNameRange = TL.getNameLoc();
+ MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
+ break;
+ }
+ case TypeLoc::UnresolvedUsing: {
+ auto TL = T->castAs<UnresolvedUsingTypeLoc>();
+ QualifierToRemove = TL.getQualifierLoc();
+ if (!QualifierToRemove)
+ break;
+ SpelledNameRange = TL.getNameLoc();
+ MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
+ break;
+ }
+ case TypeLoc::Using: {
+ auto TL = T->castAs<UsingTypeLoc>();
+ QualifierToRemove = TL.getQualifierLoc();
+ if (!QualifierToRemove)
+ break;
+ SpelledNameRange = TL.getNameLoc();
+ MustInsertAfterLoc = TL.getDecl()->getBeginLoc();
+ break;
+ }
+ default:
+ break;
}
+ if (QualifierToRemove)
+ SpelledNameRange.setBegin(QualifierToRemove.getBeginLoc());
}
if (!QualifierToRemove ||
// FIXME: This only supports removing qualifiers that are made up of just
// namespace names. If qualifier contains a type, we could take the
// longest namespace prefix and remove that.
- !QualifierToRemove.getNestedNameSpecifier()->getAsNamespace() ||
+ QualifierToRemove.getNestedNameSpecifier().getKind() !=
+ NestedNameSpecifier::Kind::Namespace ||
// Respect user config.
- isNamespaceForbidden(Inputs, *QualifierToRemove.getNestedNameSpecifier()))
+ isNamespaceForbidden(Inputs, QualifierToRemove.getNestedNameSpecifier()))
return false;
// Macros are difficult. We only want to offer code action when what's spelled
// under the cursor is a namespace qualifier. If it's a macro that expands to
@@ -384,7 +417,7 @@ Expected<Tweak::Effect> AddUsing::apply(const Selection &Inputs) {
llvm::raw_string_ostream UsingTextStream(UsingText);
UsingTextStream << "using ";
if (InsertionPoint->AlwaysFullyQualify &&
- !isFullyQualified(QualifierToRemove.getNestedNameSpecifier()))
+ !QualifierToRemove.getNestedNameSpecifier().isFullyQualified())
UsingTextStream << "::";
UsingTextStream << QualifierToSpell << SpelledName << ";"
<< InsertionPoint->Suffix;
diff --git a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
index cd07cbf..bc9a790 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/ExtractFunction.cpp
@@ -181,7 +181,7 @@ struct ExtractionZone {
bool requiresHoisting(const SourceManager &SM,
const HeuristicResolver *Resolver) const {
// First find all the declarations that happened inside extraction zone.
- llvm::SmallSet<const Decl *, 1> DeclsInExtZone;
+ llvm::SmallPtrSet<const Decl *, 1> DeclsInExtZone;
for (auto *RootStmt : RootStmts) {
findExplicitReferences(
RootStmt,
@@ -362,7 +362,7 @@ struct NewFunction {
SourceLocation DefinitionPoint;
std::optional<SourceLocation> ForwardDeclarationPoint;
const CXXRecordDecl *EnclosingClass = nullptr;
- const NestedNameSpecifier *DefinitionQualifier = nullptr;
+ NestedNameSpecifier DefinitionQualifier = std::nullopt;
const DeclContext *SemanticDC = nullptr;
const DeclContext *SyntacticDC = nullptr;
const DeclContext *ForwardDeclarationSyntacticDC = nullptr;
@@ -455,13 +455,12 @@ std::string NewFunction::renderQualifiers() const {
}
std::string NewFunction::renderDeclarationName(FunctionDeclKind K) const {
- if (DefinitionQualifier == nullptr || K != OutOfLineDefinition) {
+ if (!DefinitionQualifier || K != OutOfLineDefinition)
return Name;
- }
std::string QualifierName;
llvm::raw_string_ostream Oss(QualifierName);
- DefinitionQualifier->print(Oss, *LangOpts);
+ DefinitionQualifier.print(Oss, *LangOpts);
return llvm::formatv("{0}{1}", QualifierName, Name);
}
diff --git a/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp b/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp
index 43cfc76..2c98417 100644
--- a/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp
+++ b/clang-tools-extra/clangd/refactor/tweaks/PopulateSwitch.cpp
@@ -113,11 +113,11 @@ bool PopulateSwitch::prepare(const Selection &Sel) {
// Ignore implicit casts, since enums implicitly cast to integer types.
Cond = Cond->IgnoreParenImpCasts();
// Get the canonical type to handle typedefs.
- EnumT = Cond->getType().getCanonicalType()->getAsAdjusted<EnumType>();
+ EnumT = Cond->getType()->getAsCanonical<EnumType>();
if (!EnumT)
return false;
- EnumD = EnumT->getDecl();
- if (!EnumD || EnumD->isDependentType())
+ EnumD = EnumT->getOriginalDecl()->getDefinitionOrSelf();
+ if (EnumD->isDependentType())
return false;
// Finally, check which cases exist and which are covered.
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.cpp b/clang-tools-extra/clangd/support/DirectiveTree.cpp
index 7ea08ad..97b0598 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.cpp
+++ b/clang-tools-extra/clangd/support/DirectiveTree.cpp
@@ -356,5 +356,62 @@ TokenStream DirectiveTree::stripDirectives(const TokenStream &In) const {
return Out;
}
+namespace {
+class RangePairer {
+ std::vector<Token::Range> &Ranges;
+
+public:
+ RangePairer(std::vector<Token::Range> &Ranges) : Ranges(Ranges) {}
+
+ void walk(const DirectiveTree &T) {
+ for (const auto &C : T.Chunks)
+ std::visit(*this, C);
+ }
+
+ void operator()(const DirectiveTree::Code &C) {}
+
+ void operator()(const DirectiveTree::Directive &) {}
+
+ void operator()(const DirectiveTree::Conditional &C) {
+ Token::Range Range;
+ Token::Index Last;
+ auto First = true;
+ for (const auto &[Directive, _] : C.Branches) {
+ if (First) {
+ First = false;
+ } else {
+ Range = {Last, Directive.Tokens.Begin};
+ Ranges.push_back(Range);
+ }
+ Last = Directive.Tokens.Begin;
+ }
+
+ if (C.End.Kind != tok::pp_not_keyword) {
+ Range = {Last, C.End.Tokens.Begin};
+ Ranges.push_back(Range);
+ }
+
+ for (const auto &[_, SubTree] : C.Branches)
+ walk(SubTree);
+ }
+};
+} // namespace
+
+std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree,
+ const TokenStream &Code) {
+ std::vector<Token::Range> Ranges;
+ RangePairer(Ranges).walk(Tree);
+
+ // Transform paired ranges to start with last token in its logical line
+ for (auto &R : Ranges) {
+ const Token *Tok = &Code.tokens()[R.Begin + 1];
+ while (Tok->Kind != tok::eof && !Tok->flag(LexFlags::StartsPPLine))
+ ++Tok;
+ Tok = Tok - 1;
+ R.Begin = Tok->OriginalIndex;
+ }
+ return Ranges;
+}
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/support/DirectiveTree.h b/clang-tools-extra/clangd/support/DirectiveTree.h
index 34f5a88..373af32 100644
--- a/clang-tools-extra/clangd/support/DirectiveTree.h
+++ b/clang-tools-extra/clangd/support/DirectiveTree.h
@@ -124,6 +124,10 @@ llvm::raw_ostream &operator<<(llvm::raw_ostream &,
/// The choices are stored in Conditional::Taken nodes.
void chooseConditionalBranches(DirectiveTree &, const TokenStream &Code);
+/// Pairs preprocessor conditional directives and computes their token ranges.
+std::vector<Token::Range> pairDirectiveRanges(const DirectiveTree &Tree,
+ const TokenStream &Code);
+
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/support/Markup.cpp b/clang-tools-extra/clangd/support/Markup.cpp
index a130830..89bdc65 100644
--- a/clang-tools-extra/clangd/support/Markup.cpp
+++ b/clang-tools-extra/clangd/support/Markup.cpp
@@ -363,7 +363,12 @@ public:
void renderMarkdown(llvm::raw_ostream &OS) const override {
std::string Marker = getMarkerForCodeBlock(Contents);
// No need to pad from previous blocks, as they should end with a new line.
- OS << Marker << Language << '\n' << Contents << '\n' << Marker << '\n';
+ OS << Marker << Language << '\n' << Contents;
+ if (!Contents.empty() && Contents.back() != '\n')
+ OS << '\n';
+ // Always end with an empty line to separate code blocks from following
+ // paragraphs.
+ OS << Marker << "\n\n";
}
void renderPlainText(llvm::raw_ostream &OS) const override {
diff --git a/clang-tools-extra/clangd/test/modules_no_cdb.test b/clang-tools-extra/clangd/test/modules_no_cdb.test
new file mode 100644
index 0000000..8f92be2
--- /dev/null
+++ b/clang-tools-extra/clangd/test/modules_no_cdb.test
@@ -0,0 +1,66 @@
+# A smoke test to check that clangd works without compilation database
+#
+# Windows have different escaping modes.
+# FIXME: We should add one for windows.
+# UNSUPPORTED: system-windows
+#
+# RUN: rm -fr %t
+# RUN: mkdir -p %t
+# RUN: split-file %s %t
+#
+# RUN: sed -e "s|DIR|%/t|g" %t/definition.jsonrpc.tmpl > %t/definition.jsonrpc
+#
+# RUN: clangd -experimental-modules-support -lit-test < %t/definition.jsonrpc \
+# RUN: | FileCheck -strict-whitespace %t/definition.jsonrpc
+
+#--- A.h
+void printA();
+
+#--- Use.cpp
+#include "A.h"
+void foo() {
+ print
+}
+
+#--- definition.jsonrpc.tmpl
+{
+ "jsonrpc": "2.0",
+ "id": 0,
+ "method": "initialize",
+ "params": {
+ "processId": 123,
+ "rootPath": "clangd",
+ "capabilities": {
+ "textDocument": {
+ "completion": {
+ "completionItem": {
+ "snippetSupport": true
+ }
+ }
+ }
+ },
+ "trace": "off"
+ }
+}
+---
+{
+ "jsonrpc": "2.0",
+ "method": "textDocument/didOpen",
+ "params": {
+ "textDocument": {
+ "uri": "file://DIR/Use.cpp",
+ "languageId": "cpp",
+ "version": 1,
+ "text": "#include \"A.h\"\nvoid foo() {\n print\n}\n"
+ }
+ }
+}
+
+# CHECK: "message"{{.*}}printA{{.*}}(fix available)
+
+---
+{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file://DIR/Use.cpp"},"context":{"triggerKind":1},"position":{"line":2,"character":6}}}
+---
+{"jsonrpc":"2.0","id":2,"method":"shutdown"}
+---
+{"jsonrpc":"2.0","method":"exit"}
diff --git a/clang-tools-extra/clangd/unittests/ASTTests.cpp b/clang-tools-extra/clangd/unittests/ASTTests.cpp
index d0bc3c4..76d46ba 100644
--- a/clang-tools-extra/clangd/unittests/ASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ASTTests.cpp
@@ -421,7 +421,7 @@ TEST(ClangdAST, GetQualification) {
{
R"cpp(
namespace ns1 { namespace ns2 { void Foo(); } }
- void insert(); // ns2::Foo
+ void insert(); // ns1::ns2::Foo
namespace ns1 {
void insert(); // ns2::Foo
namespace ns2 {
@@ -429,7 +429,7 @@ TEST(ClangdAST, GetQualification) {
}
}
)cpp",
- {"ns2::", "ns2::", ""},
+ {"ns1::ns2::", "ns2::", ""},
{"ns1::"},
},
{
@@ -531,7 +531,8 @@ TEST(ClangdAST, PrintType) {
ASSERT_EQ(InsertionPoints.size(), Case.Types.size());
for (size_t I = 0, E = InsertionPoints.size(); I != E; ++I) {
const auto *DC = InsertionPoints[I];
- EXPECT_EQ(printType(AST.getASTContext().getTypeDeclType(TargetDecl), *DC),
+ EXPECT_EQ(printType(AST.getASTContext().getTypeDeclType(TargetDecl), *DC,
+ /*Placeholder=*/"", /*FullyQualify=*/true),
Case.Types[I]);
}
}
diff --git a/clang-tools-extra/clangd/unittests/CMakeLists.txt b/clang-tools-extra/clangd/unittests/CMakeLists.txt
index d425070..9656eea 100644
--- a/clang-tools-extra/clangd/unittests/CMakeLists.txt
+++ b/clang-tools-extra/clangd/unittests/CMakeLists.txt
@@ -92,6 +92,7 @@ add_unittest(ClangdUnitTests ClangdTests
SourceCodeTests.cpp
StdLibTests.cpp
SymbolCollectorTests.cpp
+ SymbolDocumentationTests.cpp
SymbolInfoTests.cpp
SyncAPI.cpp
TUSchedulerTests.cpp
diff --git a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
index 1a1c32c..7640569 100644
--- a/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
+++ b/clang-tools-extra/clangd/unittests/CodeCompleteTests.cpp
@@ -4473,6 +4473,198 @@ TEST(CompletionTest, SkipExplicitObjectParameter) {
snippetSuffix(""))));
}
}
+
+TEST(CompletionTest, MemberAccessInExplicitObjMemfn) {
+ Annotations Code(R"cpp(
+ struct A {
+ int member {};
+ int memberFnA(int a);
+ int memberFnA(this A&, float a);
+
+ void foo(this A& self) {
+ // Should not offer any members here, since
+ // it needs to be referenced through `self`.
+ mem$c1^;
+ // should offer all results
+ self.mem$c2^;
+
+ [&]() {
+ // should not offer any results
+ mem$c3^;
+ }();
+ }
+ };
+ )cpp");
+
+ auto TU = TestTU::withCode(Code.code());
+ TU.ExtraArgs = {"-std=c++23"};
+
+ auto Preamble = TU.preamble();
+ ASSERT_TRUE(Preamble);
+
+ CodeCompleteOptions Opts{};
+
+ MockFS FS;
+ auto Inputs = TU.inputs(FS);
+
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c1"),
+ Preamble.get(), Inputs, Opts);
+
+ EXPECT_THAT(Result.Completions, ElementsAre());
+ }
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c2"),
+ Preamble.get(), Inputs, Opts);
+
+ EXPECT_THAT(
+ Result.Completions,
+ UnorderedElementsAre(named("member"),
+ AllOf(named("memberFnA"), signature("(int a)"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("memberFnA"), signature("(float a)"),
+ snippetSuffix("(${1:float a})"))));
+ }
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"),
+ Preamble.get(), Inputs, Opts);
+
+ EXPECT_THAT(Result.Completions, ElementsAre());
+ }
+}
+
+TEST(CompletionTest, ListExplicitObjectOverloads) {
+ Annotations Code(R"cpp(
+ struct S {
+ void foo1(int a);
+ void foo2(int a) const;
+ void foo2(this const S& self, float a);
+ void foo3(this const S& self, int a);
+ void foo4(this S& self, int a);
+ };
+
+ void S::foo1(int a) {
+ this->$c1^;
+ }
+
+ void S::foo2(int a) const {
+ this->$c2^;
+ }
+
+ void S::foo3(this const S& self, int a) {
+ self.$c3^;
+ }
+
+ void S::foo4(this S& self, int a) {
+ self.$c4^;
+ }
+
+ void test1(S s) {
+ s.$c5^;
+ }
+
+ void test2(const S s) {
+ s.$c6^;
+ }
+ )cpp");
+
+ auto TU = TestTU::withCode(Code.code());
+ TU.ExtraArgs = {"-std=c++23"};
+
+ auto Preamble = TU.preamble();
+ ASSERT_TRUE(Preamble);
+
+ CodeCompleteOptions Opts{};
+
+ MockFS FS;
+ auto Inputs = TU.inputs(FS);
+
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c1"),
+ Preamble.get(), Inputs, Opts);
+ EXPECT_THAT(
+ Result.Completions,
+ UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(float a) const"),
+ snippetSuffix("(${1:float a})")),
+ AllOf(named("foo3"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo4"), signature("(int a)"),
+ snippetSuffix("(${1:int a})"))));
+ }
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c2"),
+ Preamble.get(), Inputs, Opts);
+ EXPECT_THAT(
+ Result.Completions,
+ UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(float a) const"),
+ snippetSuffix("(${1:float a})")),
+ AllOf(named("foo3"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})"))));
+ }
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c3"),
+ Preamble.get(), Inputs, Opts);
+ EXPECT_THAT(
+ Result.Completions,
+ UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(float a) const"),
+ snippetSuffix("(${1:float a})")),
+ AllOf(named("foo3"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})"))));
+ }
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c4"),
+ Preamble.get(), Inputs, Opts);
+ EXPECT_THAT(
+ Result.Completions,
+ UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(float a) const"),
+ snippetSuffix("(${1:float a})")),
+ AllOf(named("foo3"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo4"), signature("(int a)"),
+ snippetSuffix("(${1:int a})"))));
+ }
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c5"),
+ Preamble.get(), Inputs, Opts);
+ EXPECT_THAT(
+ Result.Completions,
+ UnorderedElementsAre(AllOf(named("foo1"), signature("(int a)"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(float a) const"),
+ snippetSuffix("(${1:float a})")),
+ AllOf(named("foo3"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo4"), signature("(int a)"),
+ snippetSuffix("(${1:int a})"))));
+ }
+ {
+ auto Result = codeComplete(testPath(TU.Filename), Code.point("c6"),
+ Preamble.get(), Inputs, Opts);
+ EXPECT_THAT(
+ Result.Completions,
+ UnorderedElementsAre(AllOf(named("foo2"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})")),
+ AllOf(named("foo2"), signature("(float a) const"),
+ snippetSuffix("(${1:float a})")),
+ AllOf(named("foo3"), signature("(int a) const"),
+ snippetSuffix("(${1:int a})"))));
+ }
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
index d71b8d5..d94e706 100644
--- a/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
+++ b/clang-tools-extra/clangd/unittests/ConfigYAMLTests.cpp
@@ -230,17 +230,19 @@ TEST(ParseYAML, CodePatterns) {
EXPECT_THAT(Results[0].Completion.CodePatterns, llvm::ValueIs(val("None")));
}
-TEST(ParseYAML, ShowAKA) {
+TEST(ParseYAML, Hover) {
CapturedDiags Diags;
Annotations YAML(R"yaml(
Hover:
ShowAKA: True
+ MacroContentsLimit: 4096
)yaml");
auto Results =
Fragment::parseYAML(YAML.code(), "config.yaml", Diags.callback());
ASSERT_THAT(Diags.Diagnostics, IsEmpty());
ASSERT_EQ(Results.size(), 1u);
EXPECT_THAT(Results[0].Hover.ShowAKA, llvm::ValueIs(val(true)));
+ EXPECT_THAT(Results[0].Hover.MacroContentsLimit, llvm::ValueIs(val(4096U)));
}
TEST(ParseYAML, InlayHints) {
diff --git a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
index cb2c17a..5c857d0 100644
--- a/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
+++ b/clang-tools-extra/clangd/unittests/DumpASTTests.cpp
@@ -72,15 +72,14 @@ declaration: Namespace - root
expression: BinaryOperator - +
expression: ImplicitCast - LValueToRValue
expression: DeclRef - x
- specifier: TypeSpec
+ specifier: Type
type: Record - S
expression: ImplicitCast - LValueToRValue
expression: Member - x
expression: CXXBindTemporary
expression: CXXTemporaryObject - S
- type: Elaborated
+ type: Record - S
specifier: Namespace - root::
- type: Record - S
)"},
{R"cpp(
namespace root {
@@ -104,14 +103,13 @@ declaration: Namespace - root
expression: BinaryOperator - +
expression: ImplicitCast - LValueToRValue
expression: DeclRef - x
- specifier: TypeSpec
+ specifier: Type
type: Record - S
expression: ImplicitCast - LValueToRValue
expression: Member - x
expression: CXXTemporaryObject - S
- type: Elaborated
+ type: Record - S
specifier: Namespace - root::
- type: Record - S
)"},
{R"cpp(
namespace root {
@@ -138,7 +136,7 @@ declaration: Namespace - root
type: Builtin - unsigned int
statement: Return
expression: DependentScopeDeclRef - value
- specifier: TypeSpec
+ specifier: Type
type: TemplateTypeParm - T
)"},
{R"cpp(
@@ -154,8 +152,7 @@ declaration: Var - root
expression: DeclRef - operator+
expression: MaterializeTemporary - lvalue
expression: CXXTemporaryObject - Foo
- type: Elaborated
- type: Record - Foo
+ type: Record - Foo
expression: IntegerLiteral - 42
)"},
{R"cpp(
diff --git a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
index 4d77f9d..f369e1b 100644
--- a/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
+++ b/clang-tools-extra/clangd/unittests/FindTargetTests.cpp
@@ -731,6 +731,12 @@ TEST_F(TargetDeclTest, BuiltinTemplates) {
using type_pack_element = [[__type_pack_element]]<N, Pack...>;
)cpp";
EXPECT_DECLS("TemplateSpecializationTypeLoc", );
+
+ Code = R"cpp(
+ template <template <class...> class Templ, class... Types>
+ using dedup_types = Templ<[[__builtin_dedup_pack]]<Types...>...>;
+ )cpp";
+ EXPECT_DECLS("TemplateSpecializationTypeLoc", );
}
TEST_F(TargetDeclTest, MemberOfTemplate) {
@@ -992,7 +998,7 @@ TEST_F(TargetDeclTest, DependentTypes) {
)cpp";
EXPECT_DECLS("DependentNameTypeLoc", "struct B");
- // Heuristic resolution of dependent type name which doesn't get a TypeLoc
+ // Heuristic resolution of dependent type name within a NestedNameSpecifierLoc
Code = R"cpp(
template <typename>
struct A { struct B { struct C {}; }; };
@@ -1000,7 +1006,7 @@ TEST_F(TargetDeclTest, DependentTypes) {
template <typename T>
void foo(typename A<T>::[[B]]::C);
)cpp";
- EXPECT_DECLS("NestedNameSpecifierLoc", "struct B");
+ EXPECT_DECLS("DependentNameTypeLoc", "struct B");
// Heuristic resolution of dependent type name whose qualifier is also
// dependent
diff --git a/clang-tools-extra/clangd/unittests/HoverTests.cpp b/clang-tools-extra/clangd/unittests/HoverTests.cpp
index 12d260d..743c0dc 100644
--- a/clang-tools-extra/clangd/unittests/HoverTests.cpp
+++ b/clang-tools-extra/clangd/unittests/HoverTests.cpp
@@ -2894,7 +2894,7 @@ TEST(Hover, All) {
)cpp",
[](HoverInfo &HI) {
HI.Name = "this";
- HI.Definition = "const Foo<T> *";
+ HI.Definition = "const ns::Foo<T> *";
}},
{
R"cpp(// this expr for specialization class
@@ -2910,7 +2910,7 @@ TEST(Hover, All) {
)cpp",
[](HoverInfo &HI) {
HI.Name = "this";
- HI.Definition = "Foo<int> *";
+ HI.Definition = "ns::Foo<int> *";
}},
{
R"cpp(// this expr for partial specialization struct
@@ -2926,7 +2926,7 @@ TEST(Hover, All) {
)cpp",
[](HoverInfo &HI) {
HI.Name = "this";
- HI.Definition = "const Foo<int, F> *";
+ HI.Definition = "const ns::Foo<int, F> *";
}},
{
R"cpp(
@@ -3046,8 +3046,8 @@ TEST(Hover, All) {
HI.Kind = index::SymbolKind::Function;
HI.NamespaceScope = "";
HI.Definition = "MyRect foobar()";
- HI.Type = {"MyRect ()", "MyRect ()"};
- HI.ReturnType = {"MyRect", "MyRect"};
+ HI.Type = {"MyRect ()", "struct MyRect ()"};
+ HI.ReturnType = {"MyRect", "struct MyRect"};
HI.Parameters.emplace();
}},
{R"cpp(
@@ -3409,7 +3409,8 @@ TEST(Hover, DocsFromMostSpecial) {
TEST(Hover, Present) {
struct {
const std::function<void(HoverInfo &)> Builder;
- llvm::StringRef ExpectedRender;
+ llvm::StringRef ExpectedMarkdownRender;
+ llvm::StringRef ExpectedDoxygenRender;
} Cases[] = {
{
[](HoverInfo &HI) {
@@ -3417,6 +3418,7 @@ TEST(Hover, Present) {
HI.Name = "X";
},
R"(X)",
+ R"(### `X`)",
},
{
[](HoverInfo &HI) {
@@ -3424,6 +3426,7 @@ TEST(Hover, Present) {
HI.Name = "foo";
},
R"(namespace-alias foo)",
+ R"(### namespace-alias `foo`)",
},
{
[](HoverInfo &HI) {
@@ -3446,6 +3449,24 @@ Size: 10 bytes
documentation
template <typename T, typename C = bool> class Foo {})",
+ R"(### class
+
+---
+```cpp
+template <typename T, typename C = bool> class Foo {}
+```
+
+---
+**Template Parameters:**
+
+- `typename T`
+- `typename C = bool`
+
+---
+documentation
+
+---
+Size: 10 bytes)",
},
{
[](HoverInfo &HI) {
@@ -3476,6 +3497,26 @@ template <typename T, typename C = bool> class Foo {})",
"\n"
"// In namespace ns\n"
"ret_type foo(params) {}",
+ R"(### function
+
+---
+```cpp
+// In namespace ns
+ret_type foo(params) {}
+```
+
+---
+**Parameters:**
+
+-
+- `type (aka can_type)`
+- `type foo (aka can_type)`
+- `type foo = default (aka can_type)`
+
+---
+**Returns:**
+
+`ret_type (aka can_ret_type)`)",
},
{
[](HoverInfo &HI) {
@@ -3502,6 +3543,22 @@ Size: 4 bytes (+4 bytes padding), alignment 4 bytes
// In test::Bar
def)",
+ R"(### field
+
+---
+```cpp
+// In test::Bar
+def
+```
+
+---
+Type: `type (aka can_type)`
+
+Value = `value`
+
+Offset: 12 bytes
+
+Size: 4 bytes (+4 bytes padding), alignment 4 bytes)",
},
{
[](HoverInfo &HI) {
@@ -3528,6 +3585,22 @@ Size: 25 bits (+4 bits padding), alignment 8 bytes
// In test::Bar
def)",
+ R"(### field
+
+---
+```cpp
+// In test::Bar
+def
+```
+
+---
+Type: `type (aka can_type)`
+
+Value = `value`
+
+Offset: 4 bytes and 3 bits
+
+Size: 25 bits (+4 bits padding), alignment 8 bytes)",
},
{
[](HoverInfo &HI) {
@@ -3541,6 +3614,13 @@ def)",
// In test::Bar
public: def)",
+ R"(### field
+
+---
+```cpp
+// In test::Bar
+public: def
+```)",
},
{
[](HoverInfo &HI) {
@@ -3560,6 +3640,18 @@ public: def)",
// In cls<int>
protected: size_t method())",
+ R"(### instance-method
+
+---
+```cpp
+// In cls<int>
+protected: size_t method()
+```
+
+---
+**Returns:**
+
+`size_t (aka unsigned long)`)",
},
{
[](HoverInfo &HI) {
@@ -3587,6 +3679,19 @@ Parameters:
// In cls
public: cls(int a, int b = 5))",
+ R"(### constructor
+
+---
+```cpp
+// In cls
+public: cls(int a, int b = 5)
+```
+
+---
+**Parameters:**
+
+- `int a`
+- `int b = 5`)",
},
{
[](HoverInfo &HI) {
@@ -3600,6 +3705,13 @@ public: cls(int a, int b = 5))",
// In namespace ns1
private: union foo {})",
+ R"(### union
+
+---
+```cpp
+// In namespace ns1
+private: union foo {}
+```)",
},
{
[](HoverInfo &HI) {
@@ -3625,6 +3737,20 @@ Passed as arg_a
// In test::Bar
int foo = 3)",
+ R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed as arg_a)",
},
{
[](HoverInfo &HI) {
@@ -3637,6 +3763,10 @@ int foo = 3)",
R"(variable foo
Passed by value)",
+ R"(### variable `foo`
+
+---
+Passed by value)",
},
{
[](HoverInfo &HI) {
@@ -3662,6 +3792,20 @@ Passed by reference as arg_a
// In test::Bar
int foo = 3)",
+ R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed by reference as arg_a)",
},
{
[](HoverInfo &HI) {
@@ -3687,6 +3831,20 @@ Passed as arg_a (converted to alias_int)
// In test::Bar
int foo = 3)",
+ R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed as arg_a (converted to alias_int))",
},
{
[](HoverInfo &HI) {
@@ -3702,6 +3860,15 @@ int foo = 3)",
// Expands to
(1 + 1))",
+ R"(### macro
+
+---
+```cpp
+#define PLUS_ONE(X) (X+1)
+
+// Expands to
+(1 + 1)
+```)",
},
{
[](HoverInfo &HI) {
@@ -3727,38 +3894,283 @@ Passed by const reference as arg_a (converted to int)
// In test::Bar
int foo = 3)",
+ R"(### variable
+
+---
+```cpp
+// In test::Bar
+int foo = 3
+```
+
+---
+Type: `int`
+
+Value = `3`
+
+Passed by const reference as arg_a (converted to int))",
},
{
[](HoverInfo &HI) {
HI.Name = "stdio.h";
HI.Definition = "/usr/include/stdio.h";
+ HI.Kind = index::SymbolKind::IncludeDirective;
},
R"(stdio.h
/usr/include/stdio.h)",
+ R"(### `stdio.h`
+
+`/usr/include/stdio.h`)",
+ },
+ {
+ [](HoverInfo &HI) {
+ HI.Name = "foo.h";
+ HI.UsedSymbolNames = {"Foo", "Bar", "Bar"};
+ HI.Kind = index::SymbolKind::IncludeDirective;
+ },
+ R"(foo.h
+
+provides Foo, Bar, Bar)",
+ R"(### `foo.h`
+
+---
+provides `Foo`, `Bar`, `Bar`)",
},
{[](HoverInfo &HI) {
HI.Name = "foo.h";
- HI.UsedSymbolNames = {"Foo", "Bar", "Bar"};
+ HI.UsedSymbolNames = {"Foo", "Bar", "Baz", "Foobar", "Qux", "Quux"};
+ HI.Kind = index::SymbolKind::IncludeDirective;
},
R"(foo.h
-provides Foo, Bar, Bar)"},
+provides Foo, Bar, Baz, Foobar, Qux and 1 more)",
+ R"(### `foo.h`
+
+---
+provides `Foo`, `Bar`, `Baz`, `Foobar`, `Qux` and 1 more)"}};
+
+ for (const auto &C : Cases) {
+ HoverInfo HI;
+ C.Builder(HI);
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Markdown;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedMarkdownRender);
+ }
+ for (const auto &C : Cases) {
+ HoverInfo HI;
+ C.Builder(HI);
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedDoxygenRender);
+ }
+}
+
+TEST(Hover, PresentDocumentation) {
+ struct {
+ const std::function<void(HoverInfo &)> Builder;
+ llvm::StringRef ExpectedMarkdownRender;
+ llvm::StringRef ExpectedDoxygenRender;
+ } Cases[] = {
{[](HoverInfo &HI) {
- HI.Name = "foo.h";
- HI.UsedSymbolNames = {"Foo", "Bar", "Baz", "Foobar", "Qux", "Quux"};
+ HI.Kind = index::SymbolKind::Function;
+ HI.Documentation = "@brief brief doc\n\n"
+ "longer doc";
+ HI.Definition = "void foo()";
+ HI.Name = "foo";
},
- R"(foo.h
+ R"(### function `foo`
+
+---
+@brief brief doc
+
+longer doc
+
+---
+```cpp
+void foo()
+```)",
+ R"(### function
-provides Foo, Bar, Baz, Foobar, Qux and 1 more)"}};
+---
+```cpp
+void foo()
+```
+
+---
+brief doc
+
+---
+longer doc)"},
+ {[](HoverInfo &HI) {
+ HI.Kind = index::SymbolKind::Function;
+ HI.Documentation = "@brief brief doc\n\n"
+ "longer doc";
+ HI.Definition = "int foo()";
+ HI.ReturnType = "int";
+ HI.Name = "foo";
+ },
+ R"(### function `foo`
+
+---
+→ `int`
+
+@brief brief doc
+
+longer doc
+
+---
+```cpp
+int foo()
+```)",
+ R"(### function
+
+---
+```cpp
+int foo()
+```
+
+---
+brief doc
+
+---
+**Returns:**
+
+`int`
+
+---
+longer doc)"},
+ {[](HoverInfo &HI) {
+ HI.Kind = index::SymbolKind::Function;
+ HI.Documentation = "@brief brief doc\n\n"
+ "longer doc\n@param a this is a param\n@return it "
+ "returns something";
+ HI.Definition = "int foo(int a)";
+ HI.ReturnType = "int";
+ HI.Name = "foo";
+ HI.Parameters.emplace();
+ HI.Parameters->emplace_back();
+ HI.Parameters->back().Type = "int";
+ HI.Parameters->back().Name = "a";
+ },
+ R"(### function `foo`
+
+---
+→ `int`
+
+Parameters:
+
+- `int a`
+
+@brief brief doc
+
+longer doc
+@param a this is a param
+@return it returns something
+
+---
+```cpp
+int foo(int a)
+```)",
+ R"(### function
+
+---
+```cpp
+int foo(int a)
+```
+
+---
+brief doc
+
+---
+**Parameters:**
+
+- `int a` - this is a param
+
+---
+**Returns:**
+
+`int` - it returns something
+
+---
+longer doc)"},
+ {[](HoverInfo &HI) {
+ HI.Kind = index::SymbolKind::Function;
+ HI.Documentation = "@brief brief doc\n\n"
+ "longer doc\n@param a this is a param\n@param b "
+ "does not exist\n@return it returns something";
+ HI.Definition = "int foo(int a)";
+ HI.ReturnType = "int";
+ HI.Name = "foo";
+ HI.Parameters.emplace();
+ HI.Parameters->emplace_back();
+ HI.Parameters->back().Type = "int";
+ HI.Parameters->back().Name = "a";
+ },
+ R"(### function `foo`
+
+---
+→ `int`
+
+Parameters:
+
+- `int a`
+
+@brief brief doc
+
+longer doc
+@param a this is a param
+@param b does not exist
+@return it returns something
+
+---
+```cpp
+int foo(int a)
+```)",
+ R"(### function
+
+---
+```cpp
+int foo(int a)
+```
+
+---
+brief doc
+
+---
+**Parameters:**
+
+- `int a` - this is a param
+
+---
+**Returns:**
+
+`int` - it returns something
+
+---
+longer doc)"},
+ };
for (const auto &C : Cases) {
HoverInfo HI;
C.Builder(HI);
Config Cfg;
Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Markdown;
WithContextValue WithCfg(Config::Key, std::move(Cfg));
- EXPECT_EQ(HI.present(MarkupKind::PlainText), C.ExpectedRender);
+ EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedMarkdownRender);
+ }
+ for (const auto &C : Cases) {
+ HoverInfo HI;
+ C.Builder(HI);
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ EXPECT_EQ(HI.present(MarkupKind::Markdown), C.ExpectedDoxygenRender);
}
}
@@ -3936,6 +4348,21 @@ TEST(Hover, PresentRulers) {
"```";
EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedMarkdown);
+ llvm::StringRef ExpectedDoxygenMarkdown = //
+ "### variable\n"
+ "\n"
+ "---\n"
+ "```cpp\n"
+ "def\n"
+ "```\n\n"
+ "---\n"
+ "Value = `val`";
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ EXPECT_EQ(HI.present(MarkupKind::Markdown), ExpectedDoxygenMarkdown);
+
llvm::StringRef ExpectedPlaintext = R"pt(variable foo
Value = val
@@ -4339,6 +4766,188 @@ constexpr u64 pow_with_mod(u64 a, u64 b, u64 p) {
EXPECT_TRUE(H->Value);
EXPECT_TRUE(H->Type);
}
+
+TEST(Hover, HoverMacroContentsLimit) {
+ const char *const Code =
+ R"cpp(
+ #define C(A) A##A // Concatenate
+ #define E(A) C(A) // Expand
+ #define Z0032 00000000000000000000000000000000
+ #define Z0064 E(Z0032)
+ #define Z0128 E(Z0064)
+ #define Z0256 E(Z0128)
+ #define Z0512 E(Z0256)
+ #define Z1024 E(Z0512)
+ #define Z2048 E(Z1024)
+ #define Z4096 E(Z2048) // 4096 zeroes
+ int main() { return [[^Z4096]]; }
+ )cpp";
+
+ struct {
+ uint32_t MacroContentsLimit;
+ const std::string ExpectedDefinition;
+ } Cases[] = {
+ // With a limit of 2048, the macro expansion should get dropped.
+ {2048, "#define Z4096 E(Z2048)"},
+ // With a limit of 8192, the macro expansion should be fully expanded.
+ {8192, std::string("#define Z4096 E(Z2048)\n\n") +
+ std::string("// Expands to\n") + std::string(4096, '0')},
+ };
+ for (const auto &Case : Cases) {
+ SCOPED_TRACE(Code);
+
+ Annotations T(Code);
+ TestTU TU = TestTU::withCode(T.code());
+ auto AST = TU.build();
+ Config Cfg;
+ Cfg.Hover.MacroContentsLimit = Case.MacroContentsLimit;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ auto H = getHover(AST, T.point(), format::getLLVMStyle(), nullptr);
+ ASSERT_TRUE(H);
+
+ EXPECT_EQ(H->Definition, Case.ExpectedDefinition);
+ }
+}
+
+TEST(Hover, FunctionParameters) {
+ struct {
+ const char *const Code;
+ const std::function<void(HoverInfo &)> ExpectedBuilder;
+ std::string ExpectedRender;
+ } Cases[] = {
+ {R"cpp(/// Function doc
+ void foo(int [[^a]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "a";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int a";
+ HI.Documentation = "";
+ },
+ "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nType: `int`"},
+ {R"cpp(/// Function doc
+ /// @param a this is doc for a
+ void foo(int [[^a]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "a";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int a";
+ HI.Documentation = "this is doc for a";
+ },
+ "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nthis is doc "
+ "for a\n\n---\nType: `int`"},
+ {R"cpp(/// Function doc
+ /// @param b this is doc for b
+ void foo(int [[^a]], int b);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "a";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int a";
+ HI.Documentation = "";
+ },
+ "### param\n\n---\n```cpp\n// In foo\nint a\n```\n\n---\nType: `int`"},
+ {R"cpp(/// Function doc
+ /// @param b this is doc for \p b
+ void foo(int a, int [[^b]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "b";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int b";
+ HI.Documentation = "this is doc for \\p b";
+ },
+ "### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is doc "
+ "for `b`\n\n---\nType: `int`"},
+ {R"cpp(/// Function doc
+ /// @param b this is doc for \p b
+ template <typename T>
+ void foo(T a, T [[^b]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "b";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "T";
+ HI.Definition = "T b";
+ HI.Documentation = "this is doc for \\p b";
+ },
+ "### param\n\n---\n```cpp\n// In foo\nT b\n```\n\n---\nthis is doc for "
+ "`b`\n\n---\nType: `T`"},
+ {R"cpp(/// Function doc
+ /// @param b this is <b>doc</b> <html-tag attribute/> <another-html-tag attribute="value">for</another-html-tag> \p b
+ void foo(int a, int [[^b]]);
+ )cpp",
+ [](HoverInfo &HI) {
+ HI.Name = "b";
+ HI.Kind = index::SymbolKind::Parameter;
+ HI.NamespaceScope = "";
+ HI.LocalScope = "foo::";
+ HI.Type = "int";
+ HI.Definition = "int b";
+ HI.Documentation =
+ "this is <b>doc</b> <html-tag attribute/> <another-html-tag "
+ "attribute=\"value\">for</another-html-tag> \\p b";
+ },
+ "### param\n\n---\n```cpp\n// In foo\nint b\n```\n\n---\nthis is "
+ "\\<b>doc\\</b> \\<html-tag attribute/> \\<another-html-tag "
+ "attribute=\"value\">for\\</another-html-tag> `b`\n\n---\nType: `int`"},
+ };
+
+ // Create a tiny index, so tests above can verify documentation is fetched.
+ Symbol IndexSym = func("indexSymbol");
+ IndexSym.Documentation = "comment from index";
+ SymbolSlab::Builder Symbols;
+ Symbols.insert(IndexSym);
+ auto Index =
+ MemIndex::build(std::move(Symbols).build(), RefSlab(), RelationSlab());
+
+ for (const auto &Case : Cases) {
+ SCOPED_TRACE(Case.Code);
+
+ Annotations T(Case.Code);
+ TestTU TU = TestTU::withCode(T.code());
+ auto AST = TU.build();
+ Config Cfg;
+ Cfg.Hover.ShowAKA = true;
+ Cfg.Documentation.CommentFormat = Config::CommentFormatPolicy::Doxygen;
+ WithContextValue WithCfg(Config::Key, std::move(Cfg));
+ auto H = getHover(AST, T.point(), format::getLLVMStyle(), Index.get());
+ ASSERT_TRUE(H);
+ HoverInfo Expected;
+ Expected.SymRange = T.range();
+ Case.ExpectedBuilder(Expected);
+
+ EXPECT_EQ(H->present(MarkupKind::Markdown), Case.ExpectedRender);
+ EXPECT_EQ(H->NamespaceScope, Expected.NamespaceScope);
+ EXPECT_EQ(H->LocalScope, Expected.LocalScope);
+ EXPECT_EQ(H->Name, Expected.Name);
+ EXPECT_EQ(H->Kind, Expected.Kind);
+ EXPECT_EQ(H->Documentation, Expected.Documentation);
+ EXPECT_EQ(H->Definition, Expected.Definition);
+ EXPECT_EQ(H->Type, Expected.Type);
+ EXPECT_EQ(H->ReturnType, Expected.ReturnType);
+ EXPECT_EQ(H->Parameters, Expected.Parameters);
+ EXPECT_EQ(H->TemplateParameters, Expected.TemplateParameters);
+ EXPECT_EQ(H->SymRange, Expected.SymRange);
+ EXPECT_EQ(H->Value, Expected.Value);
+ }
+}
+
} // namespace
} // namespace clangd
} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
index e0cd955..99e728c 100644
--- a/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
+++ b/clang-tools-extra/clangd/unittests/InlayHintTests.cpp
@@ -1295,14 +1295,7 @@ TEST(TypeHints, NoQualifiers) {
}
}
)cpp",
- ExpectedHint{": S1", "x"},
- // FIXME: We want to suppress scope specifiers
- // here because we are into the whole
- // brevity thing, but the ElaboratedType
- // printer does not honor the SuppressScope
- // flag by design, so we need to extend the
- // PrintingPolicy to support this use case.
- ExpectedHint{": S2::Inner<int>", "y"});
+ ExpectedHint{": S1", "x"}, ExpectedHint{": Inner<int>", "y"});
}
TEST(TypeHints, Lambda) {
diff --git a/clang-tools-extra/clangd/unittests/QualityTests.cpp b/clang-tools-extra/clangd/unittests/QualityTests.cpp
index 619ea32..4954659 100644
--- a/clang-tools-extra/clangd/unittests/QualityTests.cpp
+++ b/clang-tools-extra/clangd/unittests/QualityTests.cpp
@@ -121,7 +121,9 @@ TEST(QualityTests, SymbolRelevanceSignalExtraction) {
SymbolRelevanceSignals Relevance;
Relevance.merge(CodeCompletionResult(&findDecl(AST, "deprecated"),
- /*Priority=*/42, nullptr, false,
+ /*Priority=*/42,
+ /*Qualifier=*/std::nullopt,
+ /*QualifierIsInformative=*/false,
/*Accessible=*/false));
EXPECT_EQ(Relevance.NameMatch, SymbolRelevanceSignals().NameMatch);
EXPECT_TRUE(Relevance.Forbidden);
@@ -487,13 +489,15 @@ TEST(QualityTests, ItemWithFixItsRankedDown) {
auto AST = Header.build();
SymbolRelevanceSignals RelevanceWithFixIt;
- RelevanceWithFixIt.merge(CodeCompletionResult(&findDecl(AST, "x"), 0, nullptr,
- false, true, {FixItHint{}}));
+ RelevanceWithFixIt.merge(CodeCompletionResult(
+ &findDecl(AST, "x"), /*Priority=*/0, /*Qualifier=*/std::nullopt,
+ /*QualifierIsInformative=*/false, /*Accessible=*/true, {FixItHint{}}));
EXPECT_TRUE(RelevanceWithFixIt.NeedsFixIts);
SymbolRelevanceSignals RelevanceWithoutFixIt;
- RelevanceWithoutFixIt.merge(
- CodeCompletionResult(&findDecl(AST, "x"), 0, nullptr, false, true, {}));
+ RelevanceWithoutFixIt.merge(CodeCompletionResult(
+ &findDecl(AST, "x"), /*Priority=*/0, /*Qualifier=*/std::nullopt,
+ /*QualifierIsInformative=*/false, /*Accessible=*/true, {}));
EXPECT_FALSE(RelevanceWithoutFixIt.NeedsFixIts);
EXPECT_LT(RelevanceWithFixIt.evaluateHeuristics(),
diff --git a/clang-tools-extra/clangd/unittests/RenameTests.cpp b/clang-tools-extra/clangd/unittests/RenameTests.cpp
index 2cb0722..5d2a77b 100644
--- a/clang-tools-extra/clangd/unittests/RenameTests.cpp
+++ b/clang-tools-extra/clangd/unittests/RenameTests.cpp
@@ -17,10 +17,11 @@
#include "clang/Tooling/Core/Replacement.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/MemoryBuffer.h"
-#include <algorithm>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include <algorithm>
+
namespace clang {
namespace clangd {
namespace {
@@ -861,6 +862,25 @@ TEST(RenameTest, WithinFileRename) {
void func([[Fo^o]] *f) {}
)cpp",
+
+ // rename with explicit object parameter
+ R"cpp(
+ struct Foo {
+ int [[memb^er]] {};
+ auto&& getter1(this auto&& self) {
+ auto local = [&] {
+ return self.[[memb^er]];
+ }();
+ return local + self.[[memb^er]];
+ }
+ auto&& getter2(this Foo&& self) {
+ return self.[[memb^er]];
+ }
+ int normal() {
+ return this->[[mem^ber]] + [[memb^er]];
+ }
+ };
+ )cpp",
};
llvm::StringRef NewName = "NewName";
for (llvm::StringRef T : Tests) {
@@ -868,6 +888,7 @@ TEST(RenameTest, WithinFileRename) {
Annotations Code(T);
auto TU = TestTU::withCode(Code.code());
TU.ExtraArgs.push_back("-xobjective-c++");
+ TU.ExtraArgs.push_back("-std=c++23");
auto AST = TU.build();
auto Index = TU.index();
for (const auto &RenamePos : Code.points()) {
diff --git a/clang-tools-extra/clangd/unittests/SelectionTests.cpp b/clang-tools-extra/clangd/unittests/SelectionTests.cpp
index aaaf758..3df19d8 100644
--- a/clang-tools-extra/clangd/unittests/SelectionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SelectionTests.cpp
@@ -104,9 +104,9 @@ TEST(SelectionTest, CommonAncestor) {
{
R"cpp(
template <typename T>
- int x = [[T::^U::]]ccc();
+ int x = T::[[^U]]::ccc();
)cpp",
- "NestedNameSpecifierLoc",
+ "DependentNameTypeLoc",
},
{
R"cpp(
diff --git a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
index 7ede19c..4efae25 100644
--- a/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
+++ b/clang-tools-extra/clangd/unittests/SemanticSelectionTests.cpp
@@ -371,6 +371,45 @@ TEST(FoldingRanges, PseudoParserWithoutLineFoldings) {
//[[ foo
/* bar */]]
)cpp",
+ R"cpp(
+ //Ignore non-conditional directives
+ #define A 1
+
+ void func() {[[
+ int Variable = 100;
+
+ #ifdef FOO[[
+ Variable = 1;
+ #if 1[[
+ Variable = 4;
+ ]]#endif
+ ]]#else[[
+ Variable = 2;
+ //handle nested directives
+ #if 1[[
+ Variable = 3;
+ ]]#endif
+ ]]#endif
+
+
+ ]]}
+ )cpp",
+ R"cpp(
+ int Variable = 0;
+ #if defined(WALDO)
+ Variable = 1;
+ #
+ )cpp",
+ R"cpp(
+ int Variable = 0;
+ #if defined(WALDO)[[
+ Variable = 1;
+ ]]#elif 1[[
+ Variable = 2;
+ ]]#else
+ Variable = 3;
+ #
+ )cpp",
};
for (const char *Test : Tests) {
auto T = Annotations(Test);
diff --git a/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
new file mode 100644
index 0000000..31a6a14
--- /dev/null
+++ b/clang-tools-extra/clangd/unittests/SymbolDocumentationTests.cpp
@@ -0,0 +1,215 @@
+//===-- SymbolDocumentationTests.cpp --------------------------------------===//
+//
+// Part of the LLVM Project, 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
+//
+//===----------------------------------------------------------------------===//
+#include "SymbolDocumentation.h"
+
+#include "support/Markup.h"
+#include "clang/Basic/CommentOptions.h"
+#include "llvm/ADT/StringRef.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+
+TEST(SymbolDocumentation, UnhandledDocs) {
+
+ CommentOptions CommentOpts;
+
+ struct Case {
+ llvm::StringRef Documentation;
+ llvm::StringRef ExpectedRenderEscapedMarkdown;
+ llvm::StringRef ExpectedRenderMarkdown;
+ llvm::StringRef ExpectedRenderPlainText;
+ } Cases[] = {
+ {
+ "foo bar",
+ "foo bar",
+ "foo bar",
+ "foo bar",
+ },
+ {
+ "foo\nbar\n",
+ "foo\nbar",
+ "foo\nbar",
+ "foo bar",
+ },
+ {
+ "foo\n\nbar\n",
+ "foo\n\nbar",
+ "foo\n\nbar",
+ "foo\n\nbar",
+ },
+ {
+ "foo \\p bar baz",
+ "foo `bar` baz",
+ "foo `bar` baz",
+ "foo bar baz",
+ },
+ {
+ "foo \\e bar baz",
+ "foo \\*bar\\* baz",
+ "foo *bar* baz",
+ "foo *bar* baz",
+ },
+ {
+ "foo \\b bar baz",
+ "foo \\*\\*bar\\*\\* baz",
+ "foo **bar** baz",
+ "foo **bar** baz",
+ },
+ {
+ "foo \\ref bar baz",
+ "foo \\*\\*\\\\ref\\*\\* \\*bar\\* baz",
+ "foo **\\ref** *bar* baz",
+ "foo **\\ref** *bar* baz",
+ },
+ {
+ "foo @ref bar baz",
+ "foo \\*\\*@ref\\*\\* \\*bar\\* baz",
+ "foo **@ref** *bar* baz",
+ "foo **@ref** *bar* baz",
+ },
+ {
+ "\\brief this is a \\n\nbrief description",
+ "",
+ "",
+ "",
+ },
+ {
+ "\\throw exception foo",
+ "\\*\\*\\\\throw\\*\\* \\*exception\\* foo",
+ "**\\throw** *exception* foo",
+ "**\\throw** *exception* foo",
+ },
+ {
+ R"(\brief this is a brief description
+
+\li item 1
+\li item 2
+\arg item 3)",
+ R"(- item 1
+
+- item 2
+
+- item 3)",
+ R"(- item 1
+
+- item 2
+
+- item 3)",
+ R"(- item 1
+
+- item 2
+
+- item 3)",
+ },
+ {
+ "\\defgroup mygroup this is a group\nthis is not a group description",
+ "\\*\\*@defgroup\\*\\* `mygroup this is a group`\n\nthis is not a "
+ "group "
+ "description",
+ "**@defgroup** `mygroup this is a group`\n\nthis is not a group "
+ "description",
+ "**@defgroup** `mygroup this is a group`\n\nthis is not a group "
+ "description",
+ },
+ {
+ R"(\verbatim
+this is a
+verbatim block containing
+some verbatim text
+\endverbatim)",
+ R"(\*\*@verbatim\*\*
+
+```
+this is a
+verbatim block containing
+some verbatim text
+```
+
+\*\*@endverbatim\*\*)",
+ R"(**@verbatim**
+
+```
+this is a
+verbatim block containing
+some verbatim text
+```
+
+**@endverbatim**)",
+ R"(**@verbatim**
+
+this is a
+verbatim block containing
+some verbatim text
+
+**@endverbatim**)",
+ },
+ {
+ "@param foo this is a parameter\n@param bar this is another "
+ "parameter",
+ "",
+ "",
+ "",
+ },
+ {
+ R"(@brief brief docs
+
+@param foo this is a parameter
+
+\brief another brief?
+
+\details these are details
+
+More description
+documentation)",
+ R"(\*\*\\brief\*\* another brief?
+
+\*\*\\details\*\* these are details
+
+More description
+documentation)",
+ R"(**\brief** another brief?
+
+**\details** these are details
+
+More description
+documentation)",
+ R"(**\brief** another brief?
+
+**\details** these are details
+
+More description documentation)",
+ },
+ {
+ R"(<b>this is a bold text</b>
+normal text<i>this is an italic text</i>
+<code>this is a code block</code>)",
+ R"(\<b>this is a bold text\</b>
+normal text\<i>this is an italic text\</i>
+\<code>this is a code block\</code>)",
+ R"(\<b>this is a bold text\</b>
+normal text\<i>this is an italic text\</i>
+\<code>this is a code block\</code>)",
+ "<b>this is a bold text</b> normal text<i>this is an italic text</i> "
+ "<code>this is a code block</code>",
+ },
+ };
+ for (const auto &C : Cases) {
+ markup::Document Doc;
+ SymbolDocCommentVisitor SymbolDoc(C.Documentation, CommentOpts);
+
+ SymbolDoc.docToMarkup(Doc);
+
+ EXPECT_EQ(Doc.asPlainText(), C.ExpectedRenderPlainText);
+ EXPECT_EQ(Doc.asMarkdown(), C.ExpectedRenderMarkdown);
+ EXPECT_EQ(Doc.asEscapedMarkdown(), C.ExpectedRenderEscapedMarkdown);
+ }
+}
+
+} // namespace clangd
+} // namespace clang
diff --git a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
index 482f230..5f91f31 100644
--- a/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
+++ b/clang-tools-extra/clangd/unittests/support/MarkupTests.cpp
@@ -463,6 +463,7 @@ TEST(Document, Separators) {
```cpp
test
```
+
bar)md";
EXPECT_EQ(D.asEscapedMarkdown(), ExpectedMarkdown);
EXPECT_EQ(D.asMarkdown(), ExpectedMarkdown);
@@ -559,6 +560,7 @@ foo
bar
baz
```
+
```cpp
foo
```)md";
@@ -571,6 +573,12 @@ foo
foo)pt";
EXPECT_EQ(D.asPlainText(), ExpectedPlainText);
+
+ Document D2;
+ D2.addCodeBlock("");
+ EXPECT_EQ(D2.asEscapedMarkdown(), "```cpp\n```");
+ EXPECT_EQ(D2.asMarkdown(), "```cpp\n```");
+ EXPECT_EQ(D2.asPlainText(), "");
}
TEST(BulletList, Render) {