VitaNuo updated this revision to Diff 498401.
VitaNuo marked 14 inline comments as done.
VitaNuo added a comment.
Herald added a subscriber: ChuanqiXu.
Address review comments. Make diagnostics symref-centered and attach them to
each reference.
Repository:
rG LLVM Github Monorepo
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D143496/new/
https://reviews.llvm.org/D143496
Files:
clang-tools-extra/clangd/Config.h
clang-tools-extra/clangd/ConfigCompile.cpp
clang-tools-extra/clangd/ConfigFragment.h
clang-tools-extra/clangd/ConfigYAML.cpp
clang-tools-extra/clangd/IncludeCleaner.cpp
clang-tools-extra/clangd/IncludeCleaner.h
clang-tools-extra/clangd/ParsedAST.cpp
clang-tools-extra/clangd/Preamble.cpp
clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h
clang-tools-extra/include-cleaner/lib/Analysis.cpp
Index: clang-tools-extra/include-cleaner/lib/Analysis.cpp
===================================================================
--- clang-tools-extra/include-cleaner/lib/Analysis.cpp
+++ clang-tools-extra/include-cleaner/lib/Analysis.cpp
@@ -49,8 +49,8 @@
}
}
-static std::string spellHeader(const Header &H, HeaderSearch &HS,
- const FileEntry *Main) {
+std::string spellHeader(const Header &H, HeaderSearch &HS,
+ const FileEntry *Main) {
switch (H.kind()) {
case Header::Physical: {
bool IsSystem = false;
Index: clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h
===================================================================
--- clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h
+++ clang-tools-extra/include-cleaner/include/clang-include-cleaner/Analysis.h
@@ -73,6 +73,8 @@
std::string fixIncludes(const AnalysisResults &Results, llvm::StringRef Code,
const format::FormatStyle &IncludeStyle);
+std::string spellHeader(const Header &H, HeaderSearch &HS,
+ const FileEntry *Main);
} // namespace include_cleaner
} // namespace clang
Index: clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
+++ clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
@@ -9,11 +9,19 @@
#include "Annotations.h"
#include "Config.h"
#include "IncludeCleaner.h"
+#include "ParsedAST.h"
#include "SourceCode.h"
#include "TestFS.h"
#include "TestTU.h"
+#include "clang-include-cleaner/Analysis.h"
+#include "clang-include-cleaner/Types.h"
#include "support/Context.h"
+#include "clang/AST/DeclBase.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/ScopeExit.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
#include "llvm/Testing/Support/SupportHelpers.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
@@ -342,7 +350,8 @@
auto AST = TU.build();
EXPECT_THAT(computeUnusedIncludes(AST),
ElementsAre(Pointee(writtenInclusion("<queue>"))));
- EXPECT_THAT(computeUnusedIncludesExperimental(AST),
+ IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
+ EXPECT_THAT(Findings.UnusedIncludes,
ElementsAre(Pointee(writtenInclusion("<queue>"))));
}
@@ -379,12 +388,69 @@
computeUnusedIncludes(AST),
UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")),
Pointee(writtenInclusion("\"dir/unused.h\""))));
+
+ IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
EXPECT_THAT(
- computeUnusedIncludesExperimental(AST),
+ Findings.UnusedIncludes,
UnorderedElementsAre(Pointee(writtenInclusion("\"unused.h\"")),
Pointee(writtenInclusion("\"dir/unused.h\""))));
}
+TEST(IncludeCleaner, GetMissingHeaders) {
+ llvm::StringLiteral MainFile = R"cpp(
+ #include "a.h"
+ #include "dir/c.h"
+ #include <e.h>
+
+ void foo() {
+ b();
+ d();
+ f();
+ })cpp";
+ // Build expected ast with symbols coming from headers.
+ TestTU TU;
+ TU.Filename = "foo.cpp";
+ TU.AdditionalFiles["foo.h"] = guard("void foo();");
+ TU.AdditionalFiles["a.h"] = guard("#include \"b.h\"");
+ TU.AdditionalFiles["b.h"] = guard("void b();");
+
+ TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\"");
+ TU.AdditionalFiles["dir/d.h"] = guard("void d();");
+
+ TU.AdditionalFiles["system/e.h"] = guard("#include <f.h>");
+ TU.AdditionalFiles["system/f.h"] = guard("void f();");
+ TU.ExtraArgs.push_back("-isystem" + testPath("system"));
+
+ TU.Code = MainFile.str();
+ ParsedAST AST = TU.build();
+
+ IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
+ const SourceManager &SM = AST.getSourceManager();
+ const llvm::ArrayRef<syntax::Token> &Tokens =
+ AST.getTokens().spelledTokens(SM.getMainFileID());
+ for (const auto &Token : Tokens) {
+ if (Token.text(SM) == "b") {
+ syntax::FileRange BRange{SM, Token.location(), 1};
+ include_cleaner::Header Header{*SM.getFileManager().getFile("b.h")};
+ MissingIncludeDiagInfo BInfo{"b", BRange, {Header}};
+ EXPECT_THAT(Findings.MissingIncludes, testing::Contains(BInfo));
+ }
+ if (Token.text(SM) == "d") {
+ syntax::FileRange DRange{SM, Token.location(), 1};
+ include_cleaner::Header Header{*SM.getFileManager().getFile("dir/d.h")};
+ MissingIncludeDiagInfo DInfo{"d", DRange, {Header}};
+ EXPECT_THAT(Findings.MissingIncludes, testing::Contains(DInfo));
+ }
+ if (Token.text(SM) == "f") {
+ syntax::FileRange FRange{SM, Token.location(), 1};
+ include_cleaner::Header Header{
+ *SM.getFileManager().getFile("system/f.h")};
+ MissingIncludeDiagInfo FInfo{"f", FRange, {Header}};
+ EXPECT_THAT(Findings.MissingIncludes, testing::Contains(FInfo));
+ }
+ }
+}
+
TEST(IncludeCleaner, VirtualBuffers) {
TestTU TU;
TU.Code = R"cpp(
@@ -554,7 +620,8 @@
ReferencedFiles.User.contains(AST.getSourceManager().getMainFileID()));
EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
- EXPECT_THAT(computeUnusedIncludesExperimental(AST), IsEmpty());
+ IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
+ EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
}
TEST(IncludeCleaner, RecursiveInclusion) {
@@ -583,7 +650,8 @@
EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
- EXPECT_THAT(computeUnusedIncludesExperimental(AST), IsEmpty());
+ IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
+ EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
}
TEST(IncludeCleaner, IWYUPragmaExport) {
@@ -608,7 +676,8 @@
// FIXME: This is not correct: foo.h is unused but is not diagnosed as such
// because we ignore headers with IWYU export pragmas for now.
EXPECT_THAT(computeUnusedIncludes(AST), IsEmpty());
- EXPECT_THAT(computeUnusedIncludesExperimental(AST), IsEmpty());
+ IncludeCleanerFindings Findings = computeIncludeCleanerFindings(AST);
+ EXPECT_THAT(Findings.UnusedIncludes, IsEmpty());
}
TEST(IncludeCleaner, NoDiagsForObjC) {
@@ -627,7 +696,8 @@
TU.ExtraArgs.emplace_back("-xobjective-c");
Config Cfg;
- Cfg.Diagnostics.UnusedIncludes = Config::Strict;
+ Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;
+ Cfg.Diagnostics.MissingIncludes = Config::IncludesPolicy::Strict;
WithContextValue Ctx(Config::Key, std::move(Cfg));
ParsedAST AST = TU.build();
EXPECT_THAT(AST.getDiagnostics(), llvm::ValueIs(IsEmpty()));
Index: clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
+++ clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
@@ -1897,7 +1897,7 @@
// Off by default.
EXPECT_THAT(*TU.build().getDiagnostics(), IsEmpty());
Config Cfg;
- Cfg.Diagnostics.UnusedIncludes = Config::UnusedIncludesPolicy::Strict;
+ Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;
// Set filtering.
Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back(
[](llvm::StringRef Header) { return Header.endswith("ignore.h"); });
Index: clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
+++ clang-tools-extra/clangd/unittests/ConfigCompileTests.cpp
@@ -249,19 +249,19 @@
// Defaults to None.
EXPECT_TRUE(compileAndApply());
EXPECT_EQ(Conf.Diagnostics.UnusedIncludes,
- Config::UnusedIncludesPolicy::None);
+ Config::IncludesPolicy::None);
Frag = {};
Frag.Diagnostics.UnusedIncludes.emplace("None");
EXPECT_TRUE(compileAndApply());
EXPECT_EQ(Conf.Diagnostics.UnusedIncludes,
- Config::UnusedIncludesPolicy::None);
+ Config::IncludesPolicy::None);
Frag = {};
Frag.Diagnostics.UnusedIncludes.emplace("Strict");
EXPECT_TRUE(compileAndApply());
EXPECT_EQ(Conf.Diagnostics.UnusedIncludes,
- Config::UnusedIncludesPolicy::Strict);
+ Config::IncludesPolicy::Strict);
Frag = {};
EXPECT_TRUE(Conf.Diagnostics.Includes.IgnoreHeader.empty())
Index: clang-tools-extra/clangd/Preamble.cpp
===================================================================
--- clang-tools-extra/clangd/Preamble.cpp
+++ clang-tools-extra/clangd/Preamble.cpp
@@ -123,7 +123,7 @@
SourceMgr = &CI.getSourceManager();
Includes.collect(CI);
if (Config::current().Diagnostics.UnusedIncludes ==
- Config::UnusedIncludesPolicy::Experiment)
+ Config::IncludesPolicy::Experiment)
Pragmas.record(CI);
if (BeforeExecuteCallback)
BeforeExecuteCallback(CI);
Index: clang-tools-extra/clangd/ParsedAST.cpp
===================================================================
--- clang-tools-extra/clangd/ParsedAST.cpp
+++ clang-tools-extra/clangd/ParsedAST.cpp
@@ -688,13 +688,9 @@
std::move(Macros), std::move(Marks), std::move(ParsedDecls),
std::move(Diags), std::move(Includes),
std::move(CanonIncludes));
- if (Result.Diags) {
- auto UnusedHeadersDiags =
- issueUnusedIncludesDiagnostics(Result, Inputs.Contents);
- Result.Diags->insert(Result.Diags->end(),
- make_move_iterator(UnusedHeadersDiags.begin()),
- make_move_iterator(UnusedHeadersDiags.end()));
- }
+ if (Result.Diags)
+ llvm::move(issueIncludeCleanerDiagnostics(Result, Inputs.Contents),
+ std::back_inserter(*Result.Diags));
return std::move(Result);
}
Index: clang-tools-extra/clangd/IncludeCleaner.h
===================================================================
--- clang-tools-extra/clangd/IncludeCleaner.h
+++ clang-tools-extra/clangd/IncludeCleaner.h
@@ -20,18 +20,39 @@
#include "Headers.h"
#include "ParsedAST.h"
+#include "clang-include-cleaner/Types.h"
#include "index/CanonicalIncludes.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Tooling/Inclusions/StandardLibrary.h"
+#include "clang/Tooling/Syntax/Tokens.h"
#include "llvm/ADT/DenseSet.h"
#include "llvm/ADT/STLFunctionalExtras.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringSet.h"
#include <optional>
+#include <string>
#include <vector>
namespace clang {
namespace clangd {
+// Data needed for missing include diagnostics.
+struct MissingIncludeDiagInfo {
+ std::string SymbolName;
+ syntax::FileRange SymRefRange;
+ llvm::SmallVector<include_cleaner::Header> Providers;
+
+ bool operator==(const MissingIncludeDiagInfo &Other) const {
+ return SymbolName == Other.SymbolName && SymRefRange == Other.SymRefRange &&
+ Providers == Other.Providers;
+ }
+};
+
+struct IncludeCleanerFindings {
+ std::vector<const Inclusion *> UnusedIncludes;
+ llvm::SmallVector<MissingIncludeDiagInfo> MissingIncludes;
+};
+
struct ReferencedLocations {
llvm::DenseSet<SourceLocation> User;
llvm::DenseSet<tooling::stdlib::Symbol> Stdlib;
@@ -96,13 +117,10 @@
const llvm::DenseSet<IncludeStructure::HeaderID> &ReferencedFiles,
const llvm::StringSet<> &ReferencedPublicHeaders);
+IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST);
std::vector<const Inclusion *> computeUnusedIncludes(ParsedAST &AST);
-// The same as computeUnusedIncludes, but it is an experimental and
-// include-cleaner-lib-based implementation.
-std::vector<const Inclusion *>
-computeUnusedIncludesExperimental(ParsedAST &AST);
-std::vector<Diag> issueUnusedIncludesDiagnostics(ParsedAST &AST,
+std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST,
llvm::StringRef Code);
/// Affects whether standard library includes should be considered for
Index: clang-tools-extra/clangd/IncludeCleaner.cpp
===================================================================
--- clang-tools-extra/clangd/IncludeCleaner.cpp
+++ clang-tools-extra/clangd/IncludeCleaner.cpp
@@ -8,14 +8,29 @@
#include "IncludeCleaner.h"
#include "Config.h"
+#include "Diagnostics.h"
#include "Headers.h"
#include "ParsedAST.h"
#include "Protocol.h"
#include "SourceCode.h"
+#include "URI.h"
#include "clang-include-cleaner/Analysis.h"
#include "clang-include-cleaner/Types.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/TemplateName.h"
+#include "clang/AST/Type.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Tooling/Core/Replacement.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Tooling/Inclusions/HeaderIncludes.h"
+#include "clang/Tooling/Inclusions/StandardLibrary.h"
#include "index/CanonicalIncludes.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/Support/Casting.h"
#include "support/Logger.h"
+#include "support/Path.h"
#include "support/Trace.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ExprCXX.h"
@@ -28,12 +43,16 @@
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringSet.h"
+#include "llvm/Support/Error.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Regex.h"
-#include <functional>
+#include <iterator>
+#include <string>
#include <optional>
+#include <vector>
namespace clang {
namespace clangd {
@@ -85,7 +104,7 @@
// Using templateName case is handled by the override TraverseTemplateName.
if (TST->getTemplateName().getKind() == TemplateName::UsingTemplate)
return true;
- add(TST->getAsCXXRecordDecl()); // Specialization
+ add(TST->getAsCXXRecordDecl()); // Specialization
return true;
}
@@ -325,6 +344,42 @@
return ID;
}
+include_cleaner::Includes
+convertIncludes(const SourceManager &SM,
+ const llvm::ArrayRef<Inclusion> MainFileIncludes) {
+ include_cleaner::Includes Includes;
+ for (const Inclusion &Inc : MainFileIncludes) {
+ include_cleaner::Include TransformedInc;
+ llvm::StringRef WrittenRef = llvm::StringRef(Inc.Written);
+ TransformedInc.Spelled = WrittenRef.trim("\"<>");
+ TransformedInc.HashLocation =
+ SM.getComposedLoc(SM.getMainFileID(), Inc.HashOffset);
+ TransformedInc.Line = Inc.HashLine + 1;
+ TransformedInc.Angled = WrittenRef.starts_with("<");
+ auto FE = SM.getFileManager().getFile(Inc.Resolved);
+ if (!FE)
+ continue;
+ TransformedInc.Resolved = *FE;
+ Includes.add(std::move(TransformedInc));
+ }
+ return Includes;
+}
+
+std::string spellHeader(ParsedAST &AST, const FileEntry *MainFile,
+ include_cleaner::Header Provider) {
+ std::string SpelledHeader;
+ if (Provider.kind() == include_cleaner::Header::Physical) {
+ if (auto CanonicalPath =
+ getCanonicalPath(Provider.physical(), AST.getSourceManager())) {
+ SpelledHeader =
+ llvm::cantFail(URI::includeSpelling(URI::create(*CanonicalPath)));
+ if (!SpelledHeader.empty())
+ return SpelledHeader;
+ }
+ }
+ return include_cleaner::spellHeader(
+ Provider, AST.getPreprocessor().getHeaderSearchInfo(), MainFile);
+}
} // namespace
ReferencedLocations findReferencedLocations(ASTContext &Ctx, Preprocessor &PP,
@@ -460,6 +515,27 @@
return TranslatedHeaderIDs;
}
+std::vector<include_cleaner::SymbolReference>
+collectMacroReferences(ParsedAST &AST) {
+ const auto &SM = AST.getSourceManager();
+ // FIXME: !!this is a hacky way to collect macro references.
+ std::vector<include_cleaner::SymbolReference> Macros;
+ auto &PP = AST.getPreprocessor();
+ for (const syntax::Token &Tok :
+ AST.getTokens().spelledTokens(SM.getMainFileID())) {
+ auto Macro = locateMacroAt(Tok, PP);
+ if (!Macro)
+ continue;
+ if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid())
+ Macros.push_back(
+ {Tok.location(),
+ include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)),
+ DefLoc},
+ include_cleaner::RefType::Explicit});
+ }
+ return Macros;
+}
+
// This is the original clangd-own implementation for computing unused
// #includes. Eventually it will be deprecated and replaced by the
// include-cleaner-lib-based implementation.
@@ -474,79 +550,52 @@
translateToHeaderIDs(ReferencedFiles, AST.getIncludeStructure(), SM);
return getUnused(AST, ReferencedHeaders, ReferencedFiles.SpelledUmbrellas);
}
-std::vector<const Inclusion *> computeUnusedIncludesExperimental(ParsedAST &AST) {
- const auto &SM = AST.getSourceManager();
- const auto &Includes = AST.getIncludeStructure();
- // FIXME: this map should probably be in IncludeStructure.
- llvm::StringMap<llvm::SmallVector<IncludeStructure::HeaderID>> BySpelling;
- for (const auto &Inc : Includes.MainFileIncludes) {
- if (Inc.HeaderID)
- BySpelling.try_emplace(Inc.Written)
- .first->second.push_back(
- static_cast<IncludeStructure::HeaderID>(*Inc.HeaderID));
- }
- // FIXME: !!this is a hacky way to collect macro references.
- std::vector<include_cleaner::SymbolReference> Macros;
- auto& PP = AST.getPreprocessor();
- for (const syntax::Token &Tok :
- AST.getTokens().spelledTokens(SM.getMainFileID())) {
- auto Macro = locateMacroAt(Tok, PP);
- if (!Macro)
+
+std::vector<Diag> generateMissingIncludeDiagnostics(
+ ParsedAST &AST, llvm::SmallVector<MissingIncludeDiagInfo> MissingIncludes,
+ llvm::StringRef Code) {
+ std::vector<Diag> Result;
+ tooling::HeaderIncludes HeaderIncludes(AST.tuPath(), Code,
+ tooling::IncludeStyle{});
+ const SourceManager &SM = AST.getSourceManager();
+ const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
+ for (const auto &SymbolWithMissingInclude : MissingIncludes) {
+ std::string Spelling =
+ spellHeader(AST, MainFile, SymbolWithMissingInclude.Providers.front());
+ Diag D;
+ D.Message =
+ llvm::formatv("No header providing \"{0}\" is directly included",
+ SymbolWithMissingInclude.SymbolName);
+ D.Name = "missing-includes";
+ D.Source = Diag::DiagSource::Clangd;
+ D.File = AST.tuPath();
+ D.Severity = DiagnosticsEngine::Warning;
+ D.Range = clangd::Range{
+ offsetToPosition(Code,
+ SymbolWithMissingInclude.SymRefRange.beginOffset()),
+ offsetToPosition(Code,
+ SymbolWithMissingInclude.SymRefRange.endOffset())};
+ llvm::StringRef HeaderRef{Spelling};
+ bool Angled = HeaderRef.starts_with("<");
+ std::optional<tooling::Replacement> Replacement = HeaderIncludes.insert(
+ HeaderRef.trim("\"<>"), Angled, tooling::IncludeDirective::Include);
+ if (!Replacement.has_value())
continue;
- if (auto DefLoc = Macro->Info->getDefinitionLoc(); DefLoc.isValid())
- Macros.push_back(
- {Tok.location(),
- include_cleaner::Macro{/*Name=*/PP.getIdentifierInfo(Tok.text(SM)),
- DefLoc},
- include_cleaner::RefType::Explicit});
- }
- llvm::DenseSet<IncludeStructure::HeaderID> Used;
- include_cleaner::walkUsed(
- AST.getLocalTopLevelDecls(), /*MacroRefs=*/Macros,
- AST.getPragmaIncludes(), SM,
- [&](const include_cleaner::SymbolReference &Ref,
- llvm::ArrayRef<include_cleaner::Header> Providers) {
- for (const auto &H : Providers) {
- switch (H.kind()) {
- case include_cleaner::Header::Physical:
- if (auto HeaderID = Includes.getID(H.physical()))
- Used.insert(*HeaderID);
- break;
- case include_cleaner::Header::Standard:
- for (auto HeaderID : Includes.StdlibHeaders.lookup(H.standard()))
- Used.insert(HeaderID);
- break;
- case include_cleaner::Header::Verbatim:
- for (auto HeaderID : BySpelling.lookup(H.verbatim()))
- Used.insert(HeaderID);
- break;
- }
- }
- });
- return getUnused(AST, Used, /*ReferencedPublicHeaders*/{});
+ D.Fixes.emplace_back();
+ D.Fixes.back().Message = "#include " + Spelling;
+ TextEdit Edit = replacementToEdit(Code, *Replacement);
+ D.Fixes.back().Edits.emplace_back(std::move(Edit));
+ D.InsideMainFile = true;
+ Result.push_back(D);
+ }
+ return Result;
}
-std::vector<Diag> issueUnusedIncludesDiagnostics(ParsedAST &AST,
- llvm::StringRef Code) {
- const Config &Cfg = Config::current();
- if (Cfg.Diagnostics.UnusedIncludes == Config::UnusedIncludesPolicy::None ||
- Cfg.Diagnostics.SuppressAll ||
- Cfg.Diagnostics.Suppress.contains("unused-includes"))
- return {};
- // Interaction is only polished for C/CPP.
- if (AST.getLangOpts().ObjC)
- return {};
- trace::Span Tracer("IncludeCleaner::issueUnusedIncludesDiagnostics");
+std::vector<Diag>
+generateUnusedIncludeDiagnostics(PathRef FileName,
+ std::vector<const Inclusion *> UnusedIncludes,
+ llvm::StringRef Code) {
std::vector<Diag> Result;
- std::string FileName =
- AST.getSourceManager()
- .getFileEntryRefForID(AST.getSourceManager().getMainFileID())
- ->getName()
- .str();
- const auto &UnusedIncludes =
- Cfg.Diagnostics.UnusedIncludes == Config::UnusedIncludesPolicy::Experiment
- ? computeUnusedIncludesExperimental(AST)
- : computeUnusedIncludes(AST);
for (const auto *Inc : UnusedIncludes) {
Diag D;
D.Message =
@@ -571,8 +620,124 @@
D.Fixes.back().Edits.back().range.start.line = Inc->HashLine;
D.Fixes.back().Edits.back().range.end.line = Inc->HashLine + 1;
D.InsideMainFile = true;
- Result.push_back(std::move(D));
+ Result.push_back(D);
+ }
+ return Result;
+}
+
+IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST) {
+ const auto &SM = AST.getSourceManager();
+ const auto &Includes = AST.getIncludeStructure();
+ include_cleaner::Includes ConvertedIncludes =
+ convertIncludes(AST.getSourceManager(), Includes.MainFileIncludes);
+ const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID());
+
+ std::vector<include_cleaner::SymbolReference> Macros =
+ collectMacroReferences(AST);
+ llvm::SmallVector<MissingIncludeDiagInfo> MissingIncludes;
+ llvm::DenseSet<IncludeStructure::HeaderID> Used;
+ include_cleaner::walkUsed(
+ AST.getLocalTopLevelDecls(), /*MacroRefs=*/Macros,
+ AST.getPragmaIncludes(), SM,
+ [&](const include_cleaner::SymbolReference &Ref,
+ llvm::ArrayRef<include_cleaner::Header> Providers) {
+ bool Satisfied = false;
+ for (const auto &H : Providers) {
+ if (H.kind() == include_cleaner::Header::Physical &&
+ H.physical() == MainFile) {
+ Satisfied = true;
+ continue;
+ }
+ for (auto *&Inc : ConvertedIncludes.match(H)) {
+ Satisfied = true;
+ auto HeaderID = Includes.getID(Inc->Resolved);
+ assert(HeaderID.has_value() &&
+ "ConvertedIncludes only contains resolved includes.");
+ Used.insert(*HeaderID);
+ }
+ }
+
+ const syntax::TokenBuffer &Tokens = AST.getTokens();
+ if (!Satisfied && !Providers.empty() &&
+ Ref.RT == include_cleaner::RefType::Explicit) {
+ if (auto SpelledForExpanded = Tokens.spelledForExpanded(
+ Tokens.expandedTokens(Ref.RefLocation))) {
+ syntax::FileRange Range = syntax::Token::range(
+ AST.getSourceManager(), (*SpelledForExpanded)[0],
+ (*SpelledForExpanded)[(*SpelledForExpanded).size() - 1]);
+ llvm::SmallVector<include_cleaner::Header> ProviderHeaders;
+ for (const auto H : Providers) {
+ ProviderHeaders.push_back(H);
+ }
+
+ std::string SymbolName;
+ switch (Ref.Target.kind()) {
+ case include_cleaner::Symbol::Macro:
+ SymbolName = Ref.Target.macro().Name->getName();
+ break;
+ case include_cleaner::Symbol::Declaration:
+ SymbolName =
+ static_cast<const NamedDecl &>(Ref.Target.declaration())
+ .getQualifiedNameAsString();
+ break;
+ }
+ MissingIncludeDiagInfo DiagInfo{SymbolName, Range, ProviderHeaders};
+ MissingIncludes.push_back(std::move(DiagInfo));
+ }
+ }
+ });
+ std::vector<const Inclusion *> UnusedIncludes =
+ getUnused(AST, Used, /*ReferencedPublicHeaders*/ {});
+ return {UnusedIncludes, MissingIncludes};
+}
+
+std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST,
+ llvm::StringRef Code) {
+ // Interaction is only polished for C/CPP.
+ if (AST.getLangOpts().ObjC)
+ return {};
+ const Config &Cfg = Config::current();
+ if (Cfg.Diagnostics.SuppressAll ||
+ (Cfg.Diagnostics.Suppress.contains("unused-includes") &&
+ Cfg.Diagnostics.Suppress.contains("missing-includes"))) {
+ return {};
+ }
+
+ trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics");
+
+ std::vector<const Inclusion *> UnusedIncludes;
+ llvm::SmallVector<MissingIncludeDiagInfo> MissingIncludes;
+
+ IncludeCleanerFindings Findings;
+ if (Cfg.Diagnostics.MissingIncludes == Config::IncludesPolicy::Strict ||
+ Cfg.Diagnostics.UnusedIncludes == Config::IncludesPolicy::Experiment) {
+ // will need include-cleaner results, call it once
+ Findings = computeIncludeCleanerFindings(AST);
}
+
+ if (!Cfg.Diagnostics.Suppress.contains("unused-includes")) {
+ switch (Cfg.Diagnostics.UnusedIncludes) {
+ case Config::IncludesPolicy::Experiment:
+ UnusedIncludes = std::move(Findings.UnusedIncludes);
+ break;
+ case Config::IncludesPolicy::Strict:
+ UnusedIncludes = computeUnusedIncludes(AST);
+ break;
+ case Config::IncludesPolicy::None:
+ // do nothing
+ break;
+ }
+ }
+
+ if (Cfg.Diagnostics.MissingIncludes == Config::IncludesPolicy::Strict &&
+ !Cfg.Diagnostics.Suppress.contains("missing-includes")) {
+ MissingIncludes = std::move(Findings.MissingIncludes);
+ }
+
+ std::vector<Diag> Result =
+ generateUnusedIncludeDiagnostics(AST.tuPath(), UnusedIncludes, Code);
+ llvm::move(generateMissingIncludeDiagnostics(AST, MissingIncludes, Code),
+ std::back_inserter(Result));
return Result;
}
Index: clang-tools-extra/clangd/ConfigYAML.cpp
===================================================================
--- clang-tools-extra/clangd/ConfigYAML.cpp
+++ clang-tools-extra/clangd/ConfigYAML.cpp
@@ -128,6 +128,9 @@
Dict.handle("UnusedIncludes", [&](Node &N) {
F.UnusedIncludes = scalarValue(N, "UnusedIncludes");
});
+ Dict.handle("MissingIncludes", [&](Node &N) {
+ F.MissingIncludes = scalarValue(N, "MissingIncludes");
+ });
Dict.handle("Includes", [&](Node &N) { parse(F.Includes, N); });
Dict.handle("ClangTidy", [&](Node &N) { parse(F.ClangTidy, N); });
Dict.parse(N);
Index: clang-tools-extra/clangd/ConfigFragment.h
===================================================================
--- clang-tools-extra/clangd/ConfigFragment.h
+++ clang-tools-extra/clangd/ConfigFragment.h
@@ -221,7 +221,7 @@
/// This often has other advantages, such as skipping some analysis.
std::vector<Located<std::string>> Suppress;
- /// Controls how clangd will correct "unnecessary #include directives.
+ /// Controls how clangd will correct "unnecessary" #include directives.
/// clangd can warn if a header is `#include`d but not used, and suggest
/// removing it.
//
@@ -235,6 +235,19 @@
/// - None
std::optional<Located<std::string>> UnusedIncludes;
+ /// Controls if clangd should analyze missing #include directives.
+ /// clangd will warn if no header providing a symbol is `#include`d
+ /// (missing) directly, and suggest adding it.
+ ///
+ /// Strict means a header providing a symbol is missing if it is not
+ /// *directly #include'd. The file might still compile if the header is
+ /// included transitively.
+ ///
+ /// Valid values are:
+ /// - Strict
+ /// - None
+ std::optional<Located<std::string>> MissingIncludes;
+
/// Controls IncludeCleaner diagnostics.
struct IncludesBlock {
/// Regexes that will be used to avoid diagnosing certain includes as
Index: clang-tools-extra/clangd/ConfigCompile.cpp
===================================================================
--- clang-tools-extra/clangd/ConfigCompile.cpp
+++ clang-tools-extra/clangd/ConfigCompile.cpp
@@ -432,15 +432,27 @@
if (F.UnusedIncludes)
if (auto Val =
- compileEnum<Config::UnusedIncludesPolicy>("UnusedIncludes",
+ compileEnum<Config::IncludesPolicy>("UnusedIncludes",
**F.UnusedIncludes)
- .map("Strict", Config::UnusedIncludesPolicy::Strict)
- .map("Experiment", Config::UnusedIncludesPolicy::Experiment)
- .map("None", Config::UnusedIncludesPolicy::None)
+ .map("Strict", Config::IncludesPolicy::Strict)
+ .map("Experiment", Config::IncludesPolicy::Experiment)
+ .map("None", Config::IncludesPolicy::None)
.value())
Out.Apply.push_back([Val](const Params &, Config &C) {
C.Diagnostics.UnusedIncludes = *Val;
});
+
+ if (F.MissingIncludes)
+ if (auto Val =
+ compileEnum<Config::IncludesPolicy>("MissingIncludes",
+ **F.MissingIncludes)
+ .map("Strict", Config::IncludesPolicy::Strict)
+ .map("None", Config::IncludesPolicy::None)
+ .value())
+ Out.Apply.push_back([Val](const Params &, Config &C) {
+ C.Diagnostics.MissingIncludes = *Val;
+ });
+
compile(std::move(F.Includes));
compile(std::move(F.ClangTidy));
Index: clang-tools-extra/clangd/Config.h
===================================================================
--- clang-tools-extra/clangd/Config.h
+++ clang-tools-extra/clangd/Config.h
@@ -88,11 +88,12 @@
bool StandardLibrary = true;
} Index;
- enum UnusedIncludesPolicy {
- /// Diagnose unused includes.
+ enum IncludesPolicy {
+ /// Diagnose missing and unused includes.
Strict,
None,
- /// The same as Strict, but using the include-cleaner library.
+ /// The same as Strict, but using the include-cleaner library for
+ /// unused includes.
Experiment,
};
/// Controls warnings and errors when parsing code.
@@ -107,7 +108,8 @@
llvm::StringMap<std::string> CheckOptions;
} ClangTidy;
- UnusedIncludesPolicy UnusedIncludes = None;
+ IncludesPolicy UnusedIncludes = None;
+ IncludesPolicy MissingIncludes = None;
/// IncludeCleaner will not diagnose usages of these headers matched by
/// these regexes.
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits