diff --git a/clang-tools-extra/clangd/IncludeCleaner.cpp b/clang-tools-extra/clangd/IncludeCleaner.cpp index dc4c8fc498b1f..382ea3ffe342b 100644 --- a/clang-tools-extra/clangd/IncludeCleaner.cpp +++ b/clang-tools-extra/clangd/IncludeCleaner.cpp @@ -117,7 +117,9 @@ bool mayConsiderUnused(const Inclusion &Inc, ParsedAST &AST, std::vector generateMissingIncludeDiagnostics( ParsedAST &AST, llvm::ArrayRef MissingIncludes, - llvm::StringRef Code, HeaderFilter IgnoreHeaders, const ThreadsafeFS &TFS) { + llvm::StringRef Code, HeaderFilter IgnoreHeaders, + HeaderFilter AngledHeaders, HeaderFilter QuotedHeaders, + const ThreadsafeFS &TFS) { std::vector Result; const SourceManager &SM = AST.getSourceManager(); const FileEntry *MainFile = SM.getFileEntryForID(SM.getMainFileID()); @@ -141,7 +143,18 @@ std::vector generateMissingIncludeDiagnostics( AST.getPreprocessor().getHeaderSearchInfo(), MainFile}); llvm::StringRef HeaderRef{Spelling}; + bool Angled = HeaderRef.starts_with("<"); + if (SymbolWithMissingInclude.Providers.front().kind() == + include_cleaner::Header::Kind::Physical) { + for (auto &Filter : Angled ? QuotedHeaders : AngledHeaders) { + if (Filter(ResolvedPath)) { + Angled = !Angled; + break; + } + } + } + // We might suggest insertion of an existing include in edge cases, e.g., // include is present in a PP-disabled region, or spelling of the header // turns out to be the same as one of the unresolved includes in the @@ -151,6 +164,11 @@ std::vector generateMissingIncludeDiagnostics( if (!Replacement.has_value()) continue; + if (Angled != (Spelling.front() == '<')) { + Spelling.front() = Angled ? '<' : '"'; + Spelling.back() = Angled ? '>' : '"'; + } + Diag &D = Result.emplace_back(); D.Message = llvm::formatv("No header providing \"{0}\" is directly included", @@ -481,18 +499,19 @@ bool isPreferredProvider(const Inclusion &Inc, return false; // no header provides the symbol } -std::vector -issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code, - const IncludeCleanerFindings &Findings, - const ThreadsafeFS &TFS, - HeaderFilter IgnoreHeaders) { +std::vector issueIncludeCleanerDiagnostics( + ParsedAST &AST, llvm::StringRef Code, + const IncludeCleanerFindings &Findings, const ThreadsafeFS &TFS, + HeaderFilter IgnoreHeaders, HeaderFilter AngledHeaders, + HeaderFilter QuotedHeaders) { trace::Span Tracer("IncludeCleaner::issueIncludeCleanerDiagnostics"); std::vector UnusedIncludes = generateUnusedIncludeDiagnostics( AST.tuPath(), Findings.UnusedIncludes, Code, IgnoreHeaders); std::optional RemoveAllUnused = removeAllUnusedIncludes(UnusedIncludes); std::vector MissingIncludeDiags = generateMissingIncludeDiagnostics( - AST, Findings.MissingIncludes, Code, IgnoreHeaders, TFS); + AST, Findings.MissingIncludes, Code, IgnoreHeaders, AngledHeaders, + QuotedHeaders, TFS); std::optional AddAllMissing = addAllMissingIncludes(MissingIncludeDiags); std::optional FixAll; diff --git a/clang-tools-extra/clangd/IncludeCleaner.h b/clang-tools-extra/clangd/IncludeCleaner.h index 3f6e3b2fd45b6..9439eeed6a3e4 100644 --- a/clang-tools-extra/clangd/IncludeCleaner.h +++ b/clang-tools-extra/clangd/IncludeCleaner.h @@ -57,11 +57,11 @@ IncludeCleanerFindings computeIncludeCleanerFindings(ParsedAST &AST, bool AnalyzeAngledIncludes = false); -std::vector -issueIncludeCleanerDiagnostics(ParsedAST &AST, llvm::StringRef Code, - const IncludeCleanerFindings &Findings, - const ThreadsafeFS &TFS, - HeaderFilter IgnoreHeader = {}); +std::vector issueIncludeCleanerDiagnostics( + ParsedAST &AST, llvm::StringRef Code, + const IncludeCleanerFindings &Findings, const ThreadsafeFS &TFS, + HeaderFilter IgnoreHeader = {}, HeaderFilter AngledHeaders = {}, + HeaderFilter QuotedHeaders = {}); /// Converts the clangd include representation to include-cleaner /// include representation. diff --git a/clang-tools-extra/clangd/ParsedAST.cpp b/clang-tools-extra/clangd/ParsedAST.cpp index 9e1f6bb977226..48896e5f4ff87 100644 --- a/clang-tools-extra/clangd/ParsedAST.cpp +++ b/clang-tools-extra/clangd/ParsedAST.cpp @@ -381,8 +381,9 @@ std::vector getIncludeCleanerDiags(ParsedAST &AST, llvm::StringRef Code, Findings.MissingIncludes.clear(); if (SuppressUnused) Findings.UnusedIncludes.clear(); - return issueIncludeCleanerDiagnostics(AST, Code, Findings, TFS, - Cfg.Diagnostics.Includes.IgnoreHeader); + return issueIncludeCleanerDiagnostics( + AST, Code, Findings, TFS, Cfg.Diagnostics.Includes.IgnoreHeader, + Cfg.Style.AngledHeaders, Cfg.Style.QuotedHeaders); } tidy::ClangTidyCheckFactories diff --git a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp index 0ee748c1ed2d0..ec733cbe9c42d 100644 --- a/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp +++ b/clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp @@ -220,13 +220,16 @@ TEST(IncludeCleaner, ComputeMissingHeaders) { TEST(IncludeCleaner, GenerateMissingHeaderDiags) { Annotations MainFile(R"cpp( #include "a.h" +#include "angled_wrapper.h" #include "all.h" $insert_b[[]]#include "baz.h" #include "dir/c.h" $insert_d[[]]$insert_foo[[]]#include "fuzz.h" #include "header.h" -$insert_foobar[[]]#include -$insert_f[[]]$insert_vector[[]] +$insert_foobar[[]]$insert_quoted[[]]$insert_quoted2[[]]#include "quoted_wrapper.h" +$insert_angled[[]]#include +$insert_f[[]]#include +$insert_vector[[]] #define DEF(X) const Foo *X; #define BAZ(X) const X x @@ -237,6 +240,9 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) { void foo() { $b[[b]](); + $angled[[angled]](); + $quoted[[quoted]](); + $quoted2[[quoted2]](); ns::$bar[[Bar]] bar; bar.d(); @@ -263,12 +269,22 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) { TU.AdditionalFiles["a.h"] = guard("#include \"b.h\""); TU.AdditionalFiles["b.h"] = guard("void b();"); + TU.AdditionalFiles["angled_wrapper.h"] = guard("#include "); + TU.AdditionalFiles["angled.h"] = guard("void angled();"); + TU.ExtraArgs.push_back("-I" + testPath(".")); + + TU.AdditionalFiles["quoted_wrapper.h"] = guard("#include \"quoted.h\""); + TU.AdditionalFiles["quoted.h"] = guard("void quoted();"); + TU.AdditionalFiles["dir/c.h"] = guard("#include \"d.h\""); TU.AdditionalFiles["dir/d.h"] = guard("namespace ns { struct Bar { void d(); }; }"); TU.AdditionalFiles["system/e.h"] = guard("#include "); TU.AdditionalFiles["system/f.h"] = guard("void f();"); + TU.AdditionalFiles["system/quoted2_wrapper.h"] = + guard("#include "); + TU.AdditionalFiles["system/quoted2.h"] = guard("void quoted2();"); TU.ExtraArgs.push_back("-isystem" + testPath("system")); TU.AdditionalFiles["fuzz.h"] = guard("#include \"buzz.h\""); @@ -297,7 +313,15 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) { Findings.UnusedIncludes.clear(); std::vector Diags = issueIncludeCleanerDiagnostics( AST, TU.Code, Findings, MockFS(), - {[](llvm::StringRef Header) { return Header.ends_with("buzz.h"); }}); + /*IgnoreHeaders=*/{[](llvm::StringRef Header) { + return Header.ends_with("buzz.h"); + }}, + /*AngledHeaders=*/{[](llvm::StringRef Header) { + return Header.contains("angled.h"); + }}, + /*QuotedHeaders=*/{[](llvm::StringRef Header) { + return Header.contains("quoted.h") || Header.contains("quoted2.h"); + }}); EXPECT_THAT( Diags, UnorderedElementsAre( @@ -306,6 +330,23 @@ TEST(IncludeCleaner, GenerateMissingHeaderDiags) { withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n", "#include \"b.h\""), FixMessage("add all missing includes")})), + AllOf(Diag(MainFile.range("angled"), + "No header providing \"angled\" is directly included"), + withFix({Fix(MainFile.range("insert_angled"), + "#include \n", "#include "), + FixMessage("add all missing includes")})), + AllOf( + Diag(MainFile.range("quoted"), + "No header providing \"quoted\" is directly included"), + withFix({Fix(MainFile.range("insert_quoted"), + "#include \"quoted.h\"\n", "#include \"quoted.h\""), + FixMessage("add all missing includes")})), + AllOf(Diag(MainFile.range("quoted2"), + "No header providing \"quoted2\" is directly included"), + withFix( + {Fix(MainFile.range("insert_quoted2"), + "#include \"quoted2.h\"\n", "#include \"quoted2.h\""), + FixMessage("add all missing includes")})), AllOf(Diag(MainFile.range("bar"), "No header providing \"ns::Bar\" is directly included"), withFix({Fix(MainFile.range("insert_d"),