kadircet updated this revision to Diff 191831.
kadircet marked 8 inline comments as done.
kadircet added a comment.

- Address comments


Repository:
  rCTE Clang Tools Extra

CHANGES SINCE LAST ACTION
  https://reviews.llvm.org/D59302/new/

https://reviews.llvm.org/D59302

Files:
  clangd/ClangdUnit.cpp
  clangd/Diagnostics.cpp
  clangd/Diagnostics.h
  unittests/clangd/DiagnosticsTests.cpp

Index: unittests/clangd/DiagnosticsTests.cpp
===================================================================
--- unittests/clangd/DiagnosticsTests.cpp
+++ unittests/clangd/DiagnosticsTests.cpp
@@ -9,6 +9,7 @@
 #include "Annotations.h"
 #include "ClangdUnit.h"
 #include "SourceCode.h"
+#include "TestFS.h"
 #include "TestIndex.h"
 #include "TestTU.h"
 #include "index/MemIndex.h"
@@ -73,7 +74,6 @@
   return true;
 }
 
-
 // Helper function to make tests shorter.
 Position pos(int line, int character) {
   Position Res;
@@ -603,7 +603,183 @@
                               "Add include \"x.h\" for symbol a::X")))));
 }
 
+ParsedAST build(const std::string &MainFile,
+                const llvm::StringMap<std::string> &Files) {
+  std::vector<const char *> Cmd = {"clang", MainFile.c_str()};
+  ParseInputs Inputs;
+  Inputs.CompileCommand.Filename = MainFile;
+  Inputs.CompileCommand.CommandLine = {Cmd.begin(), Cmd.end()};
+  Inputs.CompileCommand.Directory = testRoot();
+  Inputs.Contents = Files.lookup(MainFile);
+  Inputs.FS = buildTestFS(Files);
+  Inputs.Opts = ParseOptions();
+  auto PCHs = std::make_shared<PCHContainerOperations>();
+  auto CI = buildCompilerInvocation(Inputs);
+  assert(CI && "Failed to build compilation invocation.");
+  auto Preamble =
+      buildPreamble(MainFile, *CI,
+                    /*OldPreamble=*/nullptr,
+                    /*OldCompileCommand=*/Inputs.CompileCommand, Inputs, PCHs,
+                    /*StoreInMemory=*/true, /*PreambleCallback=*/nullptr);
+  auto AST = buildAST(MainFile, createInvocationFromCommandLine(Cmd), Inputs,
+                      Preamble, PCHs);
+  if (!AST.hasValue()) {
+    ADD_FAILURE() << "Failed to build code:\n" << Files.lookup(MainFile);
+    llvm_unreachable("Failed to build TestTU!");
+  }
+  return std::move(*AST);
+}
+
+TEST(DiagsInHeaders, DiagInsideHeader) {
+  Annotations Main(R"cpp(
+    #include [["a.h"]]
+    void foo() {})cpp");
+  std::string MainFile = testPath("main.cc");
+  llvm::StringMap<std::string> Files = {{MainFile, Main.code()},
+                                        {testPath("a.h"), "no_type_spec;"}};
+  auto FS = buildTestFS(Files);
+
+  EXPECT_THAT(build(MainFile, Files).getDiagnostics(),
+              UnorderedElementsAre(
+                  Diag(Main.range(), "/clangd-test/a.h:1:1: C++ requires a "
+                                     "type specifier for all declarations")));
+}
+
+TEST(DiagsInHeaders, DiagInTransitiveInclude) {
+  Annotations Main(R"cpp(
+    #include [["a.h"]]
+    void foo() {})cpp");
+  std::string MainFile = testPath("main.cc");
+  llvm::StringMap<std::string> Files = {{MainFile, Main.code()},
+                                        {testPath("a.h"), "#include \"b.h\""},
+                                        {testPath("b.h"), "no_type_spec;"}};
+  auto FS = buildTestFS(Files);
+
+  EXPECT_THAT(build(MainFile, Files).getDiagnostics(),
+              UnorderedElementsAre(
+                  Diag(Main.range(),
+                       "In file included from: "
+                       "/clangd-test/a.h:1:10:\n/clangd-test/b.h:1:1: C++ "
+                       "requires a type specifier for all declarations")));
+}
+
+TEST(DiagsInHeaders, DiagInMultipleHeaders) {
+  Annotations Main(R"cpp(
+    #include $a[["a.h"]]
+    #include $b[["b.h"]]
+    void foo() {})cpp");
+  std::string MainFile = testPath("main.cc");
+  llvm::StringMap<std::string> Files = {{MainFile, Main.code()},
+                                        {testPath("a.h"), "no_type_spec;"},
+                                        {testPath("b.h"), "no_type_spec;"}};
+  auto FS = buildTestFS(Files);
+
+  EXPECT_THAT(
+      build(MainFile, Files).getDiagnostics(),
+      UnorderedElementsAre(
+          Diag(Main.range("a"), "/clangd-test/a.h:1:1: C++ requires a type "
+                                "specifier for all declarations"),
+          Diag(Main.range("b"), "/clangd-test/b.h:1:1: C++ requires a type "
+                                "specifier for all declarations")));
+}
+
+TEST(DiagsInHeaders, PreferExpansionLocation) {
+  Annotations Main(R"cpp(
+    #include [["a.h"]]
+    #include "b.h"
+    void foo() {})cpp");
+  std::string MainFile = testPath("main.cc");
+  llvm::StringMap<std::string> Files = {
+      {MainFile, Main.code()},
+      {testPath("a.h"), "#include \"b.h\"\n"},
+      {testPath("b.h"), "#ifndef X\n#define X\nno_type_spec;\n#endif"}};
+  auto FS = buildTestFS(Files);
+
+  EXPECT_THAT(build(MainFile, Files).getDiagnostics(),
+              UnorderedElementsAre(
+                  Diag(Main.range(),
+                       "In file included from: "
+                       "/clangd-test/a.h:1:10:\n/clangd-test/b.h:3:1: C++ "
+                       "requires a type specifier for all declarations")));
+}
+
+TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
+  Annotations Main(R"cpp(
+    #define X
+    #include "a.h"
+    #undef X
+    #include [["b.h"]]
+    void foo() {})cpp");
+  std::string MainFile = testPath("main.cc");
+  llvm::StringMap<std::string> Files = {
+      {MainFile, Main.code()},
+      {testPath("a.h"), "#include \"c.h\"\n"},
+      {testPath("b.h"), "#include \"c.h\"\n"},
+      {testPath("c.h"), "#ifndef X\n#define X\nno_type_spec;\n#endif"}};
+  auto FS = buildTestFS(Files);
+
+  EXPECT_THAT(build(MainFile, Files).getDiagnostics(),
+              UnorderedElementsAre(
+                  Diag(Main.range(),
+                       "In file included from: "
+                       "/clangd-test/b.h:1:10:\n/clangd-test/c.h:3:1: C++ "
+                       "requires a type specifier for all declarations")));
+}
+
+TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
+  Annotations Main(R"cpp(
+    #include [["a.h"]]
+    #include "b.h"
+    void foo() {})cpp");
+  std::string MainFile = testPath("main.cc");
+  llvm::StringMap<std::string> Files = {{MainFile, Main.code()},
+                                        {testPath("a.h"), "#include \"c.h\"\n"},
+                                        {testPath("b.h"), "#include \"c.h\"\n"},
+                                        {testPath("c.h"), R"cpp(
+      #ifndef X
+      #define X
+      no_type_spec_0;
+      no_type_spec_1;
+      no_type_spec_2;
+      no_type_spec_3;
+      no_type_spec_4;
+      no_type_spec_5;
+      no_type_spec_6;
+      no_type_spec_7;
+      no_type_spec_8;
+      no_type_spec_9;
+      no_type_spec_10;
+      #endif)cpp"}};
+  auto FS = buildTestFS(Files);
+
+  EXPECT_THAT(
+      build(MainFile, Files).getDiagnostics(),
+      UnorderedElementsAre(
+          Diag(Main.range(), R"cpp(In file included from: /clangd-test/a.h:1:10:
+/clangd-test/c.h:4:7: C++ requires a type specifier for all declarations
+And 10 more diagnostics)cpp")));
+}
+
+TEST(DiagsInHeaders, OnlyErrorOrFatal) {
+  Annotations Main(R"cpp(
+    #include [["a.h"]]
+    void foo() {})cpp");
+  std::string MainFile = testPath("main.cc");
+  llvm::StringMap<std::string> Files = {{MainFile, Main.code()},
+                                        {testPath("a.h"),
+                                         R"cpp(
+                                           no_type_spec;
+                                           int x = 5/0;
+                                         )cpp"}};
+  auto FS = buildTestFS(Files);
+
+  EXPECT_THAT(
+      build(MainFile, Files).getDiagnostics(),
+      UnorderedElementsAre(Diag(
+          Main.range(),
+          R"(/clangd-test/a.h:2:44: C++ requires a type specifier for all declarations)")));
+}
+
 } // namespace
 } // namespace clangd
 } // namespace clang
-
Index: clangd/Diagnostics.h
===================================================================
--- clangd/Diagnostics.h
+++ clangd/Diagnostics.h
@@ -14,6 +14,8 @@
 #include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/LangOptions.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/None.h"
+#include "llvm/ADT/Optional.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/StringSet.h"
 #include <cassert>
Index: clangd/Diagnostics.cpp
===================================================================
--- clangd/Diagnostics.cpp
+++ clangd/Diagnostics.cpp
@@ -10,30 +10,19 @@
 #include "Compiler.h"
 #include "Logger.h"
 #include "SourceCode.h"
+#include "clang/Basic/Diagnostic.h"
+#include "clang/Basic/SourceLocation.h"
 #include "clang/Basic/SourceManager.h"
 #include "clang/Lex/Lexer.h"
 #include "llvm/Support/Capacity.h"
 #include "llvm/Support/Path.h"
+#include "llvm/Support/Signals.h"
 #include <algorithm>
 
 namespace clang {
 namespace clangd {
 
 namespace {
-
-bool mentionsMainFile(const Diag &D) {
-  if (D.InsideMainFile)
-    return true;
-  // Fixes are always in the main file.
-  if (!D.Fixes.empty())
-    return true;
-  for (auto &N : D.Notes) {
-    if (N.InsideMainFile)
-      return true;
-  }
-  return false;
-}
-
 // Checks whether a location is within a half-open range.
 // Note that clang also uses closed source ranges, which this can't handle!
 bool locationInRange(SourceLocation L, CharSourceRange R,
@@ -379,6 +368,38 @@
     LastDiag = Diag();
     LastDiag->ID = Info.getID();
     FillDiagBase(*LastDiag);
+    auto IncludeLocation = Info.getSourceManager()
+                               .getPresumedLoc(Info.getLocation(),
+                                               /*UseLineDirectives=*/false)
+                               .getIncludeLoc();
+    std::vector<std::string> IncludeStack;
+    while (IncludeLocation.isValid()) {
+      LastDiag->Range.start = LastDiag->Range.end =
+          sourceLocToPosition(Info.getSourceManager(), IncludeLocation);
+      IncludeStack.push_back(
+          IncludeLocation.printToString(Info.getSourceManager()));
+      IncludeLocation = Info.getSourceManager()
+                            .getPresumedLoc(IncludeLocation,
+                                            /*UseLineDirectives=*/false)
+                            .getIncludeLoc();
+    }
+    if (!IncludeStack.empty()) {
+      std::string Message;
+      {
+        llvm::raw_string_ostream OS(Message);
+        // We don't show the main file as part of include stack, which is the
+        // last element.
+        if (IncludeStack.size() > 1) {
+          IncludeStack.pop_back();
+          std::reverse(IncludeStack.begin(), IncludeStack.end());
+          for (llvm::StringRef Inc : IncludeStack)
+            OS << "In file included from: " << Inc << ":\n";
+        }
+        OS << Info.getLocation().printToString(Info.getSourceManager()) << ": "
+           << LastDiag->Message;
+      }
+      LastDiag->Message = std::move(Message);
+    }
 
     if (!Info.getFixItHints().empty())
       AddFix(true /* try to invent a message instead of repeating the diag */);
@@ -413,11 +434,7 @@
 void StoreDiags::flushLastDiag() {
   if (!LastDiag)
     return;
-  if (mentionsMainFile(*LastDiag))
-    Output.push_back(std::move(*LastDiag));
-  else
-    vlog("Dropped diagnostic outside main file: {0}: {1}", LastDiag->File,
-         LastDiag->Message);
+  Output.push_back(std::move(*LastDiag));
   LastDiag.reset();
 }
 
Index: clangd/ClangdUnit.cpp
===================================================================
--- clangd/ClangdUnit.cpp
+++ clangd/ClangdUnit.cpp
@@ -14,11 +14,13 @@
 #include "Headers.h"
 #include "IncludeFixer.h"
 #include "Logger.h"
+#include "Protocol.h"
 #include "SourceCode.h"
 #include "Trace.h"
 #include "index/CanonicalIncludes.h"
 #include "index/Index.h"
 #include "clang/AST/ASTContext.h"
+#include "clang/Basic/Diagnostic.h"
 #include "clang/Basic/LangOptions.h"
 #include "clang/Frontend/CompilerInstance.h"
 #include "clang/Frontend/CompilerInvocation.h"
@@ -34,9 +36,12 @@
 #include "clang/Serialization/ASTWriter.h"
 #include "clang/Tooling/CompilationDatabase.h"
 #include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/ADT/STLExtras.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/ScopedPrinter.h"
 #include "llvm/Support/raw_ostream.h"
 #include <algorithm>
 #include <memory>
@@ -45,6 +50,19 @@
 namespace clangd {
 namespace {
 
+bool mentionsMainFile(const Diag &D) {
+  if (D.InsideMainFile)
+    return true;
+  // Fixes are always in the main file.
+  if (!D.Fixes.empty())
+    return true;
+  for (auto &N : D.Notes) {
+    if (N.InsideMainFile)
+      return true;
+  }
+  return false;
+}
+
 bool compileCommandsAreEqual(const tooling::CompileCommand &LHS,
                              const tooling::CompileCommand &RHS) {
   // We don't check for Output, it should not matter to clangd.
@@ -236,6 +254,19 @@
   const LangOptions &LangOpts;
 };
 
+// Get the range for the included header begining at Position.
+Range positionToRange(const Position &Pos,
+                      const std::vector<Inclusion> &MainFileIncludes) {
+  Inclusion Inc;
+  Inc.R.start = Pos;
+  auto Res =
+      std::lower_bound(MainFileIncludes.begin(), MainFileIncludes.end(), Inc,
+                       [](const Inclusion &LHS, const Inclusion &RHS) {
+                         return LHS.R.start.line < RHS.R.start.line;
+                       });
+  return Res->R;
+}
+
 } // namespace
 
 void dumpAST(ParsedAST &AST, llvm::raw_ostream &OS) {
@@ -371,14 +402,57 @@
   // So just inform the preprocessor of EOF, while keeping everything alive.
   Clang->getPreprocessor().EndSourceFile();
 
-  std::vector<Diag> Diags = ASTDiags.take();
+  auto ShouldSurfaceDiag = [](const Diag &D) {
+    if (D.Severity == DiagnosticsEngine::Level::Error ||
+        D.Severity == DiagnosticsEngine::Level::Fatal)
+      return true;
+    return false;
+  };
+
+  std::vector<Diag> Diags;
+  llvm::DenseMap<int, int> NumberOfDiagsAtLine;
+  auto TryAddDiag = [&Diags, &NumberOfDiagsAtLine](const Diag &D) {
+    if (++NumberOfDiagsAtLine[D.Range.start.line] > 1) {
+      vlog("Dropped diagnostic: {0}: {1}", D.File, D.Message);
+      return;
+    }
+    Diags.push_back(D);
+  };
+
+  for (const Diag &D : ASTDiags.take()) {
+    if (mentionsMainFile(D))
+      Diags.push_back(D);
+    else if (ShouldSurfaceDiag(D))
+      TryAddDiag(D);
+  }
   // Populate diagnostic source.
   for (auto &D : Diags)
     D.S =
         !CTContext->getCheckName(D.ID).empty() ? Diag::ClangTidy : Diag::Clang;
   // Add diagnostics from the preamble, if any.
-  if (Preamble)
-    Diags.insert(Diags.begin(), Preamble->Diags.begin(), Preamble->Diags.end());
+  if (Preamble) {
+    for (const Diag &D : Preamble->Diags) {
+      if (mentionsMainFile(D))
+        Diags.push_back(D);
+      else if (ShouldSurfaceDiag(D))
+        TryAddDiag(D);
+    }
+  }
+
+  // Remap diagnostics outside main files into their include location inside
+  // main file. Also update diagnostic message if there are more diags at the
+  // same line.
+  for (auto &D : Diags) {
+    if (!mentionsMainFile(D)) {
+      // We've alredy shown the first diag, so subtract it.
+      size_t HasMoreDiags = NumberOfDiagsAtLine[D.Range.start.line] - 1;
+      D.Range = positionToRange(D.Range.start, Includes.MainFileIncludes);
+      D.File = MainInput.getFile();
+      if (HasMoreDiags)
+        D.Message +=
+            llvm::formatv("\nAnd {0} more diagnostics", HasMoreDiags).str();
+    }
+  }
   return ParsedAST(std::move(Preamble), std::move(Clang), std::move(Action),
                    std::move(ParsedDecls), std::move(Diags),
                    std::move(Includes), std::move(CanonIncludes));
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to