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