hokein created this revision.
hokein added a reviewer: kadircet.
Herald added subscribers: arphaman, mgrang.
Herald added a project: All.
hokein requested review of this revision.
Herald added subscribers: MaskRay, ilya-biryukov.
Herald added a project: clang-tools-extra.

For each unused-include/missing-include diagnostic, we provide fix-all
alternative to them.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D147684

Files:
  clang-tools-extra/clangd/IncludeCleaner.cpp
  clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
  clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp

Index: clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
+++ clang-tools-extra/clangd/unittests/IncludeCleanerTests.cpp
@@ -44,8 +44,8 @@
 using ::testing::Pointee;
 using ::testing::UnorderedElementsAre;
 
-Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher) {
-  return Field(&Diag::Fixes, ElementsAre(FixMatcher));
+Matcher<const Diag &> withFix(std::vector<::testing::Matcher<Fix>> FixMatcheres) {
+  return Field(&Diag::Fixes, testing::UnorderedElementsAreArray(FixMatcheres));
 }
 
 MATCHER_P2(Diag, Range, Message,
@@ -59,6 +59,8 @@
   return arg.Message == Message && arg.Edits.size() == 1 &&
          arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
 }
+MATCHER_P(FixMessage, Message, "") { return arg.Message == Message; }
+
 
 std::string guard(llvm::StringRef Code) {
   return "#pragma once\n" + Code.str();
@@ -254,42 +256,69 @@
       UnorderedElementsAre(
           AllOf(Diag(MainFile.range("b"),
                      "No header providing \"b\" is directly included"),
-                withFix(Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
-                            "#include \"b.h\""))),
+                withFix({Fix(MainFile.range("insert_b"), "#include \"b.h\"\n",
+                             "#include \"b.h\""),
+                         FixMessage("add all missing #includes"),
+                         FixMessage("add all missing #includes and remove all "
+                                    "unused ones")})),
           AllOf(Diag(MainFile.range("bar"),
                      "No header providing \"ns::Bar\" is directly included"),
-                withFix(Fix(MainFile.range("insert_d"),
-                            "#include \"dir/d.h\"\n", "#include \"dir/d.h\""))),
+                withFix({Fix(MainFile.range("insert_d"),
+                             "#include \"dir/d.h\"\n", "#include \"dir/d.h\""),
+                         FixMessage("add all missing #includes"),
+                         FixMessage("add all missing #includes and remove all "
+                                    "unused ones")})),
           AllOf(Diag(MainFile.range("f"),
                      "No header providing \"f\" is directly included"),
-                withFix(Fix(MainFile.range("insert_f"), "#include <f.h>\n",
-                            "#include <f.h>"))),
+                withFix({Fix(MainFile.range("insert_f"), "#include <f.h>\n",
+                             "#include <f.h>"),
+                         FixMessage("add all missing #includes"),
+                         FixMessage("add all missing #includes and remove all "
+                                    "unused ones")})),
           AllOf(
               Diag(MainFile.range("foobar"),
                    "No header providing \"foobar\" is directly included"),
-              withFix(Fix(MainFile.range("insert_foobar"),
-                          "#include \"public.h\"\n", "#include \"public.h\""))),
+              withFix({Fix(MainFile.range("insert_foobar"),
+                           "#include \"public.h\"\n", "#include \"public.h\""),
+                       FixMessage("add all missing #includes"),
+                       FixMessage("add all missing #includes and remove all "
+                                  "unused ones")})),
           AllOf(
               Diag(MainFile.range("vector"),
                    "No header providing \"std::vector\" is directly included"),
-              withFix(Fix(MainFile.range("insert_vector"),
-                          "#include <vector>\n", "#include <vector>"))),
+              withFix({Fix(MainFile.range("insert_vector"),
+                           "#include <vector>\n", "#include <vector>"),
+                       FixMessage("add all missing #includes"),
+                       FixMessage("add all missing #includes and remove all "
+                                  "unused ones")})),
           AllOf(Diag(MainFile.range("FOO"),
                      "No header providing \"FOO\" is directly included"),
-                withFix(Fix(MainFile.range("insert_foo"),
-                            "#include \"foo.h\"\n", "#include \"foo.h\""))),
+                withFix({Fix(MainFile.range("insert_foo"),
+                             "#include \"foo.h\"\n", "#include \"foo.h\""),
+                         FixMessage("add all missing #includes"),
+                         FixMessage("add all missing #includes and remove all "
+                                    "unused ones")})),
           AllOf(Diag(MainFile.range("DEF"),
                      "No header providing \"Foo\" is directly included"),
-                withFix(Fix(MainFile.range("insert_foo"),
-                            "#include \"foo.h\"\n", "#include \"foo.h\""))),
+                withFix({Fix(MainFile.range("insert_foo"),
+                             "#include \"foo.h\"\n", "#include \"foo.h\""),
+                         FixMessage("add all missing #includes"),
+                         FixMessage("add all missing #includes and remove all "
+                                    "unused ones")})),
           AllOf(Diag(MainFile.range("BAR"),
                      "No header providing \"BAR\" is directly included"),
-                withFix(Fix(MainFile.range("insert_foo"),
-                            "#include \"foo.h\"\n", "#include \"foo.h\""))),
+                withFix({Fix(MainFile.range("insert_foo"),
+                             "#include \"foo.h\"\n", "#include \"foo.h\""),
+                         FixMessage("add all missing #includes"),
+                         FixMessage("add all missing #includes and remove all "
+                                    "unused ones")})),
           AllOf(Diag(MainFile.range("Foo"),
                      "No header providing \"Foo\" is directly included"),
-                withFix(Fix(MainFile.range("insert_foo"),
-                            "#include \"foo.h\"\n", "#include \"foo.h\"")))));
+                withFix({Fix(MainFile.range("insert_foo"),
+                             "#include \"foo.h\"\n", "#include \"foo.h\""),
+                         FixMessage("add all missing #includes"),
+                         FixMessage("add all missing #includes and remove all "
+                                    "unused ones")}))));
 }
 
 TEST(IncludeCleaner, IWYUPragmas) {
Index: clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
===================================================================
--- clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
+++ clang-tools-extra/clangd/unittests/DiagnosticsTests.cpp
@@ -54,6 +54,11 @@
                                          ::testing::Matcher<Fix> FixMatcher2) {
   return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2));
 }
+::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher1,
+                                         ::testing::Matcher<Fix> FixMatcher2,
+                                         ::testing::Matcher<Fix> FixMatcher3) {
+  return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2, FixMatcher3));
+}
 
 ::testing::Matcher<const Diag &> withID(unsigned ID) {
   return Field(&Diag::ID, ID);
@@ -1908,11 +1913,14 @@
   auto AST = TU.build();
   EXPECT_THAT(
       *AST.getDiagnostics(),
-      UnorderedElementsAre(AllOf(
-          Diag(Test.range("diag"),
-               "included header unused.h is not used directly"),
-          withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd),
-          withFix(Fix(Test.range("fix"), "", "remove #include directive")))));
+      UnorderedElementsAre(
+          AllOf(Diag(Test.range("diag"),
+                     "included header unused.h is not used directly"),
+                withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd),
+                withFix(Fix(Test.range("fix"), "", "remove #include directive"),
+                        fixMessage("remove all unused #includes"),
+                        fixMessage("add all missing #includes and remove all "
+                                   "unused ones")))));
   auto &Diag = AST.getDiagnostics()->front();
   EXPECT_EQ(getDiagnosticDocURI(Diag.Source, Diag.ID, Diag.Name),
             std::string("https://clangd.llvm.org/guides/include-cleaner";));
Index: clang-tools-extra/clangd/IncludeCleaner.cpp
===================================================================
--- clang-tools-extra/clangd/IncludeCleaner.cpp
+++ clang-tools-extra/clangd/IncludeCleaner.cpp
@@ -51,10 +51,12 @@
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/Regex.h"
+#include <algorithm>
 #include <cassert>
 #include <iterator>
 #include <optional>
 #include <string>
+#include <tuple>
 #include <utility>
 #include <vector>
 
@@ -413,6 +415,69 @@
   return {std::move(UnusedIncludes), std::move(MissingIncludes)};
 }
 
+Fix removeAllUnusedIncludes(llvm::ArrayRef<const Inclusion *> UnusedIncludes) {
+  Fix RemoveAll;
+  RemoveAll.Message = "remove all unused #includes";
+  for (const auto *Inc : UnusedIncludes) {
+    RemoveAll.Edits.emplace_back();
+    RemoveAll.Edits.back().range.start.line = Inc->HashLine;
+    RemoveAll.Edits.back().range.end.line = Inc->HashLine + 1;
+  }
+  return RemoveAll;
+}
+Fix addAllMissingIncludes(llvm::ArrayRef<Diag> MissingIncludeDiags) {
+  Fix AddAllMissing;
+  AddAllMissing.Message = "add all missing #includes";
+
+  for (const auto &Diag : MissingIncludeDiags) {
+    assert(Diag.Fixes.size() == 1 && "Expected exactly one fix.");
+    AddAllMissing.Edits.insert(AddAllMissing.Edits.end(),
+                               Diag.Fixes.front().Edits.begin(),
+                               Diag.Fixes.front().Edits.end());
+  }
+  llvm::sort(AddAllMissing.Edits, [](const TextEdit &L, const TextEdit &R) {
+    return std::tie(L.range.start, L.range.end, L.newText) <
+           std::tie(R.range.start, R.range.end, R.newText);
+  });
+  AddAllMissing.Edits.erase(
+      std::unique(AddAllMissing.Edits.begin(), AddAllMissing.Edits.end()),
+      AddAllMissing.Edits.end());
+  return AddAllMissing;
+}
+
+std::vector<Diag> generateIncludeCleanerDiagnostic(
+    ParsedAST &AST, const IncludeCleanerFindings &Findings,
+    llvm::StringRef Code) {
+  Fix RemoveAllUnused = removeAllUnusedIncludes(Findings.UnusedIncludes);
+
+  std::vector<Diag> MissingIncludeDiags = generateMissingIncludeDiagnostics(
+      AST, Findings.MissingIncludes, Code);
+  Fix AddAllMissing = addAllMissingIncludes(MissingIncludeDiags);
+
+  Fix FixAll;
+  FixAll.Message = "add all missing #includes and remove all unused ones";
+  FixAll.Edits = RemoveAllUnused.Edits;
+  // Append the missing include edits.
+  FixAll.Edits.insert(FixAll.Edits.end(), AddAllMissing.Edits.begin(),
+                      AddAllMissing.Edits.end());
+
+  for (auto &Diag : MissingIncludeDiags) {
+    Diag.Fixes.push_back(AddAllMissing);
+    Diag.Fixes.push_back(FixAll);
+  }
+  std::vector<Diag> UnusedIncludes = generateUnusedIncludeDiagnostics(
+      AST.tuPath(), Findings.UnusedIncludes, Code);
+  for (auto &Diag : UnusedIncludes) {
+    Diag.Fixes.push_back(RemoveAllUnused);
+    Diag.Fixes.push_back(FixAll);
+  }
+
+  auto Result = std::move(MissingIncludeDiags);
+  llvm::move(UnusedIncludes,
+             std::back_inserter(Result));
+  return Result;
+}
+
 std::vector<Diag> issueIncludeCleanerDiagnostics(ParsedAST &AST,
                                                  llvm::StringRef Code) {
   // Interaction is only polished for C/CPP.
@@ -428,13 +493,7 @@
     // will need include-cleaner results, call it once
     Findings = computeIncludeCleanerFindings(AST);
   }
-
-  std::vector<Diag> Result = generateUnusedIncludeDiagnostics(
-      AST.tuPath(), Findings.UnusedIncludes, Code);
-  llvm::move(
-      generateMissingIncludeDiagnostics(AST, Findings.MissingIncludes, Code),
-      std::back_inserter(Result));
-  return Result;
+  return generateIncludeCleanerDiagnostic(AST, Findings, Code);
 }
 
 } // namespace clangd
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to