brintos

brintos / llvm-project-archived public Read only

0
0
Text · 76.8 KiB · f9ff6f2 Raw
2157 lines · cpp
1//===--- DiagnosticsTests.cpp ------------------------------------*- C++-*-===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "../clang-tidy/ClangTidyOptions.h"10#include "Annotations.h"11#include "Config.h"12#include "Diagnostics.h"13#include "Feature.h"14#include "FeatureModule.h"15#include "ParsedAST.h"16#include "Protocol.h"17#include "TestFS.h"18#include "TestIndex.h"19#include "TestTU.h"20#include "TidyProvider.h"21#include "index/MemIndex.h"22#include "index/Ref.h"23#include "index/Relation.h"24#include "index/Symbol.h"25#include "support/Context.h"26#include "support/Path.h"27#include "clang/AST/Decl.h"28#include "clang/Basic/Diagnostic.h"29#include "clang/Basic/DiagnosticSema.h"30#include "clang/Basic/LLVM.h"31#include "clang/Basic/Specifiers.h"32#include "llvm/ADT/ArrayRef.h"33#include "llvm/ADT/StringRef.h"34#include "llvm/Support/JSON.h"35#include "llvm/Support/ScopedPrinter.h"36#include "llvm/Support/TargetSelect.h"37#include "llvm/Testing/Support/SupportHelpers.h"38#include "gmock/gmock.h"39#include "gtest/gtest.h"40#include <cstddef>41#include <memory>42#include <optional>43#include <string>44#include <utility>45#include <vector>46 47namespace clang {48namespace clangd {49namespace {50 51using ::testing::_;52using ::testing::AllOf;53using ::testing::Contains;54using ::testing::Each;55using ::testing::ElementsAre;56using ::testing::Field;57using ::testing::IsEmpty;58using ::testing::Not;59using ::testing::Pair;60using ::testing::SizeIs;61using ::testing::UnorderedElementsAre;62 63::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher) {64  return Field(&Diag::Fixes, ElementsAre(FixMatcher));65}66 67::testing::Matcher<const Diag &> withFix(::testing::Matcher<Fix> FixMatcher1,68                                         ::testing::Matcher<Fix> FixMatcher2) {69  return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2));70}71 72::testing::Matcher<const Diag &> withID(unsigned ID) {73  return Field(&Diag::ID, ID);74}75::testing::Matcher<const Diag &>76withNote(::testing::Matcher<Note> NoteMatcher) {77  return Field(&Diag::Notes, ElementsAre(NoteMatcher));78}79 80::testing::Matcher<const Diag &>81withNote(::testing::Matcher<Note> NoteMatcher1,82         ::testing::Matcher<Note> NoteMatcher2) {83  return Field(&Diag::Notes, UnorderedElementsAre(NoteMatcher1, NoteMatcher2));84}85 86::testing::Matcher<const Diag &>87withTag(::testing::Matcher<DiagnosticTag> TagMatcher) {88  return Field(&Diag::Tags, Contains(TagMatcher));89}90 91MATCHER_P(hasRange, Range, "") { return arg.Range == Range; }92 93MATCHER_P2(Diag, Range, Message,94           "Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {95  return arg.Range == Range && arg.Message == Message;96}97 98MATCHER_P3(Fix, Range, Replacement, Message,99           "Fix " + llvm::to_string(Range) + " => " +100               ::testing::PrintToString(Replacement) + " = [" + Message + "]") {101  return arg.Message == Message && arg.Edits.size() == 1 &&102         arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;103}104 105MATCHER_P(fixMessage, Message, "") { return arg.Message == Message; }106 107MATCHER_P(equalToLSPDiag, LSPDiag,108          "LSP diagnostic " + llvm::to_string(LSPDiag)) {109  if (toJSON(arg) != toJSON(LSPDiag)) {110    *result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",111                                      toJSON(LSPDiag), toJSON(arg))112                            .str();113    return false;114  }115  return true;116}117 118MATCHER_P(diagSource, S, "") { return arg.Source == S; }119MATCHER_P(diagName, N, "") { return arg.Name == N; }120MATCHER_P(diagSeverity, S, "") { return arg.Severity == S; }121 122MATCHER_P(equalToFix, Fix, "LSP fix " + llvm::to_string(Fix)) {123  if (arg.Message != Fix.Message)124    return false;125  if (arg.Edits.size() != Fix.Edits.size())126    return false;127  for (std::size_t I = 0; I < arg.Edits.size(); ++I) {128    if (arg.Edits[I].range != Fix.Edits[I].range ||129        arg.Edits[I].newText != Fix.Edits[I].newText)130      return false;131  }132  return true;133}134 135// Helper function to make tests shorter.136Position pos(int Line, int Character) {137  Position Res;138  Res.line = Line;139  Res.character = Character;140  return Res;141}142 143// Normally returns the provided diagnostics matcher.144// If clang-tidy checks are not linked in, returns a matcher for no diagnostics!145// This is intended for tests where the diagnostics come from clang-tidy checks.146// We don't #ifdef each individual test as it's intrusive and we want to ensure147// that as much of the test is still compiled an run as possible.148::testing::Matcher<std::vector<clangd::Diag>>149ifTidyChecks(::testing::Matcher<std::vector<clangd::Diag>> M) {150  if (!CLANGD_TIDY_CHECKS)151    return IsEmpty();152  return M;153}154 155TEST(DiagnosticsTest, DiagnosticRanges) {156  // Check we report correct ranges, including various edge-cases.157  Annotations Test(R"cpp(158    // error-ok159    #define ID(X) X160    namespace test{};161    void $decl[[foo]]();162    int main() {163      struct Container { int* begin(); int* end(); } *container;164      for (auto i : $insertstar[[]]$range[[container]]) {165      }166 167      $typo[[go\168o]]();169      foo()$semicolon[[]]//with comments170      $unk[[unknown]]();171      double $type[[bar]] = "foo";172      struct Foo { int x; }; Foo a;173      a.$nomember[[y]];174      test::$nomembernamespace[[test]];175      $macro[[ID($macroarg[[fod]])]]();176    }177  )cpp");178  auto TU = TestTU::withCode(Test.code());179  EXPECT_THAT(180      TU.build().getDiagnostics(),181      ElementsAre(182          // Make sure the whole token is highlighted.183          AllOf(Diag(Test.range("range"),184                     "invalid range expression of type 'struct Container *'; "185                     "did you mean to dereference it with '*'?"),186                withFix(Fix(Test.range("insertstar"), "*", "insert '*'"))),187          // This range spans lines.188          AllOf(Diag(Test.range("typo"),189                     "use of undeclared identifier 'goo'; did you mean 'foo'?"),190                diagSource(Diag::Clang), diagName("undeclared_var_use_suggest"),191                withFix(192                    Fix(Test.range("typo"), "foo", "change 'go\\…' to 'foo'")),193                // This is a pretty normal range.194                withNote(Diag(Test.range("decl"), "'foo' declared here"))),195          // This range is zero-width and insertion. Therefore make sure we are196          // not expanding it into other tokens. Since we are not going to197          // replace those.198          AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"),199                withFix(Fix(Test.range("semicolon"), ";", "insert ';'"))),200          // This range isn't provided by clang, we expand to the token.201          Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"),202          Diag(Test.range("type"),203               "cannot initialize a variable of type 'double' with an lvalue "204               "of type 'const char[4]'"),205          Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"),206          Diag(Test.range("nomembernamespace"),207               "no member named 'test' in namespace 'test'"),208          AllOf(Diag(Test.range("macro"),209                     "use of undeclared identifier 'fod'; did you mean 'foo'?"),210                withFix(Fix(Test.range("macroarg"), "foo",211                            "change 'fod' to 'foo'")))));212}213 214// Verify that the -Wswitch case-not-covered diagnostic range covers the215// whole expression. This is important because the "populate-switch" tweak216// fires for the full expression range (see tweaks/PopulateSwitchTests.cpp).217// The quickfix flow only works end-to-end if the tweak can be triggered on218// the diagnostic's range.219TEST(DiagnosticsTest, WSwitch) {220  Annotations Test(R"cpp(221    enum A { X };222    struct B { A a; };223    void foo(B b) {224      switch ([[b.a]]) {}225    }226  )cpp");227  auto TU = TestTU::withCode(Test.code());228  TU.ExtraArgs = {"-Wswitch"};229  EXPECT_THAT(TU.build().getDiagnostics(),230              ElementsAre(Diag(Test.range(),231                               "enumeration value 'X' not handled in switch")));232}233 234TEST(DiagnosticsTest, FlagsMatter) {235  Annotations Test("[[void]] main() {} // error-ok");236  auto TU = TestTU::withCode(Test.code());237  EXPECT_THAT(TU.build().getDiagnostics(),238              ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),239                                withFix(Fix(Test.range(), "int",240                                            "change 'void' to 'int'")))));241  // Same code built as C gets different diagnostics.242  TU.Filename = "Plain.c";243  EXPECT_THAT(244      TU.build().getDiagnostics(),245      ElementsAre(AllOf(246          Diag(Test.range(), "return type of 'main' is not 'int'"),247          withFix(Fix(Test.range(), "int", "change return type to 'int'")))));248}249 250TEST(DiagnosticsTest, DiagnosticPreamble) {251  Annotations Test(R"cpp(252    #include $[["not-found.h"]] // error-ok253  )cpp");254 255  auto TU = TestTU::withCode(Test.code());256  EXPECT_THAT(TU.build().getDiagnostics(),257              ElementsAre(::testing::AllOf(258                  Diag(Test.range(), "'not-found.h' file not found"),259                  diagSource(Diag::Clang), diagName("pp_file_not_found"))));260}261 262TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {263  Annotations Test(R"cpp(264    float foo = [[0.1f]];265  )cpp");266  auto TU = TestTU::withCode(Test.code());267  // Enable alias clang-tidy checks, these check emits the same diagnostics268  // (except the check name).269  TU.ClangTidyProvider = addTidyChecks("readability-uppercase-literal-suffix,"270                                       "hicpp-uppercase-literal-suffix");271  // Verify that we filter out the duplicated diagnostic message.272  EXPECT_THAT(273      TU.build().getDiagnostics(),274      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(275          Diag(Test.range(),276               "floating point literal has suffix 'f', which is not uppercase"),277          diagSource(Diag::ClangTidy)))));278 279  Test = Annotations(R"cpp(280    template<typename T>281    void func(T) {282      float f = [[0.3f]];283    }284    void k() {285      func(123);286      func(2.0);287    }288  )cpp");289  TU.Code = std::string(Test.code());290  // The check doesn't handle template instantiations which ends up emitting291  // duplicated messages, verify that we deduplicate them.292  EXPECT_THAT(293      TU.build().getDiagnostics(),294      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(295          Diag(Test.range(),296               "floating point literal has suffix 'f', which is not uppercase"),297          diagSource(Diag::ClangTidy)))));298}299 300TEST(DiagnosticsTest, ClangTidy) {301  Annotations Test(R"cpp(302    #include $deprecated[["assert.h"]]303 304    #define $macrodef[[SQUARE]](X) (X)*(X)305    int $main[[main]]() {306      int y = 4;307      return SQUARE($macroarg[[++]]y);308      return $doubled[[sizeof(sizeof(int))]];309    }310 311    // misc-no-recursion uses a custom traversal from the TUDecl312    void foo();313    void $bar[[bar]]() {314      foo();315    }316    void $foo[[foo]]() {317      bar();318    }319  )cpp");320  auto TU = TestTU::withCode(Test.code());321  TU.HeaderFilename = "assert.h"; // Suppress "not found" error.322  TU.ClangTidyProvider = addTidyChecks("bugprone-sizeof-expression,"323                                       "bugprone-macro-repeated-side-effects,"324                                       "modernize-deprecated-headers,"325                                       "modernize-use-trailing-return-type,"326                                       "misc-no-recursion");327  TU.ExtraArgs.push_back("-Wno-unsequenced");328  EXPECT_THAT(329      TU.build().getDiagnostics(),330      ifTidyChecks(UnorderedElementsAre(331          AllOf(Diag(Test.range("deprecated"),332                     "inclusion of deprecated C++ header 'assert.h'; consider "333                     "using 'cassert' instead"),334                diagSource(Diag::ClangTidy),335                diagName("modernize-deprecated-headers"),336                withFix(Fix(Test.range("deprecated"), "<cassert>",337                            "change '\"assert.h\"' to '<cassert>'"))),338          Diag(Test.range("doubled"),339               "suspicious usage of 'sizeof(sizeof(...))'"),340          AllOf(Diag(Test.range("macroarg"),341                     "side effects in the 1st macro argument 'X' are "342                     "repeated in "343                     "macro expansion"),344                diagSource(Diag::ClangTidy),345                diagName("bugprone-macro-repeated-side-effects"),346                withNote(Diag(Test.range("macrodef"),347                              "macro 'SQUARE' defined here"))),348          AllOf(Diag(Test.range("main"),349                     "use a trailing return type for this function"),350                diagSource(Diag::ClangTidy),351                diagName("modernize-use-trailing-return-type"),352                // Verify there's no "[check-name]" suffix in the message.353                withFix(fixMessage(354                    "use a trailing return type for this function"))),355          Diag(Test.range("foo"),356               "function 'foo' is within a recursive call chain"),357          Diag(Test.range("bar"),358               "function 'bar' is within a recursive call chain"))));359}360 361TEST(DiagnosticsTest, ClangTidyEOF) {362  // clang-format off363  Annotations Test(R"cpp(364  [[#]]include <b.h>365  #include "a.h")cpp");366  // clang-format on367  auto TU = TestTU::withCode(Test.code());368  TU.ExtraArgs = {"-isystem."};369  TU.AdditionalFiles["a.h"] = TU.AdditionalFiles["b.h"] = "";370  TU.ClangTidyProvider = addTidyChecks("llvm-include-order");371  EXPECT_THAT(372      TU.build().getDiagnostics(),373      ifTidyChecks(Contains(374          AllOf(Diag(Test.range(), "#includes are not sorted properly"),375                diagSource(Diag::ClangTidy), diagName("llvm-include-order")))));376}377 378TEST(DiagnosticTest, TemplatesInHeaders) {379  // Diagnostics from templates defined in headers are placed at the expansion.380  Annotations Main(R"cpp(381    Derived<int> [[y]]; // error-ok382  )cpp");383  Annotations Header(R"cpp(384    template <typename T>385    struct Derived : [[T]] {};386  )cpp");387  TestTU TU = TestTU::withCode(Main.code());388  TU.HeaderCode = Header.code().str();389  EXPECT_THAT(390      TU.build().getDiagnostics(),391      ElementsAre(AllOf(392          Diag(Main.range(), "in template: base specifier must name a class"),393          withNote(Diag(Header.range(), "error occurred here"),394                   Diag(Main.range(), "in instantiation of template class "395                                      "'Derived<int>' requested here")))));396}397 398TEST(DiagnosticTest, MakeUnique) {399  // We usually miss diagnostics from header functions as we don't parse them.400  // std::make_unique is an exception.401  Annotations Main(R"cpp(402    struct S { S(char*); };403    auto x = std::[[make_unique]]<S>(42); // error-ok404  )cpp");405  TestTU TU = TestTU::withCode(Main.code());406  TU.HeaderCode = R"cpp(407    namespace std {408    // These mocks aren't quite right - we omit unique_ptr for simplicity.409    // forward is included to show its body is not needed to get the diagnostic.410    template <typename T> T&& forward(T& t);411    template <typename T, typename... A> T* make_unique(A&&... args) {412       return new T(std::forward<A>(args)...);413    }414    }415  )cpp";416  EXPECT_THAT(TU.build().getDiagnostics(),417              UnorderedElementsAre(418                  Diag(Main.range(),419                       "in template: "420                       "no matching constructor for initialization of 'S'")));421}422 423TEST(DiagnosticTest, CoroutineInHeader) {424  StringRef CoroutineH = R"cpp(425namespace std {426template <class Ret, typename... T>427struct coroutine_traits { using promise_type = typename Ret::promise_type; };428 429template <class Promise = void>430struct coroutine_handle {431  static coroutine_handle from_address(void *) noexcept;432  static coroutine_handle from_promise(Promise &promise);433  constexpr void* address() const noexcept;434};435template <>436struct coroutine_handle<void> {437  template <class PromiseType>438  coroutine_handle(coroutine_handle<PromiseType>) noexcept;439  static coroutine_handle from_address(void *);440  constexpr void* address() const noexcept;441};442 443struct awaitable {444  bool await_ready() noexcept { return false; }445  void await_suspend(coroutine_handle<>) noexcept {}446  void await_resume() noexcept {}447};448} // namespace std449  )cpp";450 451  StringRef Header = R"cpp(452#include "coroutine.h"453template <typename T> struct [[clang::coro_return_type]] Gen {454  struct promise_type {455    Gen<T> get_return_object() {456      return {};457    }458    std::awaitable  initial_suspend();459    std::awaitable  final_suspend() noexcept;460    void unhandled_exception();461    void return_value(T t);462  };463};464 465Gen<int> foo_coro(int b) { co_return b; }466  )cpp";467  Annotations Main(R"cpp(468// error-ok469#include "header.hpp"470Gen<int> $[[bar_coro]](int b) { return foo_coro(b); }471  )cpp");472  TestTU TU = TestTU::withCode(Main.code());473  TU.AdditionalFiles["coroutine.h"] = std::string(CoroutineH);474  TU.AdditionalFiles["header.hpp"] = std::string(Header);475  TU.ExtraArgs.push_back("--std=c++20");476  EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(hasRange(Main.range())));477}478 479TEST(DiagnosticTest, MakeShared) {480  // We usually miss diagnostics from header functions as we don't parse them.481  // std::make_shared is only parsed when --parse-forwarding-functions is set482  Annotations Main(R"cpp(483    struct S { S(char*); };484    auto x = std::[[make_shared]]<S>(42); // error-ok485  )cpp");486  TestTU TU = TestTU::withCode(Main.code());487  TU.HeaderCode = R"cpp(488    namespace std {489    // These mocks aren't quite right - we omit shared_ptr for simplicity.490    // forward is included to show its body is not needed to get the diagnostic.491    template <typename T> T&& forward(T& t);492    template <typename T, typename... A> T* make_shared(A&&... args) {493       return new T(std::forward<A>(args)...);494    }495    }496  )cpp";497  TU.ParseOpts.PreambleParseForwardingFunctions = true;498  EXPECT_THAT(TU.build().getDiagnostics(),499              UnorderedElementsAre(500                  Diag(Main.range(),501                       "in template: "502                       "no matching constructor for initialization of 'S'")));503}504 505TEST(DiagnosticTest, NoMultipleDiagnosticInFlight) {506  Annotations Main(R"cpp(507    template <typename T> struct Foo {508      T *begin();509      T *end();510    };511    struct LabelInfo {512      int a;513      bool b;514    };515 516    void f() {517      Foo<LabelInfo> label_info_map;518      [[for]] (auto it = label_info_map.begin(); it != label_info_map.end(); ++it) {519        auto S = *it;520      }521    }522  )cpp");523  TestTU TU = TestTU::withCode(Main.code());524  TU.ClangTidyProvider = addTidyChecks("modernize-loop-convert");525  EXPECT_THAT(526      TU.build().getDiagnostics(),527      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(528          Diag(Main.range(), "use range-based for loop instead"),529          diagSource(Diag::ClangTidy), diagName("modernize-loop-convert")))));530}531 532TEST(DiagnosticTest, RespectsDiagnosticConfig) {533  Annotations Main(R"cpp(534    // error-ok535    void x() {536      [[unknown]]();537      $ret[[return]] 42;538    }539  )cpp");540  auto TU = TestTU::withCode(Main.code());541  EXPECT_THAT(542      TU.build().getDiagnostics(),543      ElementsAre(Diag(Main.range(), "use of undeclared identifier 'unknown'"),544                  Diag(Main.range("ret"),545                       "void function 'x' should not return a value")));546  Config Cfg;547  Cfg.Diagnostics.Suppress.insert("return-mismatch");548  WithContextValue WithCfg(Config::Key, std::move(Cfg));549  EXPECT_THAT(TU.build().getDiagnostics(),550              ElementsAre(Diag(Main.range(),551                               "use of undeclared identifier 'unknown'")));552}553 554TEST(DiagnosticTest, RespectsDiagnosticConfigInHeader) {555  Annotations Header(R"cpp(556    int x = "42";  // error-ok557  )cpp");558  Annotations Main(R"cpp(559    #include "header.hpp"560  )cpp");561  auto TU = TestTU::withCode(Main.code());562  TU.AdditionalFiles["header.hpp"] = std::string(Header.code());563  Config Cfg;564  Cfg.Diagnostics.Suppress.insert("init_conversion_failed");565  WithContextValue WithCfg(Config::Key, std::move(Cfg));566  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());567}568 569TEST(DiagnosticTest, ClangTidySuppressionComment) {570  Annotations Main(R"cpp(571    int main() {572      int i = 3;573      double d = 8 / i;  // NOLINT574      // NOLINTNEXTLINE575      double e = 8 / i;576      #define BAD 8 / i577      double f = BAD;  // NOLINT578      double g = [[8]] / i;579      #define BAD2 BAD580      double h = BAD2;  // NOLINT581      // NOLINTBEGIN582      double x = BAD2;583      double y = BAD2;584      // NOLINTEND585 586      // verify no crashes on unmatched nolints.587      // NOLINTBEGIN588    }589  )cpp");590  TestTU TU = TestTU::withCode(Main.code());591  TU.ClangTidyProvider = addTidyChecks("bugprone-integer-division");592  EXPECT_THAT(593      TU.build().getDiagnostics(),594      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(595          Diag(Main.range(), "result of integer division used in a floating "596                             "point context; possible loss of precision"),597          diagSource(Diag::ClangTidy),598          diagName("bugprone-integer-division")))));599}600 601TEST(DiagnosticTest, ClangTidySystemMacro) {602  Annotations Main(R"cpp(603    #include "user.h"604    #include "system.h"605    int i = 3;606    double x = $inline[[8]] / i;607    double y = $user[[DIVIDE_USER]](i);608    double z = DIVIDE_SYS(i);609  )cpp");610 611  auto TU = TestTU::withCode(Main.code());612  TU.AdditionalFiles["user.h"] = R"cpp(613    #define DIVIDE_USER(Y) 8/Y614  )cpp";615  TU.AdditionalFiles["system.h"] = R"cpp(616    #pragma clang system_header617    #define DIVIDE_SYS(Y) 8/Y618  )cpp";619 620  TU.ClangTidyProvider = addTidyChecks("bugprone-integer-division");621  std::string BadDivision = "result of integer division used in a floating "622                            "point context; possible loss of precision";623 624  // Expect to see warning from user macros, but not system macros.625  // This matches clang-tidy --system-headers=0 (the default).626  EXPECT_THAT(TU.build().getDiagnostics(),627              ifTidyChecks(628                  UnorderedElementsAre(Diag(Main.range("inline"), BadDivision),629                                       Diag(Main.range("user"), BadDivision))));630}631 632TEST(DiagnosticTest, ClangTidyWarningAsError) {633  Annotations Main(R"cpp(634    int main() {635      int i = 3;636      double f = [[8]] / i; // error-ok637    }638  )cpp");639  TestTU TU = TestTU::withCode(Main.code());640  TU.ClangTidyProvider =641      addTidyChecks("bugprone-integer-division", "bugprone-integer-division");642  EXPECT_THAT(643      TU.build().getDiagnostics(),644      ifTidyChecks(UnorderedElementsAre(::testing::AllOf(645          Diag(Main.range(), "result of integer division used in a floating "646                             "point context; possible loss of precision"),647          diagSource(Diag::ClangTidy), diagName("bugprone-integer-division"),648          diagSeverity(DiagnosticsEngine::Error)))));649}650 651TidyProvider addClangArgs(std::vector<llvm::StringRef> ExtraArgs,652                          llvm::StringRef Checks) {653  return [ExtraArgs = std::move(ExtraArgs), Checks = Checks.str()](654             tidy::ClangTidyOptions &Opts, llvm::StringRef) {655    if (!Opts.ExtraArgs)656      Opts.ExtraArgs.emplace();657    for (llvm::StringRef Arg : ExtraArgs)658      Opts.ExtraArgs->emplace_back(Arg);659    if (!Checks.empty())660      Opts.Checks = Checks;661  };662}663 664TEST(DiagnosticTest, ClangTidyEnablesClangWarning) {665  Annotations Main(R"cpp( // error-ok666    static void [[foo]]() {}667  )cpp");668  TestTU TU = TestTU::withCode(Main.code());669  // This is always emitted as a clang warning, not a clang-tidy diagnostic.670  auto UnusedFooWarning =671      AllOf(Diag(Main.range(), "unused function 'foo'"),672            diagName("-Wunused-function"), diagSource(Diag::Clang),673            diagSeverity(DiagnosticsEngine::Warning));674 675  // Check the -Wunused warning isn't initially on.676  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());677 678  // We enable warnings based on clang-tidy extra args, if the matching679  // clang-diagnostic- is there.680  TU.ClangTidyProvider =681      addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");682  EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));683 684  // clang-diagnostic-* is acceptable685  TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "clang-diagnostic-*");686  EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning));687  // And plain * (may turn on other checks too).688  TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "*");689  EXPECT_THAT(TU.build().getDiagnostics(), Contains(UnusedFooWarning));690  // And we can explicitly exclude a category too.691  TU.ClangTidyProvider = addClangArgs(692      {"-Wunused"}, "clang-diagnostic-*,-clang-diagnostic-unused-function");693  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());694 695  // Without the exact check specified, the warnings are not enabled.696  TU.ClangTidyProvider = addClangArgs({"-Wunused"}, "clang-diagnostic-unused");697  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());698 699  // We don't respect other args.700  TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Dfoo=bar"},701                                      "clang-diagnostic-unused-function");702  EXPECT_THAT(TU.build().getDiagnostics(), ElementsAre(UnusedFooWarning))703      << "Not unused function 'bar'!";704 705  // -Werror doesn't apply to warnings enabled by clang-tidy extra args.706  TU.ExtraArgs = {"-Werror"};707  TU.ClangTidyProvider =708      addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");709  EXPECT_THAT(TU.build().getDiagnostics(),710              ElementsAre(diagSeverity(DiagnosticsEngine::Warning)));711 712  // But clang-tidy extra args won't *downgrade* errors to warnings either.713  TU.ExtraArgs = {"-Wunused", "-Werror"};714  TU.ClangTidyProvider =715      addClangArgs({"-Wunused"}, "clang-diagnostic-unused-function");716  EXPECT_THAT(TU.build().getDiagnostics(),717              ElementsAre(diagSeverity(DiagnosticsEngine::Error)));718 719  // FIXME: we're erroneously downgrading the whole group, this should be Error.720  TU.ExtraArgs = {"-Wunused-function", "-Werror"};721  TU.ClangTidyProvider =722      addClangArgs({"-Wunused"}, "clang-diagnostic-unused-label");723  EXPECT_THAT(TU.build().getDiagnostics(),724              ElementsAre(diagSeverity(DiagnosticsEngine::Warning)));725 726  // This looks silly, but it's the typical result if a warning is enabled by a727  // high-level .clang-tidy file and disabled by a low-level one.728  TU.ExtraArgs = {};729  TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Wno-unused"},730                                      "clang-diagnostic-unused-function");731  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());732 733  // Overriding only works in the proper order.734  TU.ClangTidyProvider =735      addClangArgs({"-Wunused"}, {"clang-diagnostic-unused-function"});736  EXPECT_THAT(TU.build().getDiagnostics(), SizeIs(1));737 738  // More specific vs less-specific: match clang behavior739  TU.ClangTidyProvider = addClangArgs({"-Wunused", "-Wno-unused-function"},740                                      {"clang-diagnostic-unused-function"});741  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());742  TU.ClangTidyProvider = addClangArgs({"-Wunused-function", "-Wno-unused"},743                                      {"clang-diagnostic-unused-function"});744  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());745 746  // We do allow clang-tidy config to disable warnings from the compile747  // command. It's unclear this is ideal, but it's hard to avoid.748  TU.ExtraArgs = {"-Wunused"};749  TU.ClangTidyProvider = addClangArgs({"-Wno-unused"}, {});750  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());751 752  TU.ExtraArgs = {"-Wno-unused"};753  TU.ClangTidyProvider = addClangArgs({"-Wunused"}, {"-*, clang-diagnostic-*"});754  EXPECT_THAT(TU.build().getDiagnostics(), SizeIs(1));755}756 757TEST(DiagnosticTest, LongFixMessages) {758  // We limit the size of printed code.759  Annotations Source(R"cpp(760    int main() {761      // error-ok762      int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;763      [[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;764    }765  )cpp");766  TestTU TU = TestTU::withCode(Source.code());767  EXPECT_THAT(768      TU.build().getDiagnostics(),769      ElementsAre(withFix(Fix(770          Source.range(),771          "somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",772          "change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "773          "'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));774  // Only show changes up to a first newline.775  Source = Annotations(R"cpp(776    // error-ok777    int main() {778      int ident;779      [[ide\780n]] = 10; // error-ok781    }782  )cpp");783  TU.Code = std::string(Source.code());784  EXPECT_THAT(TU.build().getDiagnostics(),785              ElementsAre(withFix(786                  Fix(Source.range(), "ident", "change 'ide\\…' to 'ident'"))));787}788 789TEST(DiagnosticTest, NewLineFixMessage) {790  Annotations Source("int a;[[]]");791  TestTU TU = TestTU::withCode(Source.code());792  TU.ExtraArgs = {"-Wnewline-eof"};793  EXPECT_THAT(794      TU.build().getDiagnostics(),795      ElementsAre(withFix((Fix(Source.range(), "\n", "insert '\\n'")))));796}797 798TEST(DiagnosticTest, ClangTidySuppressionCommentTrumpsWarningAsError) {799  Annotations Main(R"cpp(800    int main() {801      int i = 3;802      double f = [[8]] / i;  // NOLINT803    }804  )cpp");805  TestTU TU = TestTU::withCode(Main.code());806  TU.ClangTidyProvider =807      addTidyChecks("bugprone-integer-division", "bugprone-integer-division");808  EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());809}810 811TEST(DiagnosticTest, ClangTidyNoLiteralDataInMacroToken) {812  Annotations Main(R"cpp(813    #define SIGTERM 15814    using pthread_t = int;815    int pthread_kill(pthread_t thread, int sig);816    int func() {817      pthread_t thread;818      return pthread_kill(thread, 0);819    }820  )cpp");821  TestTU TU = TestTU::withCode(Main.code());822  TU.ClangTidyProvider = addTidyChecks("bugprone-bad-signal-to-kill-thread");823  EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre()); // no-crash824}825 826TEST(DiagnosticTest, ClangTidyMacroToEnumCheck) {827  Annotations Main(R"cpp(828    #if 1829    auto foo();830    #endif831  )cpp");832  TestTU TU = TestTU::withCode(Main.code());833  std::vector<TidyProvider> Providers;834  Providers.push_back(835      addTidyChecks("cppcoreguidelines-macro-to-enum,modernize-macro-to-enum"));836  Providers.push_back(disableUnusableChecks());837  TU.ClangTidyProvider = combine(std::move(Providers));838  EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre()); // no-crash839}840 841TEST(DiagnosticTest, ElseAfterReturnRange) {842  Annotations Main(R"cpp(843    int foo(int cond) {844    if (cond == 1) {845      return 42;846    } [[else]] if (cond == 2) {847      return 43;848    }849    return 44;850    }851  )cpp");852  TestTU TU = TestTU::withCode(Main.code());853  TU.ClangTidyProvider = addTidyChecks("llvm-else-after-return");854  EXPECT_THAT(TU.build().getDiagnostics(),855              ifTidyChecks(ElementsAre(856                  Diag(Main.range(), "do not use 'else' after 'return'"))));857}858 859TEST(DiagnosticTest, ClangTidySelfContainedDiags) {860  Annotations Main(R"cpp($MathHeader[[]]861    struct Foo{862      int A, B;863      Foo()$Fix[[]] {864        $A[[A = 1;]]865        $B[[B = 1;]]866      }867    };868    void InitVariables() {869      float $C[[C]]$CFix[[]];870      double $D[[D]]$DFix[[]];871    }872  )cpp");873  TestTU TU = TestTU::withCode(Main.code());874  TU.ClangTidyProvider =875      addTidyChecks("cppcoreguidelines-prefer-member-initializer,"876                    "cppcoreguidelines-init-variables");877  clangd::Fix ExpectedAFix;878  ExpectedAFix.Message =879      "'A' should be initialized in a member initializer of the constructor";880  ExpectedAFix.Edits.push_back(TextEdit{Main.range("Fix"), " : A(1)"});881  ExpectedAFix.Edits.push_back(TextEdit{Main.range("A"), ""});882 883  // When invoking clang-tidy normally, this code would produce `, B(1)` as the884  // fix the `B` member, as it would think its already included the ` : ` from885  // the previous `A` fix.886  clangd::Fix ExpectedBFix;887  ExpectedBFix.Message =888      "'B' should be initialized in a member initializer of the constructor";889  ExpectedBFix.Edits.push_back(TextEdit{Main.range("Fix"), " : B(1)"});890  ExpectedBFix.Edits.push_back(TextEdit{Main.range("B"), ""});891 892  clangd::Fix ExpectedCFix;893  ExpectedCFix.Message = "variable 'C' is not initialized";894  ExpectedCFix.Edits.push_back(TextEdit{Main.range("CFix"), " = NAN"});895  ExpectedCFix.Edits.push_back(896      TextEdit{Main.range("MathHeader"), "#include <math.h>\n\n"});897 898  // Again in clang-tidy only the include directive would be emitted for the899  // first warning. However we need the include attaching for both warnings.900  clangd::Fix ExpectedDFix;901  ExpectedDFix.Message = "variable 'D' is not initialized";902  ExpectedDFix.Edits.push_back(TextEdit{Main.range("DFix"), " = NAN"});903  ExpectedDFix.Edits.push_back(904      TextEdit{Main.range("MathHeader"), "#include <math.h>\n\n"});905  EXPECT_THAT(906      TU.build().getDiagnostics(),907      ifTidyChecks(UnorderedElementsAre(908          AllOf(Diag(Main.range("A"), "'A' should be initialized in a member "909                                      "initializer of the constructor"),910                withFix(equalToFix(ExpectedAFix))),911          AllOf(Diag(Main.range("B"), "'B' should be initialized in a member "912                                      "initializer of the constructor"),913                withFix(equalToFix(ExpectedBFix))),914          AllOf(Diag(Main.range("C"), "variable 'C' is not initialized"),915                withFix(equalToFix(ExpectedCFix))),916          AllOf(Diag(Main.range("D"), "variable 'D' is not initialized"),917                withFix(equalToFix(ExpectedDFix))))));918}919 920TEST(DiagnosticTest, ClangTidySelfContainedDiagsFormatting) {921  Annotations Main(R"cpp(922    class Interface {923    public:924      virtual void Reset1() = 0;925      virtual void Reset2() = 0;926    };927    class A : public Interface {928      // This will be marked by clangd to use override instead of virtual929      $virtual1[[virtual    ]]void $Reset1[[Reset1]]()$override1[[]];930      $virtual2[[virtual      ]]/**/void $Reset2[[Reset2]]()$override2[[]];931    };932  )cpp");933  TestTU TU = TestTU::withCode(Main.code());934  TU.ClangTidyProvider =935      addTidyChecks("cppcoreguidelines-explicit-virtual-functions,");936  clangd::Fix const ExpectedFix1{937      "prefer using 'override' or (rarely) 'final' "938      "instead of 'virtual'",939      {TextEdit{Main.range("override1"), " override"},940       TextEdit{Main.range("virtual1"), ""}},941      {}};942  clangd::Fix const ExpectedFix2{943      "prefer using 'override' or (rarely) 'final' "944      "instead of 'virtual'",945      {TextEdit{Main.range("override2"), " override"},946       TextEdit{Main.range("virtual2"), ""}},947      {}};948  // Note that in the Fix we expect the "virtual" keyword and the following949  // whitespace to be deleted950  EXPECT_THAT(TU.build().getDiagnostics(),951              ifTidyChecks(UnorderedElementsAre(952                  AllOf(Diag(Main.range("Reset1"),953                             "prefer using 'override' or (rarely) 'final' "954                             "instead of 'virtual'"),955                        withFix(equalToFix(ExpectedFix1))),956                  AllOf(Diag(Main.range("Reset2"),957                             "prefer using 'override' or (rarely) 'final' "958                             "instead of 'virtual'"),959                        withFix(equalToFix(ExpectedFix2))))));960}961 962TEST(DiagnosticsTest, ClangTidyCallingIntoPreprocessor) {963  std::string Main = R"cpp(964    extern "C" {965    #include "b.h"966    }967  )cpp";968  std::string Header = R"cpp(969    #define EXTERN extern970    EXTERN int waldo();971  )cpp";972  auto TU = TestTU::withCode(Main);973  TU.AdditionalFiles["b.h"] = Header;974  TU.ClangTidyProvider = addTidyChecks("modernize-use-trailing-return-type");975  // Check that no assertion failures occur during the build976  TU.build();977}978 979TEST(DiagnosticsTest, Preprocessor) {980  // This looks like a preamble, but there's an #else in the middle!981  // Check that:982  //  - the #else doesn't generate diagnostics (we had this bug)983  //  - we get diagnostics from the taken branch984  //  - we get no diagnostics from the not taken branch985  Annotations Test(R"cpp(986    #ifndef FOO987    #define FOO988      int a = [[b]]; // error-ok989    #else990      int x = y;991    #endif992    )cpp");993  EXPECT_THAT(994      TestTU::withCode(Test.code()).build().getDiagnostics(),995      ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));996}997 998TEST(DiagnosticsTest, IgnoreVerify) {999  auto TU = TestTU::withCode(R"cpp(1000    int a; // expected-error {{}}1001  )cpp");1002  TU.ExtraArgs.push_back("-Xclang");1003  TU.ExtraArgs.push_back("-verify");1004  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());1005}1006 1007TEST(DiagnosticTest, IgnoreBEFilelistOptions) {1008  auto TU = TestTU::withCode("");1009  TU.ExtraArgs.push_back("-Xclang");1010  for (const auto *DisableOption :1011       {"-fsanitize-ignorelist=null", "-fprofile-list=null",1012        "-fxray-always-instrument=null", "-fxray-never-instrument=null",1013        "-fxray-attr-list=null"}) {1014    TU.ExtraArgs.push_back(DisableOption);1015    EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());1016    TU.ExtraArgs.pop_back();1017  }1018}1019 1020// Recursive main-file include is diagnosed, and doesn't crash.1021TEST(DiagnosticsTest, RecursivePreamble) {1022  auto TU = TestTU::withCode(R"cpp(1023    #include "foo.h" // error-ok1024    int symbol;1025  )cpp");1026  TU.Filename = "foo.h";1027  EXPECT_THAT(TU.build().getDiagnostics(),1028              ElementsAre(diagName("pp_including_mainfile_in_preamble")));1029  EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));1030}1031 1032// Recursive main-file include with #pragma once guard is OK.1033TEST(DiagnosticsTest, RecursivePreamblePragmaOnce) {1034  auto TU = TestTU::withCode(R"cpp(1035    #pragma once1036    #include "foo.h"1037    int symbol;1038  )cpp");1039  TU.Filename = "foo.h";1040  EXPECT_THAT(TU.build().getDiagnostics(),1041              Not(Contains(diagName("pp_including_mainfile_in_preamble"))));1042  EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));1043}1044 1045// Recursive main-file include with #ifndef guard should be OK.1046// However, it's not yet recognized (incomplete at end of preamble).1047TEST(DiagnosticsTest, RecursivePreambleIfndefGuard) {1048  auto TU = TestTU::withCode(R"cpp(1049    #ifndef FOO1050    #define FOO1051    #include "foo.h" // error-ok1052    int symbol;1053    #endif1054  )cpp");1055  TU.Filename = "foo.h";1056  // FIXME: should be no errors here.1057  EXPECT_THAT(TU.build().getDiagnostics(),1058              ElementsAre(diagName("pp_including_mainfile_in_preamble")));1059  EXPECT_THAT(TU.build().getLocalTopLevelDecls(), SizeIs(1));1060}1061 1062TEST(DiagnosticsTest, PreambleWithPragmaAssumeNonnull) {1063  auto TU = TestTU::withCode(R"cpp(1064#pragma clang assume_nonnull begin1065void foo(int *x);1066#pragma clang assume_nonnull end1067)cpp");1068  auto AST = TU.build();1069  EXPECT_THAT(AST.getDiagnostics(), IsEmpty());1070  const auto *X = cast<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);1071  ASSERT_TRUE(X->getOriginalType()->getNullability() ==1072              NullabilityKind::NonNull);1073}1074 1075TEST(DiagnosticsTest, PreambleHeaderWithBadPragmaAssumeNonnull) {1076  Annotations Header(R"cpp(1077#pragma clang assume_nonnull begin  // error-ok1078void foo(int *X);1079)cpp");1080  auto TU = TestTU::withCode(R"cpp(1081#include "foo.h"  // unterminated assume_nonnull should not affect bar.1082void bar(int *Y);1083)cpp");1084  TU.AdditionalFiles = {{"foo.h", std::string(Header.code())}};1085  auto AST = TU.build();1086  EXPECT_THAT(AST.getDiagnostics(),1087              ElementsAre(diagName("pp_eof_in_assume_nonnull")));1088  const auto *X = cast<FunctionDecl>(findDecl(AST, "foo")).getParamDecl(0);1089  ASSERT_TRUE(X->getOriginalType()->getNullability() ==1090              NullabilityKind::NonNull);1091  const auto *Y = cast<FunctionDecl>(findDecl(AST, "bar")).getParamDecl(0);1092  ASSERT_FALSE(Y->getOriginalType()->getNullability());1093}1094 1095TEST(DiagnosticsTest, InsideMacros) {1096  Annotations Test(R"cpp(1097    #define TEN 101098    #define RET(x) return x + 101099 1100    int* foo() {1101      RET($foo[[0]]); // error-ok1102    }1103    int* bar() {1104      return $bar[[TEN]];1105    }1106    )cpp");1107  EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(),1108              ElementsAre(Diag(Test.range("foo"),1109                               "cannot initialize return object of type "1110                               "'int *' with an rvalue of type 'int'"),1111                          Diag(Test.range("bar"),1112                               "cannot initialize return object of type "1113                               "'int *' with an rvalue of type 'int'")));1114}1115 1116TEST(DiagnosticsTest, NoFixItInMacro) {1117  Annotations Test(R"cpp(1118    #define Define(name) void name() {}1119 1120    [[Define]](main) // error-ok1121  )cpp");1122  auto TU = TestTU::withCode(Test.code());1123  EXPECT_THAT(TU.build().getDiagnostics(),1124              ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),1125                                Not(withFix(_)))));1126}1127 1128TEST(DiagnosticsTest, PragmaSystemHeader) {1129  Annotations Test("#pragma clang [[system_header]]\n");1130  auto TU = TestTU::withCode(Test.code());1131  EXPECT_THAT(1132      TU.build().getDiagnostics(),1133      ElementsAre(AllOf(1134          Diag(Test.range(), "#pragma system_header ignored in main file"))));1135  TU.Filename = "TestTU.h";1136  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());1137}1138 1139TEST(ClangdTest, MSAsm) {1140  // Parsing MS assembly tries to use the target MCAsmInfo, which we don't link.1141  // We used to crash here. Now clang emits a diagnostic, which we filter out.1142  llvm::InitializeAllTargetInfos(); // As in ClangdMain1143  auto TU = TestTU::withCode("void fn() { __asm { cmp cl,64 } }");1144  TU.ExtraArgs = {"-fms-extensions"};1145  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());1146}1147 1148TEST(DiagnosticsTest, ToLSP) {1149  URIForFile MainFile =1150      URIForFile::canonicalize(testPath("foo/bar/main.cpp"), "");1151  URIForFile HeaderFile =1152      URIForFile::canonicalize(testPath("foo/bar/header.h"), "");1153 1154  clangd::Diag D;1155  D.ID = clang::diag::err_undeclared_var_use;1156  D.Tags = {DiagnosticTag::Unnecessary};1157  D.Name = "undeclared_var_use";1158  D.Source = clangd::Diag::Clang;1159  D.Message = "something terrible happened";1160  D.Range = {pos(1, 2), pos(3, 4)};1161  D.InsideMainFile = true;1162  D.Severity = DiagnosticsEngine::Error;1163  D.File = "foo/bar/main.cpp";1164  D.AbsFile = std::string(MainFile.file());1165  D.OpaqueData["test"] = "bar";1166 1167  clangd::Note NoteInMain;1168  NoteInMain.Message = "declared somewhere in the main file";1169  NoteInMain.Range = {pos(5, 6), pos(7, 8)};1170  NoteInMain.Severity = DiagnosticsEngine::Remark;1171  NoteInMain.File = "../foo/bar/main.cpp";1172  NoteInMain.InsideMainFile = true;1173  NoteInMain.AbsFile = std::string(MainFile.file());1174 1175  D.Notes.push_back(NoteInMain);1176 1177  clangd::Note NoteInHeader;1178  NoteInHeader.Message = "declared somewhere in the header file";1179  NoteInHeader.Range = {pos(9, 10), pos(11, 12)};1180  NoteInHeader.Severity = DiagnosticsEngine::Note;1181  NoteInHeader.File = "../foo/baz/header.h";1182  NoteInHeader.InsideMainFile = false;1183  NoteInHeader.AbsFile = std::string(HeaderFile.file());1184  D.Notes.push_back(NoteInHeader);1185 1186  clangd::Fix F;1187  F.Message = "do something";1188  D.Fixes.push_back(F);1189 1190  // Diagnostics should turn into these:1191  clangd::Diagnostic MainLSP;1192  MainLSP.range = D.Range;1193  MainLSP.severity = getSeverity(DiagnosticsEngine::Error);1194  MainLSP.code = "undeclared_var_use";1195  MainLSP.source = "clang";1196  MainLSP.message =1197      R"(Something terrible happened (fix available)1198 1199main.cpp:6:7: remark: declared somewhere in the main file1200 1201../foo/baz/header.h:10:11:1202note: declared somewhere in the header file)";1203  MainLSP.tags = {DiagnosticTag::Unnecessary};1204  MainLSP.data = D.OpaqueData;1205 1206  clangd::Diagnostic NoteInMainLSP;1207  NoteInMainLSP.range = NoteInMain.Range;1208  NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark);1209  NoteInMainLSP.message = R"(Declared somewhere in the main file1210 1211main.cpp:2:3: error: something terrible happened)";1212 1213  ClangdDiagnosticOptions Opts;1214  // Transform diagnostics and check the results.1215  std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;1216  toLSPDiags(D, MainFile, Opts,1217             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {1218               LSPDiags.push_back(1219                   {std::move(LSPDiag),1220                    std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});1221             });1222 1223  EXPECT_THAT(1224      LSPDiags,1225      ElementsAre(Pair(equalToLSPDiag(MainLSP), ElementsAre(equalToFix(F))),1226                  Pair(equalToLSPDiag(NoteInMainLSP), IsEmpty())));1227  EXPECT_EQ(LSPDiags[0].first.code, "undeclared_var_use");1228  EXPECT_EQ(LSPDiags[0].first.source, "clang");1229  EXPECT_EQ(LSPDiags[1].first.code, "");1230  EXPECT_EQ(LSPDiags[1].first.source, "");1231 1232  // Same thing, but don't flatten notes into the main list.1233  LSPDiags.clear();1234  Opts.EmitRelatedLocations = true;1235  toLSPDiags(D, MainFile, Opts,1236             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {1237               LSPDiags.push_back(1238                   {std::move(LSPDiag),1239                    std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});1240             });1241  MainLSP.message = "Something terrible happened (fix available)";1242  DiagnosticRelatedInformation NoteInMainDRI;1243  NoteInMainDRI.message = "Declared somewhere in the main file";1244  NoteInMainDRI.location.range = NoteInMain.Range;1245  NoteInMainDRI.location.uri = MainFile;1246  MainLSP.relatedInformation = {NoteInMainDRI};1247  DiagnosticRelatedInformation NoteInHeaderDRI;1248  NoteInHeaderDRI.message = "Declared somewhere in the header file";1249  NoteInHeaderDRI.location.range = NoteInHeader.Range;1250  NoteInHeaderDRI.location.uri = HeaderFile;1251  MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};1252  EXPECT_THAT(LSPDiags, ElementsAre(Pair(equalToLSPDiag(MainLSP),1253                                         ElementsAre(equalToFix(F)))));1254}1255 1256struct SymbolWithHeader {1257  std::string QName;1258  std::string DeclaringFile;1259  std::string IncludeHeader;1260};1261 1262std::unique_ptr<SymbolIndex>1263buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {1264  SymbolSlab::Builder Slab;1265  for (const auto &S : Syms) {1266    Symbol Sym = cls(S.QName);1267    Sym.Flags |= Symbol::IndexedForCodeCompletion;1268    Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();1269    Sym.Definition.FileURI = S.DeclaringFile.c_str();1270    Sym.IncludeHeaders.emplace_back(S.IncludeHeader, 1, Symbol::Include);1271    Slab.insert(Sym);1272  }1273  return MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());1274}1275 1276TEST(IncludeFixerTest, IncompleteType) {1277  auto TU = TestTU::withHeaderCode("namespace ns { class X; } ns::X *x;");1278  TU.ExtraArgs.push_back("-std=c++20");1279  auto Index = buildIndexWithSymbol(1280      {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}});1281  TU.ExternalIndex = Index.get();1282 1283  std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{1284      {"incomplete_nested_name_spec", "[[ns::X::]]Nested n;"},1285      {"incomplete_base_class", "class Y : [[ns::X]] {};"},1286      {"incomplete_member_access", "auto i = x[[->]]f();"},1287      {"incomplete_type", "auto& [[[]]m] = *x;"},1288      {"init_incomplete_type",1289       "struct C { static int f(ns::X&); }; int i = C::f([[{]]});"},1290      {"bad_cast_incomplete", "auto a = [[static_cast]]<ns::X>(0);"},1291      {"template_nontype_parm_incomplete", "template <ns::X [[foo]]> int a;"},1292      {"typecheck_decl_incomplete_type", "ns::X [[var]];"},1293      {"typecheck_incomplete_tag", "auto i = [[(*x)]]->f();"},1294      {"typecheck_nonviable_condition_incomplete",1295       "struct A { operator ns::X(); } a; const ns::X &[[b]] = a;"},1296      {"invalid_incomplete_type_use", "auto var = [[ns::X()]];"},1297      {"sizeof_alignof_incomplete_or_sizeless_type",1298       "auto s = [[sizeof]](ns::X);"},1299      {"for_range_incomplete_type", "void foo() { for (auto i : [[*]]x ) {} }"},1300      {"func_def_incomplete_result", "ns::X [[func]] () {}"},1301      {"field_incomplete_or_sizeless", "class M { ns::X [[member]]; };"},1302      {"array_incomplete_or_sizeless_type", "auto s = [[(ns::X[]){}]];"},1303      {"call_incomplete_return", "ns::X f(); auto fp = &f; auto z = [[fp()]];"},1304      {"call_function_incomplete_return", "ns::X foo(); auto a = [[foo()]];"},1305      {"call_incomplete_argument", "int m(ns::X); int i = m([[*x]]);"},1306      {"switch_incomplete_class_type", "void a() { [[switch]](*x) {} }"},1307      {"delete_incomplete_class_type", "void f() { [[delete]] *x; }"},1308      {"-Wdelete-incomplete", "void f() { [[delete]] x; }"},1309      {"dereference_incomplete_type",1310       R"cpp(void f() { asm("" : "=r"([[*]]x)::); })cpp"},1311  };1312  for (auto Case : Tests) {1313    Annotations Main(Case.second);1314    TU.Code = Main.code().str() + "\n // error-ok";1315    EXPECT_THAT(1316        TU.build().getDiagnostics(),1317        ElementsAre(AllOf(diagName(Case.first), hasRange(Main.range()),1318                          withFix(Fix(Range{}, "#include \"x.h\"\n",1319                                      "Include \"x.h\" for symbol ns::X")))))1320        << Case.second;1321  }1322}1323 1324TEST(IncludeFixerTest, IncompleteEnum) {1325  Symbol Sym = enm("X");1326  Sym.Flags |= Symbol::IndexedForCodeCompletion;1327  Sym.CanonicalDeclaration.FileURI = Sym.Definition.FileURI = "unittest:///x.h";1328  Sym.IncludeHeaders.emplace_back("\"x.h\"", 1, Symbol::Include);1329  SymbolSlab::Builder Slab;1330  Slab.insert(Sym);1331  auto Index =1332      MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());1333 1334  TestTU TU;1335  TU.ExternalIndex = Index.get();1336  TU.ExtraArgs.push_back("-std=c++20");1337  TU.ExtraArgs.push_back("-fno-ms-compatibility"); // else incomplete enum is OK1338 1339  std::vector<std::pair<llvm::StringRef, llvm::StringRef>> Tests{1340      {"incomplete_enum", "enum class X : int; using enum [[X]];"},1341      {"underlying_type_of_incomplete_enum",1342       "[[__underlying_type]](enum X) i;"},1343  };1344  for (auto Case : Tests) {1345    Annotations Main(Case.second);1346    TU.Code = Main.code().str() + "\n // error-ok";1347    EXPECT_THAT(TU.build().getDiagnostics(),1348                Contains(AllOf(diagName(Case.first), hasRange(Main.range()),1349                               withFix(Fix(Range{}, "#include \"x.h\"\n",1350                                           "Include \"x.h\" for symbol X")))))1351        << Case.second;1352  }1353}1354 1355TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {1356  Annotations Test(R"cpp(// error-ok1357$insert[[]]namespace ns {1358  class X;1359}1360class Y : $base[[public ns::X]] {};1361int main() {1362  ns::X *x;1363  x$access[[->]]f();1364}1365  )cpp");1366  auto TU = TestTU::withCode(Test.code());1367  Symbol Sym = cls("ns::X");1368  Sym.Flags |= Symbol::IndexedForCodeCompletion;1369  Sym.CanonicalDeclaration.FileURI = "unittest:///x.h";1370  Sym.Definition.FileURI = "unittest:///x.cc";1371  Sym.IncludeHeaders.emplace_back("\"x.h\"", 1, Symbol::Include);1372 1373  SymbolSlab::Builder Slab;1374  Slab.insert(Sym);1375  auto Index =1376      MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());1377  TU.ExternalIndex = Index.get();1378 1379  EXPECT_THAT(TU.build().getDiagnostics(),1380              UnorderedElementsAre(1381                  Diag(Test.range("base"), "base class has incomplete type"),1382                  Diag(Test.range("access"),1383                       "member access into incomplete type 'ns::X'")));1384}1385 1386TEST(IncludeFixerTest, Typo) {1387  Annotations Test(R"cpp(// error-ok1388$insert[[]]namespace ns {1389void foo() {1390  $unqualified1[[X]] x;1391  // No fix if the unresolved type is used as specifier. (ns::)X::Nested will be1392  // considered the unresolved type.1393  $unqualified2[[X]]::Nested n;1394}1395struct S : $base[[X]] {};1396}1397void bar() {1398  ns::$qualified1[[X]] x; // ns:: is valid.1399  ns::$qualified2[[X]](); // Error: no member in namespace1400 1401  ::$global[[Global]] glob;1402}1403using Type = ns::$template[[Foo]]<int>;1404  )cpp");1405  auto TU = TestTU::withCode(Test.code());1406  auto Index = buildIndexWithSymbol(1407      {SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""},1408       SymbolWithHeader{"Global", "unittest:///global.h", "\"global.h\""},1409       SymbolWithHeader{"ns::Foo", "unittest:///foo.h", "\"foo.h\""}});1410  TU.ExternalIndex = Index.get();1411 1412  EXPECT_THAT(1413      TU.build().getDiagnostics(),1414      UnorderedElementsAre(1415          AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),1416                diagName("unknown_typename"),1417                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",1418                            "Include \"x.h\" for symbol ns::X"))),1419          Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),1420          AllOf(Diag(Test.range("qualified1"),1421                     "no type named 'X' in namespace 'ns'"),1422                diagName("typename_nested_not_found"),1423                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",1424                            "Include \"x.h\" for symbol ns::X"))),1425          AllOf(Diag(Test.range("qualified2"),1426                     "no member named 'X' in namespace 'ns'"),1427                diagName("no_member"),1428                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",1429                            "Include \"x.h\" for symbol ns::X"))),1430          AllOf(Diag(Test.range("global"),1431                     "no type named 'Global' in the global namespace"),1432                diagName("typename_nested_not_found"),1433                withFix(Fix(Test.range("insert"), "#include \"global.h\"\n",1434                            "Include \"global.h\" for symbol Global"))),1435          AllOf(Diag(Test.range("template"),1436                     "no template named 'Foo' in namespace 'ns'"),1437                diagName("no_member_template"),1438                withFix(Fix(Test.range("insert"), "#include \"foo.h\"\n",1439                            "Include \"foo.h\" for symbol ns::Foo"))),1440          AllOf(Diag(Test.range("base"), "expected class name"),1441                diagName("expected_class_name"),1442                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",1443                            "Include \"x.h\" for symbol ns::X")))));1444}1445 1446TEST(IncludeFixerTest, TypoInMacro) {1447  auto TU = TestTU::withCode(R"cpp(// error-ok1448#define ID(T) T1449X a1;1450ID(X a2);1451ns::X a3;1452ID(ns::X a4);1453namespace ns{};1454ns::X a5;1455ID(ns::X a6);1456)cpp");1457  auto Index = buildIndexWithSymbol(1458      {SymbolWithHeader{"X", "unittest:///x.h", "\"x.h\""},1459       SymbolWithHeader{"ns::X", "unittest:///ns.h", "\"x.h\""}});1460  TU.ExternalIndex = Index.get();1461  // FIXME: -fms-compatibility (which is default on windows) breaks the1462  // ns::X cases when the namespace is undeclared. Find out why!1463  TU.ExtraArgs = {"-fno-ms-compatibility"};1464  EXPECT_THAT(TU.build().getDiagnostics(), Each(withFix(_)));1465}1466 1467TEST(IncludeFixerTest, MultipleMatchedSymbols) {1468  Annotations Test(R"cpp(// error-ok1469$insert[[]]namespace na {1470namespace nb {1471void foo() {1472  $unqualified[[X]] x;1473}1474}1475}1476  )cpp");1477  auto TU = TestTU::withCode(Test.code());1478  auto Index = buildIndexWithSymbol(1479      {SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""},1480       SymbolWithHeader{"na::nb::X", "unittest:///b.h", "\"b.h\""}});1481  TU.ExternalIndex = Index.get();1482 1483  EXPECT_THAT(TU.build().getDiagnostics(),1484              UnorderedElementsAre(AllOf(1485                  Diag(Test.range("unqualified"), "unknown type name 'X'"),1486                  diagName("unknown_typename"),1487                  withFix(Fix(Test.range("insert"), "#include \"a.h\"\n",1488                              "Include \"a.h\" for symbol na::X"),1489                          Fix(Test.range("insert"), "#include \"b.h\"\n",1490                              "Include \"b.h\" for symbol na::nb::X")))));1491}1492 1493TEST(IncludeFixerTest, NoCrashMemberAccess) {1494  Annotations Test(R"cpp(// error-ok1495    struct X { int  xyz; };1496    void g() { X x; x.$[[xy]]; }1497  )cpp");1498  auto TU = TestTU::withCode(Test.code());1499  auto Index = buildIndexWithSymbol(1500      SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""});1501  TU.ExternalIndex = Index.get();1502 1503  EXPECT_THAT(1504      TU.build().getDiagnostics(),1505      UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));1506}1507 1508TEST(IncludeFixerTest, UseCachedIndexResults) {1509  // As index results for the identical request are cached, more than 5 fixes1510  // are generated.1511  Annotations Test(R"cpp(// error-ok1512$insert[[]]void foo() {1513  $x1[[X]] x;1514  $x2[[X]] x;1515  $x3[[X]] x;1516  $x4[[X]] x;1517  $x5[[X]] x;1518  $x6[[X]] x;1519  $x7[[X]] x;1520}1521 1522class X;1523void bar(X *x) {1524  x$a1[[->]]f();1525  x$a2[[->]]f();1526  x$a3[[->]]f();1527  x$a4[[->]]f();1528  x$a5[[->]]f();1529  x$a6[[->]]f();1530  x$a7[[->]]f();1531}1532  )cpp");1533  auto TU = TestTU::withCode(Test.code());1534  auto Index =1535      buildIndexWithSymbol(SymbolWithHeader{"X", "unittest:///a.h", "\"a.h\""});1536  TU.ExternalIndex = Index.get();1537 1538  auto Parsed = TU.build();1539  for (const auto &D : Parsed.getDiagnostics()) {1540    if (D.Fixes.size() != 1) {1541      ADD_FAILURE() << "D.Fixes.size() != 1";1542      continue;1543    }1544    EXPECT_EQ(D.Fixes[0].Message, std::string("Include \"a.h\" for symbol X"));1545  }1546}1547 1548TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {1549  Annotations Test(R"cpp(// error-ok1550$insert[[]]namespace ns {1551}1552void g() {  ns::$[[scope]]::X_Y();  }1553  )cpp");1554  TestTU TU;1555  TU.Code = std::string(Test.code());1556  // FIXME: Figure out why this is needed and remove it, PR43662.1557  TU.ExtraArgs.push_back("-fno-ms-compatibility");1558  auto Index = buildIndexWithSymbol(1559      SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});1560  TU.ExternalIndex = Index.get();1561 1562  EXPECT_THAT(1563      TU.build().getDiagnostics(),1564      UnorderedElementsAre(1565          AllOf(Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),1566                diagName("no_member"),1567                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",1568                            "Include \"x.h\" for symbol ns::scope::X_Y")))));1569}1570 1571TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {1572  Annotations Test(R"cpp(// error-ok1573$insert[[]]namespace clang {1574void f() {1575  // "clangd::" will be corrected to "clang::" by Sema.1576  $q1[[clangd]]::$x[[X]] x;1577  $q2[[clangd]]::$ns[[ns]]::Y y;1578}1579}1580  )cpp");1581  TestTU TU;1582  TU.Code = std::string(Test.code());1583  // FIXME: Figure out why this is needed and remove it, PR43662.1584  TU.ExtraArgs.push_back("-fno-ms-compatibility");1585  auto Index = buildIndexWithSymbol(1586      {SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},1587       SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});1588  TU.ExternalIndex = Index.get();1589 1590  EXPECT_THAT(1591      TU.build().getDiagnostics(),1592      UnorderedElementsAre(1593          AllOf(Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "1594                                       "did you mean 'clang'?"),1595                diagName("undeclared_var_use_suggest"),1596                withFix(_, // change clangd to clang1597                        Fix(Test.range("insert"), "#include \"x.h\"\n",1598                            "Include \"x.h\" for symbol clang::clangd::X"))),1599          AllOf(Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),1600                diagName("typename_nested_not_found"),1601                withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",1602                            "Include \"x.h\" for symbol clang::clangd::X"))),1603          AllOf(1604              Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "1605                                     "did you mean 'clang'?"),1606              diagName("undeclared_var_use_suggest"),1607              withFix(_, // change clangd to clang1608                      Fix(Test.range("insert"), "#include \"y.h\"\n",1609                          "Include \"y.h\" for symbol clang::clangd::ns::Y"))),1610          AllOf(Diag(Test.range("ns"),1611                     "no member named 'ns' in namespace 'clang'"),1612                diagName("no_member"),1613                withFix(1614                    Fix(Test.range("insert"), "#include \"y.h\"\n",1615                        "Include \"y.h\" for symbol clang::clangd::ns::Y")))));1616}1617 1618TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {1619  Annotations Test(R"cpp(// error-ok1620$insert[[]]namespace a {}1621namespace b = a;1622namespace c {1623  b::$[[X]] x;1624}1625  )cpp");1626  auto TU = TestTU::withCode(Test.code());1627  auto Index = buildIndexWithSymbol(1628      SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});1629  TU.ExternalIndex = Index.get();1630 1631  EXPECT_THAT(TU.build().getDiagnostics(),1632              UnorderedElementsAre(AllOf(1633                  Diag(Test.range(), "no type named 'X' in namespace 'a'"),1634                  diagName("typename_nested_not_found"),1635                  withFix(Fix(Test.range("insert"), "#include \"x.h\"\n",1636                              "Include \"x.h\" for symbol a::X")))));1637}1638 1639TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {1640  Annotations Test(R"cpp(1641    template <typename T> struct Templ {1642      template <typename U>1643      typename U::type operator=(const U &);1644    };1645 1646    struct A {1647      Templ<char> s;1648      A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.1649    };1650  )cpp");1651 1652  auto TU = TestTU::withCode(Test.code());1653  auto Index = buildIndexWithSymbol({});1654  TU.ExternalIndex = Index.get();1655 1656  EXPECT_THAT(1657      TU.build().getDiagnostics(),1658      ElementsAre(Diag(Test.range(), "use of undeclared identifier 'a'")));1659}1660 1661TEST(IncludeFixerTest, HeaderNamedInDiag) {1662  Annotations Test(R"cpp(1663    $insert[[]]int main() {1664      [[printf]]("");1665    }1666  )cpp");1667  auto TU = TestTU::withCode(Test.code());1668  TU.ExtraArgs = {"-xc", "-std=c99",1669                  "-Wno-error=implicit-function-declaration"};1670  auto Index = buildIndexWithSymbol({});1671  TU.ExternalIndex = Index.get();1672 1673  EXPECT_THAT(1674      TU.build().getDiagnostics(),1675      ElementsAre(AllOf(1676          Diag(Test.range(), "call to undeclared library function 'printf' "1677                             "with type 'int (const char *, ...)'; ISO C99 "1678                             "and later do not support implicit function "1679                             "declarations"),1680          withFix(Fix(Test.range("insert"), "#include <stdio.h>\n",1681                      "Include <stdio.h> for symbol printf")))));1682 1683  TU.ExtraArgs = {"-xc", "-std=c89"};1684  EXPECT_THAT(1685      TU.build().getDiagnostics(),1686      ElementsAre(AllOf(1687          Diag(Test.range(), "implicitly declaring library function 'printf' "1688                             "with type 'int (const char *, ...)'"),1689          withFix(Fix(Test.range("insert"), "#include <stdio.h>\n",1690                      "Include <stdio.h> for symbol printf")))));1691}1692 1693TEST(IncludeFixerTest, CImplicitFunctionDecl) {1694  Annotations Test("void x() { [[foo]](); }");1695  auto TU = TestTU::withCode(Test.code());1696  TU.Filename = "test.c";1697  TU.ExtraArgs = {"-std=c99", "-Wno-error=implicit-function-declaration"};1698 1699  Symbol Sym = func("foo");1700  Sym.Flags |= Symbol::IndexedForCodeCompletion;1701  Sym.CanonicalDeclaration.FileURI = "unittest:///foo.h";1702  Sym.IncludeHeaders.emplace_back("\"foo.h\"", 1, Symbol::Include);1703 1704  SymbolSlab::Builder Slab;1705  Slab.insert(Sym);1706  auto Index =1707      MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());1708  TU.ExternalIndex = Index.get();1709 1710  EXPECT_THAT(1711      TU.build().getDiagnostics(),1712      ElementsAre(AllOf(1713          Diag(Test.range(),1714               "call to undeclared function 'foo'; ISO C99 and later do not "1715               "support implicit function declarations"),1716          withFix(Fix(Range{}, "#include \"foo.h\"\n",1717                      "Include \"foo.h\" for symbol foo")))));1718 1719  TU.ExtraArgs = {"-std=c89", "-Wall"};1720  EXPECT_THAT(TU.build().getDiagnostics(),1721              ElementsAre(AllOf(1722                  Diag(Test.range(), "implicit declaration of function 'foo'"),1723                  withFix(Fix(Range{}, "#include \"foo.h\"\n",1724                              "Include \"foo.h\" for symbol foo")))));1725}1726 1727TEST(DiagsInHeaders, DiagInsideHeader) {1728  Annotations Main(R"cpp(1729    #include [["a.h"]]1730    void foo() {})cpp");1731  Annotations Header("[[no_type_spec]]; // error-ok");1732  TestTU TU = TestTU::withCode(Main.code());1733  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};1734  EXPECT_THAT(TU.build().getDiagnostics(),1735              UnorderedElementsAre(AllOf(1736                  Diag(Main.range(), "in included file: a type specifier is "1737                                     "required for all declarations"),1738                  withNote(Diag(Header.range(), "error occurred here")))));1739}1740 1741TEST(DiagsInHeaders, DiagInTransitiveInclude) {1742  Annotations Main(R"cpp(1743    #include [["a.h"]]1744    void foo() {})cpp");1745  TestTU TU = TestTU::withCode(Main.code());1746  TU.AdditionalFiles = {{"a.h", "#include \"b.h\""},1747                        {"b.h", "no_type_spec; // error-ok"}};1748  EXPECT_THAT(TU.build().getDiagnostics(),1749              UnorderedElementsAre(Diag(Main.range(),1750                                        "in included file: a type specifier is "1751                                        "required for all declarations")));1752}1753 1754TEST(DiagsInHeaders, DiagInMultipleHeaders) {1755  Annotations Main(R"cpp(1756    #include $a[["a.h"]]1757    #include $b[["b.h"]]1758    void foo() {})cpp");1759  TestTU TU = TestTU::withCode(Main.code());1760  TU.AdditionalFiles = {{"a.h", "no_type_spec; // error-ok"},1761                        {"b.h", "no_type_spec; // error-ok"}};1762  EXPECT_THAT(TU.build().getDiagnostics(),1763              UnorderedElementsAre(1764                  Diag(Main.range("a"), "in included file: a type specifier is "1765                                        "required for all declarations"),1766                  Diag(Main.range("b"), "in included file: a type specifier is "1767                                        "required for all declarations")));1768}1769 1770TEST(DiagsInHeaders, PreferExpansionLocation) {1771  Annotations Main(R"cpp(1772    #include [["a.h"]]1773    #include "b.h"1774    void foo() {})cpp");1775  TestTU TU = TestTU::withCode(Main.code());1776  TU.AdditionalFiles = {1777      {"a.h", "#include \"b.h\"\n"},1778      {"b.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};1779  EXPECT_THAT(TU.build().getDiagnostics(),1780              Contains(Diag(Main.range(), "in included file: a type specifier "1781                                          "is required for all declarations")));1782}1783 1784TEST(DiagsInHeaders, PreferExpansionLocationMacros) {1785  Annotations Main(R"cpp(1786    #define X1787    #include "a.h"1788    #undef X1789    #include [["b.h"]]1790    void foo() {})cpp");1791  TestTU TU = TestTU::withCode(Main.code());1792  TU.AdditionalFiles = {1793      {"a.h", "#include \"c.h\"\n"},1794      {"b.h", "#include \"c.h\"\n"},1795      {"c.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};1796  EXPECT_THAT(TU.build().getDiagnostics(),1797              UnorderedElementsAre(Diag(Main.range(),1798                                        "in included file: a type specifier is "1799                                        "required for all declarations")));1800}1801 1802TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {1803  Annotations Main(R"cpp(1804    #include [["a.h"]]1805    #include "b.h"1806    void foo() {})cpp");1807  TestTU TU = TestTU::withCode(Main.code());1808  TU.AdditionalFiles = {{"a.h", "#include \"c.h\"\n"},1809                        {"b.h", "#include \"c.h\"\n"},1810                        {"c.h", R"cpp(1811      #ifndef X1812      #define X1813      no_type_spec_0; // error-ok1814      no_type_spec_1;1815      no_type_spec_2;1816      no_type_spec_3;1817      no_type_spec_4;1818      no_type_spec_5;1819      no_type_spec_6;1820      no_type_spec_7;1821      no_type_spec_8;1822      no_type_spec_9;1823      no_type_spec_10;1824      #endif)cpp"}};1825  EXPECT_THAT(TU.build().getDiagnostics(),1826              UnorderedElementsAre(Diag(Main.range(),1827                                        "in included file: a type specifier is "1828                                        "required for all declarations")));1829}1830 1831TEST(DiagsInHeaders, OnlyErrorOrFatal) {1832  Annotations Main(R"cpp(1833    #include [["a.h"]]1834    void foo() {})cpp");1835  Annotations Header(R"cpp(1836    [[no_type_spec]]; // error-ok1837    int x = 5/0;)cpp");1838  TestTU TU = TestTU::withCode(Main.code());1839  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};1840  EXPECT_THAT(TU.build().getDiagnostics(),1841              UnorderedElementsAre(AllOf(1842                  Diag(Main.range(), "in included file: a type specifier is "1843                                     "required for all declarations"),1844                  withNote(Diag(Header.range(), "error occurred here")))));1845}1846 1847TEST(DiagsInHeaders, OnlyDefaultErrorOrFatal) {1848  Annotations Main(R"cpp(1849    #include [["a.h"]] // get unused "foo" warning when building preamble.1850    )cpp");1851  Annotations Header(R"cpp(1852    namespace { void foo() {} }1853    void func() {foo();} ;)cpp");1854  TestTU TU = TestTU::withCode(Main.code());1855  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};1856  // promote warnings to errors.1857  TU.ExtraArgs = {"-Werror", "-Wunused"};1858  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());1859}1860 1861TEST(DiagsInHeaders, FromNonWrittenSources) {1862  Annotations Main(R"cpp(1863    #include [["a.h"]]1864    void foo() {})cpp");1865  Annotations Header(R"cpp(1866    int x = 5/0;1867    int b = [[FOO]]; // error-ok)cpp");1868  TestTU TU = TestTU::withCode(Main.code());1869  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};1870  TU.ExtraArgs = {"-DFOO=NOOO"};1871  EXPECT_THAT(TU.build().getDiagnostics(),1872              UnorderedElementsAre(AllOf(1873                  Diag(Main.range(),1874                       "in included file: use of undeclared identifier 'NOOO'"),1875                  withNote(Diag(Header.range(), "error occurred here")))));1876}1877 1878TEST(DiagsInHeaders, ErrorFromMacroExpansion) {1879  Annotations Main(R"cpp(1880  void bar() {1881    int fo; // error-ok1882    #include [["a.h"]]1883  })cpp");1884  Annotations Header(R"cpp(1885  #define X foo1886  X;)cpp");1887  TestTU TU = TestTU::withCode(Main.code());1888  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};1889  EXPECT_THAT(TU.build().getDiagnostics(),1890              UnorderedElementsAre(1891                  Diag(Main.range(), "in included file: use of undeclared "1892                                     "identifier 'foo'; did you mean 'fo'?")));1893}1894 1895TEST(DiagsInHeaders, ErrorFromMacroArgument) {1896  Annotations Main(R"cpp(1897  void bar() {1898    int fo; // error-ok1899    #include [["a.h"]]1900  })cpp");1901  Annotations Header(R"cpp(1902  #define X(arg) arg1903  X(foo);)cpp");1904  TestTU TU = TestTU::withCode(Main.code());1905  TU.AdditionalFiles = {{"a.h", std::string(Header.code())}};1906  EXPECT_THAT(TU.build().getDiagnostics(),1907              UnorderedElementsAre(1908                  Diag(Main.range(), "in included file: use of undeclared "1909                                     "identifier 'foo'; did you mean 'fo'?")));1910}1911 1912TEST(IgnoreDiags, FromNonWrittenInclude) {1913  TestTU TU;1914  TU.ExtraArgs.push_back("--include=a.h");1915  TU.AdditionalFiles = {{"a.h", "void main();"}};1916  // The diagnostic "main must return int" is from the header, we don't attempt1917  // to render it in the main file as there is no written location there.1918  EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());1919}1920 1921TEST(ToLSPDiag, RangeIsInMain) {1922  ClangdDiagnosticOptions Opts;1923  clangd::Diag D;1924  D.Range = {pos(1, 2), pos(3, 4)};1925  D.Notes.emplace_back();1926  Note &N = D.Notes.back();1927  N.Range = {pos(2, 3), pos(3, 4)};1928 1929  D.InsideMainFile = true;1930  N.InsideMainFile = false;1931  toLSPDiags(D, {}, Opts,1932             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {1933               EXPECT_EQ(LSPDiag.range, D.Range);1934             });1935 1936  D.InsideMainFile = false;1937  N.InsideMainFile = true;1938  toLSPDiags(D, {}, Opts,1939             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {1940               EXPECT_EQ(LSPDiag.range, N.Range);1941             });1942}1943 1944TEST(ParsedASTTest, ModuleSawDiag) {1945  TestTU TU;1946 1947  auto AST = TU.build();1948        #if 01949  EXPECT_THAT(AST.getDiagnostics(),1950              testing::Contains(Diag(Code.range(), KDiagMsg.str())));1951        #endif1952}1953 1954TEST(Preamble, EndsOnNonEmptyLine) {1955  TestTU TU;1956  TU.ExtraArgs = {"-Wnewline-eof"};1957 1958  {1959    TU.Code = "#define FOO\n  void bar();\n";1960    auto AST = TU.build();1961    EXPECT_THAT(AST.getDiagnostics(), IsEmpty());1962  }1963  {1964    Annotations Code("#define FOO[[]]");1965    TU.Code = Code.code().str();1966    auto AST = TU.build();1967    EXPECT_THAT(1968        AST.getDiagnostics(),1969        testing::Contains(Diag(Code.range(), "no newline at end of file")));1970  }1971}1972 1973TEST(Diagnostics, Tags) {1974  TestTU TU;1975  TU.ExtraArgs = {"-Wunused", "-Wdeprecated"};1976  Annotations Test(R"cpp(1977  void bar() __attribute__((deprecated));1978  void foo() {1979    int $unused[[x]];1980    $deprecated[[bar]]();1981  })cpp");1982  TU.Code = Test.code().str();1983  EXPECT_THAT(TU.build().getDiagnostics(),1984              UnorderedElementsAre(1985                  AllOf(Diag(Test.range("unused"), "unused variable 'x'"),1986                        withTag(DiagnosticTag::Unnecessary)),1987                  AllOf(Diag(Test.range("deprecated"), "'bar' is deprecated"),1988                        withTag(DiagnosticTag::Deprecated))));1989 1990  Test = Annotations(R"cpp(1991    $typedef[[typedef int INT]];1992  )cpp");1993  TU.Code = Test.code();1994  TU.ClangTidyProvider = addTidyChecks("modernize-use-using");1995  EXPECT_THAT(1996      TU.build().getDiagnostics(),1997      ifTidyChecks(UnorderedElementsAre(1998          AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),1999                withTag(DiagnosticTag::Deprecated)))));2000}2001 2002TEST(Diagnostics, TidyDiagsArentAffectedFromWerror) {2003  TestTU TU;2004  TU.ExtraArgs = {"-Werror"};2005  Annotations Test(R"cpp($typedef[[typedef int INT]]; // error-ok)cpp");2006  TU.Code = Test.code().str();2007  TU.ClangTidyProvider = addTidyChecks("modernize-use-using");2008  EXPECT_THAT(2009      TU.build().getDiagnostics(),2010      ifTidyChecks(UnorderedElementsAre(2011          AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),2012                // Make sure severity for clang-tidy finding isn't bumped to2013                // error due to Werror in compile flags.2014                diagSeverity(DiagnosticsEngine::Warning)))));2015 2016  TU.ClangTidyProvider =2017      addTidyChecks("modernize-use-using", /*WarningsAsErrors=*/"modernize-*");2018  EXPECT_THAT(2019      TU.build().getDiagnostics(),2020      ifTidyChecks(UnorderedElementsAre(2021          AllOf(Diag(Test.range("typedef"), "use 'using' instead of 'typedef'"),2022                // Unless bumped explicitly with WarnAsError.2023                diagSeverity(DiagnosticsEngine::Error)))));2024}2025 2026TEST(Diagnostics, DeprecatedDiagsAreHints) {2027  ClangdDiagnosticOptions Opts;2028  std::optional<clangd::Diagnostic> Diag;2029  clangd::Diag D;2030  D.Range = {pos(1, 2), pos(3, 4)};2031  D.InsideMainFile = true;2032 2033  // Downgrade warnings with deprecated tags to remark.2034  D.Tags = {Deprecated};2035  D.Severity = DiagnosticsEngine::Warning;2036  toLSPDiags(D, {}, Opts,2037             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {2038               Diag = std::move(LSPDiag);2039             });2040  EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Remark));2041  Diag.reset();2042 2043  // Preserve errors.2044  D.Severity = DiagnosticsEngine::Error;2045  toLSPDiags(D, {}, Opts,2046             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {2047               Diag = std::move(LSPDiag);2048             });2049  EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Error));2050  Diag.reset();2051 2052  // No-op without tag.2053  D.Tags = {};2054  D.Severity = DiagnosticsEngine::Warning;2055  toLSPDiags(D, {}, Opts,2056             [&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {2057               Diag = std::move(LSPDiag);2058             });2059  EXPECT_EQ(Diag->severity, getSeverity(DiagnosticsEngine::Warning));2060}2061 2062TEST(DiagnosticsTest, IncludeCleaner) {2063  Annotations Test(R"cpp(2064$fix[[  $diag[[#include "unused.h"]]2065]]2066  #include "used.h"2067 2068  #include "ignore.h"2069 2070  #include <system_header.h>2071 2072  void foo() {2073    used();2074  }2075  )cpp");2076  TestTU TU;2077  TU.Code = Test.code().str();2078  TU.AdditionalFiles["unused.h"] = R"cpp(2079    #pragma once2080    void unused() {}2081  )cpp";2082  TU.AdditionalFiles["used.h"] = R"cpp(2083    #pragma once2084    void used() {}2085  )cpp";2086  TU.AdditionalFiles["ignore.h"] = R"cpp(2087    #pragma once2088    void ignore() {}2089  )cpp";2090  TU.AdditionalFiles["system/system_header.h"] = "";2091  TU.ExtraArgs = {"-isystem" + testPath("system")};2092  Config Cfg;2093  Cfg.Diagnostics.UnusedIncludes = Config::IncludesPolicy::Strict;2094  // Set filtering.2095  Cfg.Diagnostics.Includes.IgnoreHeader.emplace_back(2096      [](llvm::StringRef Header) { return Header.ends_with("ignore.h"); });2097  WithContextValue WithCfg(Config::Key, std::move(Cfg));2098  auto AST = TU.build();2099  EXPECT_THAT(2100      AST.getDiagnostics(),2101      Contains(AllOf(2102          Diag(Test.range("diag"),2103               "included header unused.h is not used directly"),2104          withTag(DiagnosticTag::Unnecessary), diagSource(Diag::Clangd),2105          withFix(Fix(Test.range("fix"), "", "remove #include directive")))));2106  auto &Diag = AST.getDiagnostics().front();2107  EXPECT_THAT(getDiagnosticDocURI(Diag.Source, Diag.ID, Diag.Name),2108              llvm::ValueIs(Not(IsEmpty())));2109  Cfg.Diagnostics.SuppressAll = true;2110  WithContextValue SuppressAllWithCfg(Config::Key, std::move(Cfg));2111  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());2112  Cfg.Diagnostics.SuppressAll = false;2113  Cfg.Diagnostics.Suppress = {"unused-includes"};2114  WithContextValue SuppressFilterWithCfg(Config::Key, std::move(Cfg));2115  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());2116}2117 2118TEST(DiagnosticsTest, FixItFromHeader) {2119  llvm::StringLiteral Header(R"cpp(2120    void foo(int *);2121    void foo(int *, int);)cpp");2122  Annotations Source(R"cpp(2123  /*error-ok*/2124    void bar() {2125      int x;2126      $diag[[foo]]($fix[[]]x, 1);2127    })cpp");2128  TestTU TU;2129  TU.Code = Source.code().str();2130  TU.HeaderCode = Header.str();2131  EXPECT_THAT(2132      TU.build().getDiagnostics(),2133      UnorderedElementsAre(AllOf(2134          Diag(Source.range("diag"), "no matching function for call to 'foo'"),2135          withFix(Fix(Source.range("fix"), "&",2136                      "candidate function not viable: no known conversion from "2137                      "'int' to 'int *' for 1st argument; take the address of "2138                      "the argument with &")))));2139}2140 2141TEST(DiagnosticsTest, UnusedInHeader) {2142  // Clang diagnoses unused static inline functions outside headers.2143  auto TU = TestTU::withCode("static inline void foo(void) {}");2144  TU.ExtraArgs.push_back("-Wunused-function");2145  TU.Filename = "test.c";2146  EXPECT_THAT(TU.build().getDiagnostics(),2147              ElementsAre(withID(diag::warn_unused_function)));2148  // Sema should recognize a *.h file open in clangd as a header.2149  // https://github.com/clangd/vscode-clangd/issues/3602150  TU.Filename = "test.h";2151  EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());2152}2153 2154} // namespace2155} // namespace clangd2156} // namespace clang2157