mirror of
https://github.com/Gericom/teak-llvm.git
synced 2025-06-19 11:35:51 -04:00

Summary: The historic behavior of TestTU is to gather diagnostics and otherwise ignore them. So if a test has a syntax error, and doesn't assert diagnostics, it silently misbehaves. This can be annoying when developing tests, as evidenced by various tests gaining "assert no diagnostics" where that's not really the point of the test. This patch aims to make that default behavior. For the first error (not warning), TestTU will call ADD_FAILURE(). This can be suppressed with a comment containing "error-ok". For now that will suppress any errors in the TU. We can make this stricter later -verify style. (-verify itself is hard to reuse because of DiagnosticConsumer interfaces...) A magic-comment was chosen over a TestTU option because of table-driven tests. In addition to the behavior change, this patch: - adds //error-ok where we're knowingly testing invalid code (e.g. for diagnostics, crash-resilience, or token-level tests) - fixes a bunch of errors in the checked-in tests, mostly trivial (missing ;) - removes a bunch of now-redundant instances of "assert no diagnostics" Reviewers: kadircet Subscribers: ilya-biryukov, MaskRay, jkorous, arphaman, usaxena95, cfe-commits Tags: #clang Differential Revision: https://reviews.llvm.org/D73199
1050 lines
37 KiB
C++
1050 lines
37 KiB
C++
//===--- DiagnosticsTests.cpp ------------------------------------*- C++-*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "Annotations.h"
|
|
#include "Diagnostics.h"
|
|
#include "ParsedAST.h"
|
|
#include "Path.h"
|
|
#include "Protocol.h"
|
|
#include "SourceCode.h"
|
|
#include "TestFS.h"
|
|
#include "TestIndex.h"
|
|
#include "TestTU.h"
|
|
#include "index/MemIndex.h"
|
|
#include "clang/Basic/Diagnostic.h"
|
|
#include "clang/Basic/DiagnosticSema.h"
|
|
#include "llvm/Support/ScopedPrinter.h"
|
|
#include "llvm/Support/TargetSelect.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
#include <algorithm>
|
|
|
|
namespace clang {
|
|
namespace clangd {
|
|
namespace {
|
|
|
|
using ::testing::_;
|
|
using ::testing::ElementsAre;
|
|
using ::testing::Field;
|
|
using ::testing::IsEmpty;
|
|
using ::testing::Pair;
|
|
using ::testing::UnorderedElementsAre;
|
|
|
|
::testing::Matcher<const Diag &> WithFix(::testing::Matcher<Fix> FixMatcher) {
|
|
return Field(&Diag::Fixes, ElementsAre(FixMatcher));
|
|
}
|
|
|
|
::testing::Matcher<const Diag &> WithFix(::testing::Matcher<Fix> FixMatcher1,
|
|
::testing::Matcher<Fix> FixMatcher2) {
|
|
return Field(&Diag::Fixes, UnorderedElementsAre(FixMatcher1, FixMatcher2));
|
|
}
|
|
|
|
::testing::Matcher<const Diag &>
|
|
WithNote(::testing::Matcher<Note> NoteMatcher) {
|
|
return Field(&Diag::Notes, ElementsAre(NoteMatcher));
|
|
}
|
|
|
|
MATCHER_P2(Diag, Range, Message,
|
|
"Diag at " + llvm::to_string(Range) + " = [" + Message + "]") {
|
|
return arg.Range == Range && arg.Message == Message;
|
|
}
|
|
|
|
MATCHER_P3(Fix, Range, Replacement, Message,
|
|
"Fix " + llvm::to_string(Range) + " => " +
|
|
::testing::PrintToString(Replacement) + " = [" + Message + "]") {
|
|
return arg.Message == Message && arg.Edits.size() == 1 &&
|
|
arg.Edits[0].range == Range && arg.Edits[0].newText == Replacement;
|
|
}
|
|
|
|
MATCHER_P(FixMessage, Message, "") {
|
|
return arg.Message == Message;
|
|
}
|
|
|
|
MATCHER_P(EqualToLSPDiag, LSPDiag,
|
|
"LSP diagnostic " + llvm::to_string(LSPDiag)) {
|
|
if (toJSON(arg) != toJSON(LSPDiag)) {
|
|
*result_listener << llvm::formatv("expected:\n{0:2}\ngot\n{1:2}",
|
|
toJSON(LSPDiag), toJSON(arg))
|
|
.str();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
MATCHER_P(DiagSource, S, "") { return arg.Source == S; }
|
|
MATCHER_P(DiagName, N, "") { return arg.Name == N; }
|
|
MATCHER_P(DiagSeverity, S, "") { return arg.Severity == S; }
|
|
|
|
MATCHER_P(EqualToFix, Fix, "LSP fix " + llvm::to_string(Fix)) {
|
|
if (arg.Message != Fix.Message)
|
|
return false;
|
|
if (arg.Edits.size() != Fix.Edits.size())
|
|
return false;
|
|
for (std::size_t I = 0; I < arg.Edits.size(); ++I) {
|
|
if (arg.Edits[I].range != Fix.Edits[I].range ||
|
|
arg.Edits[I].newText != Fix.Edits[I].newText)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Helper function to make tests shorter.
|
|
Position pos(int line, int character) {
|
|
Position Res;
|
|
Res.line = line;
|
|
Res.character = character;
|
|
return Res;
|
|
}
|
|
|
|
TEST(DiagnosticsTest, DiagnosticRanges) {
|
|
// Check we report correct ranges, including various edge-cases.
|
|
Annotations Test(R"cpp(
|
|
// error-ok
|
|
namespace test{};
|
|
void $decl[[foo]]();
|
|
class T{$explicit[[]]$constructor[[T]](int a);};
|
|
int main() {
|
|
$typo[[go\
|
|
o]]();
|
|
foo()$semicolon[[]]//with comments
|
|
$unk[[unknown]]();
|
|
double $type[[bar]] = "foo";
|
|
struct Foo { int x; }; Foo a;
|
|
a.$nomember[[y]];
|
|
test::$nomembernamespace[[test]];
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
TU.ClangTidyChecks = "-*,google-explicit-constructor";
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
ElementsAre(
|
|
// This range spans lines.
|
|
AllOf(Diag(Test.range("typo"),
|
|
"use of undeclared identifier 'goo'; did you mean 'foo'?"),
|
|
DiagSource(Diag::Clang), DiagName("undeclared_var_use_suggest"),
|
|
WithFix(
|
|
Fix(Test.range("typo"), "foo", "change 'go\\…' to 'foo'")),
|
|
// This is a pretty normal range.
|
|
WithNote(Diag(Test.range("decl"), "'foo' declared here"))),
|
|
// This range is zero-width and insertion. Therefore make sure we are
|
|
// not expanding it into other tokens. Since we are not going to
|
|
// replace those.
|
|
AllOf(Diag(Test.range("semicolon"), "expected ';' after expression"),
|
|
WithFix(Fix(Test.range("semicolon"), ";", "insert ';'"))),
|
|
// This range isn't provided by clang, we expand to the token.
|
|
Diag(Test.range("unk"), "use of undeclared identifier 'unknown'"),
|
|
Diag(Test.range("type"),
|
|
"cannot initialize a variable of type 'double' with an lvalue "
|
|
"of type 'const char [4]'"),
|
|
Diag(Test.range("nomember"), "no member named 'y' in 'Foo'"),
|
|
Diag(Test.range("nomembernamespace"),
|
|
"no member named 'test' in namespace 'test'"),
|
|
// We make sure here that the entire token is highlighted
|
|
AllOf(Diag(Test.range("constructor"),
|
|
"single-argument constructors must be marked explicit to "
|
|
"avoid unintentional implicit conversions"),
|
|
WithFix(Fix(Test.range("explicit"), "explicit ",
|
|
"insert 'explicit '")))));
|
|
}
|
|
|
|
TEST(DiagnosticsTest, FlagsMatter) {
|
|
Annotations Test("[[void]] main() {} // error-ok");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
|
|
WithFix(Fix(Test.range(), "int",
|
|
"change 'void' to 'int'")))));
|
|
// Same code built as C gets different diagnostics.
|
|
TU.Filename = "Plain.c";
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
ElementsAre(AllOf(
|
|
Diag(Test.range(), "return type of 'main' is not 'int'"),
|
|
WithFix(Fix(Test.range(), "int", "change return type to 'int'")))));
|
|
}
|
|
|
|
TEST(DiagnosticsTest, DiagnosticPreamble) {
|
|
Annotations Test(R"cpp(
|
|
#include $[["not-found.h"]] // error-ok
|
|
)cpp");
|
|
|
|
auto TU = TestTU::withCode(Test.code());
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
ElementsAre(::testing::AllOf(
|
|
Diag(Test.range(), "'not-found.h' file not found"),
|
|
DiagSource(Diag::Clang), DiagName("pp_file_not_found"))));
|
|
}
|
|
|
|
TEST(DiagnosticsTest, DeduplicatedClangTidyDiagnostics) {
|
|
Annotations Test(R"cpp(
|
|
float foo = [[0.1f]];
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
// Enable alias clang-tidy checks, these check emits the same diagnostics
|
|
// (except the check name).
|
|
TU.ClangTidyChecks = "-*, readability-uppercase-literal-suffix, "
|
|
"hicpp-uppercase-literal-suffix";
|
|
// Verify that we filter out the duplicated diagnostic message.
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(::testing::AllOf(
|
|
Diag(Test.range(),
|
|
"floating point literal has suffix 'f', which is not uppercase"),
|
|
DiagSource(Diag::ClangTidy))));
|
|
|
|
Test = Annotations(R"cpp(
|
|
template<typename T>
|
|
void func(T) {
|
|
float f = [[0.3f]];
|
|
}
|
|
void k() {
|
|
func(123);
|
|
func(2.0);
|
|
}
|
|
)cpp");
|
|
TU.Code = Test.code();
|
|
// The check doesn't handle template instantiations which ends up emitting
|
|
// duplicated messages, verify that we deduplicate them.
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(::testing::AllOf(
|
|
Diag(Test.range(),
|
|
"floating point literal has suffix 'f', which is not uppercase"),
|
|
DiagSource(Diag::ClangTidy))));
|
|
}
|
|
|
|
TEST(DiagnosticsTest, ClangTidy) {
|
|
Annotations Test(R"cpp(
|
|
#include $deprecated[["assert.h"]]
|
|
|
|
#define $macrodef[[SQUARE]](X) (X)*(X)
|
|
int $main[[main]]() {
|
|
int y = 4;
|
|
return SQUARE($macroarg[[++]]y);
|
|
return $doubled[[sizeof]](sizeof(int));
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
TU.HeaderFilename = "assert.h"; // Suppress "not found" error.
|
|
TU.ClangTidyChecks =
|
|
"-*, bugprone-sizeof-expression, bugprone-macro-repeated-side-effects, "
|
|
"modernize-deprecated-headers, modernize-use-trailing-return-type";
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
AllOf(Diag(Test.range("deprecated"),
|
|
"inclusion of deprecated C++ header 'assert.h'; consider "
|
|
"using 'cassert' instead"),
|
|
DiagSource(Diag::ClangTidy),
|
|
DiagName("modernize-deprecated-headers"),
|
|
WithFix(Fix(Test.range("deprecated"), "<cassert>",
|
|
"change '\"assert.h\"' to '<cassert>'"))),
|
|
Diag(Test.range("doubled"),
|
|
"suspicious usage of 'sizeof(sizeof(...))'"),
|
|
AllOf(
|
|
Diag(Test.range("macroarg"),
|
|
"side effects in the 1st macro argument 'X' are repeated in "
|
|
"macro expansion"),
|
|
DiagSource(Diag::ClangTidy),
|
|
DiagName("bugprone-macro-repeated-side-effects"),
|
|
WithNote(
|
|
Diag(Test.range("macrodef"), "macro 'SQUARE' defined here"))),
|
|
Diag(Test.range("macroarg"),
|
|
"multiple unsequenced modifications to 'y'"),
|
|
AllOf(
|
|
Diag(Test.range("main"),
|
|
"use a trailing return type for this function"),
|
|
DiagSource(Diag::ClangTidy),
|
|
DiagName("modernize-use-trailing-return-type"),
|
|
// Verify that we don't have "[check-name]" suffix in the message.
|
|
WithFix(FixMessage("use a trailing return type for this function")))
|
|
));
|
|
}
|
|
|
|
TEST(DiagnosticTest, ClangTidySuppressionComment) {
|
|
Annotations Main(R"cpp(
|
|
int main() {
|
|
int i = 3;
|
|
double d = 8 / i; // NOLINT
|
|
// NOLINTNEXTLINE
|
|
double e = 8 / i;
|
|
double f = [[8]] / i;
|
|
}
|
|
)cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.ClangTidyChecks = "bugprone-integer-division";
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(::testing::AllOf(
|
|
Diag(Main.range(), "result of integer division used in a floating "
|
|
"point context; possible loss of precision"),
|
|
DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"))));
|
|
}
|
|
|
|
TEST(DiagnosticTest, ClangTidyWarningAsError) {
|
|
Annotations Main(R"cpp(
|
|
int main() {
|
|
int i = 3;
|
|
double f = [[8]] / i; // error-ok
|
|
}
|
|
)cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.ClangTidyChecks = "bugprone-integer-division";
|
|
TU.ClangTidyWarningsAsErrors = "bugprone-integer-division";
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(::testing::AllOf(
|
|
Diag(Main.range(), "result of integer division used in a floating "
|
|
"point context; possible loss of precision"),
|
|
DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"),
|
|
DiagSeverity(DiagnosticsEngine::Error))));
|
|
}
|
|
|
|
TEST(DiagnosticTest, LongFixMessages) {
|
|
// We limit the size of printed code.
|
|
Annotations Source(R"cpp(
|
|
int main() {
|
|
// error-ok
|
|
int somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier;
|
|
[[omereallyreallyreallyreallyreallyreallyreallyreallylongidentifier]]= 10;
|
|
}
|
|
)cpp");
|
|
TestTU TU = TestTU::withCode(Source.code());
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
ElementsAre(WithFix(Fix(
|
|
Source.range(),
|
|
"somereallyreallyreallyreallyreallyreallyreallyreallylongidentifier",
|
|
"change 'omereallyreallyreallyreallyreallyreallyreallyreall…' to "
|
|
"'somereallyreallyreallyreallyreallyreallyreallyreal…'"))));
|
|
// Only show changes up to a first newline.
|
|
Source = Annotations(R"cpp(
|
|
// error-ok
|
|
int main() {
|
|
int ident;
|
|
[[ide\
|
|
n]] = 10; // error-ok
|
|
}
|
|
)cpp");
|
|
TU.Code = Source.code();
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
ElementsAre(WithFix(
|
|
Fix(Source.range(), "ident", "change 'ide\\…' to 'ident'"))));
|
|
}
|
|
|
|
TEST(DiagnosticTest, ClangTidyWarningAsErrorTrumpsSuppressionComment) {
|
|
Annotations Main(R"cpp(
|
|
int main() {
|
|
int i = 3;
|
|
double f = [[8]] / i; // NOLINT // error-ok
|
|
}
|
|
)cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.ClangTidyChecks = "bugprone-integer-division";
|
|
TU.ClangTidyWarningsAsErrors = "bugprone-integer-division";
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(::testing::AllOf(
|
|
Diag(Main.range(), "result of integer division used in a floating "
|
|
"point context; possible loss of precision"),
|
|
DiagSource(Diag::ClangTidy), DiagName("bugprone-integer-division"),
|
|
DiagSeverity(DiagnosticsEngine::Error))));
|
|
}
|
|
|
|
TEST(DiagnosticsTest, Preprocessor) {
|
|
// This looks like a preamble, but there's an #else in the middle!
|
|
// Check that:
|
|
// - the #else doesn't generate diagnostics (we had this bug)
|
|
// - we get diagnostics from the taken branch
|
|
// - we get no diagnostics from the not taken branch
|
|
Annotations Test(R"cpp(
|
|
#ifndef FOO
|
|
#define FOO
|
|
int a = [[b]]; // error-ok
|
|
#else
|
|
int x = y;
|
|
#endif
|
|
)cpp");
|
|
EXPECT_THAT(
|
|
TestTU::withCode(Test.code()).build().getDiagnostics(),
|
|
ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
|
|
}
|
|
|
|
TEST(DiagnosticsTest, InsideMacros) {
|
|
Annotations Test(R"cpp(
|
|
#define TEN 10
|
|
#define RET(x) return x + 10
|
|
|
|
int* foo() {
|
|
RET($foo[[0]]); // error-ok
|
|
}
|
|
int* bar() {
|
|
return $bar[[TEN]];
|
|
}
|
|
)cpp");
|
|
EXPECT_THAT(TestTU::withCode(Test.code()).build().getDiagnostics(),
|
|
ElementsAre(Diag(Test.range("foo"),
|
|
"cannot initialize return object of type "
|
|
"'int *' with an rvalue of type 'int'"),
|
|
Diag(Test.range("bar"),
|
|
"cannot initialize return object of type "
|
|
"'int *' with an rvalue of type 'int'")));
|
|
}
|
|
|
|
TEST(DiagnosticsTest, NoFixItInMacro) {
|
|
Annotations Test(R"cpp(
|
|
#define Define(name) void name() {}
|
|
|
|
[[Define]](main) // error-ok
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
ElementsAre(AllOf(Diag(Test.range(), "'main' must return 'int'"),
|
|
Not(WithFix(_)))));
|
|
}
|
|
|
|
TEST(ClangdTest, MSAsm) {
|
|
// Parsing MS assembly tries to use the target MCAsmInfo, which we don't link.
|
|
// We used to crash here. Now clang emits a diagnostic, which we filter out.
|
|
llvm::InitializeAllTargetInfos(); // As in ClangdMain
|
|
auto TU = TestTU::withCode("void fn() { __asm { cmp cl,64 } }");
|
|
TU.ExtraArgs = {"-fms-extensions"};
|
|
EXPECT_THAT(TU.build().getDiagnostics(), IsEmpty());
|
|
}
|
|
|
|
TEST(DiagnosticsTest, ToLSP) {
|
|
URIForFile MainFile =
|
|
URIForFile::canonicalize(testPath("foo/bar/main.cpp"), "");
|
|
URIForFile HeaderFile =
|
|
URIForFile::canonicalize(testPath("foo/bar/header.h"), "");
|
|
|
|
clangd::Diag D;
|
|
D.ID = clang::diag::err_enum_class_reference;
|
|
D.Name = "enum_class_reference";
|
|
D.Source = clangd::Diag::Clang;
|
|
D.Message = "something terrible happened";
|
|
D.Range = {pos(1, 2), pos(3, 4)};
|
|
D.InsideMainFile = true;
|
|
D.Severity = DiagnosticsEngine::Error;
|
|
D.File = "foo/bar/main.cpp";
|
|
D.AbsFile = MainFile.file();
|
|
|
|
clangd::Note NoteInMain;
|
|
NoteInMain.Message = "declared somewhere in the main file";
|
|
NoteInMain.Range = {pos(5, 6), pos(7, 8)};
|
|
NoteInMain.Severity = DiagnosticsEngine::Remark;
|
|
NoteInMain.File = "../foo/bar/main.cpp";
|
|
NoteInMain.InsideMainFile = true;
|
|
NoteInMain.AbsFile = MainFile.file();
|
|
|
|
D.Notes.push_back(NoteInMain);
|
|
|
|
clangd::Note NoteInHeader;
|
|
NoteInHeader.Message = "declared somewhere in the header file";
|
|
NoteInHeader.Range = {pos(9, 10), pos(11, 12)};
|
|
NoteInHeader.Severity = DiagnosticsEngine::Note;
|
|
NoteInHeader.File = "../foo/baz/header.h";
|
|
NoteInHeader.InsideMainFile = false;
|
|
NoteInHeader.AbsFile = HeaderFile.file();
|
|
D.Notes.push_back(NoteInHeader);
|
|
|
|
clangd::Fix F;
|
|
F.Message = "do something";
|
|
D.Fixes.push_back(F);
|
|
|
|
// Diagnostics should turn into these:
|
|
clangd::Diagnostic MainLSP;
|
|
MainLSP.range = D.Range;
|
|
MainLSP.severity = getSeverity(DiagnosticsEngine::Error);
|
|
MainLSP.code = "enum_class_reference";
|
|
MainLSP.source = "clang";
|
|
MainLSP.message =
|
|
R"(Something terrible happened (fix available)
|
|
|
|
main.cpp:6:7: remark: declared somewhere in the main file
|
|
|
|
../foo/baz/header.h:10:11:
|
|
note: declared somewhere in the header file)";
|
|
|
|
clangd::Diagnostic NoteInMainLSP;
|
|
NoteInMainLSP.range = NoteInMain.Range;
|
|
NoteInMainLSP.severity = getSeverity(DiagnosticsEngine::Remark);
|
|
NoteInMainLSP.message = R"(Declared somewhere in the main file
|
|
|
|
main.cpp:2:3: error: something terrible happened)";
|
|
|
|
ClangdDiagnosticOptions Opts;
|
|
// Transform diagnostics and check the results.
|
|
std::vector<std::pair<clangd::Diagnostic, std::vector<clangd::Fix>>> LSPDiags;
|
|
toLSPDiags(D, MainFile, Opts,
|
|
[&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
|
|
LSPDiags.push_back(
|
|
{std::move(LSPDiag),
|
|
std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
|
|
});
|
|
|
|
EXPECT_THAT(
|
|
LSPDiags,
|
|
ElementsAre(Pair(EqualToLSPDiag(MainLSP), ElementsAre(EqualToFix(F))),
|
|
Pair(EqualToLSPDiag(NoteInMainLSP), IsEmpty())));
|
|
EXPECT_EQ(LSPDiags[0].first.code, "enum_class_reference");
|
|
EXPECT_EQ(LSPDiags[0].first.source, "clang");
|
|
EXPECT_EQ(LSPDiags[1].first.code, "");
|
|
EXPECT_EQ(LSPDiags[1].first.source, "");
|
|
|
|
// Same thing, but don't flatten notes into the main list.
|
|
LSPDiags.clear();
|
|
Opts.EmitRelatedLocations = true;
|
|
toLSPDiags(D, MainFile, Opts,
|
|
[&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix> Fixes) {
|
|
LSPDiags.push_back(
|
|
{std::move(LSPDiag),
|
|
std::vector<clangd::Fix>(Fixes.begin(), Fixes.end())});
|
|
});
|
|
MainLSP.message = "Something terrible happened (fix available)";
|
|
DiagnosticRelatedInformation NoteInMainDRI;
|
|
NoteInMainDRI.message = "Declared somewhere in the main file";
|
|
NoteInMainDRI.location.range = NoteInMain.Range;
|
|
NoteInMainDRI.location.uri = MainFile;
|
|
MainLSP.relatedInformation = {NoteInMainDRI};
|
|
DiagnosticRelatedInformation NoteInHeaderDRI;
|
|
NoteInHeaderDRI.message = "Declared somewhere in the header file";
|
|
NoteInHeaderDRI.location.range = NoteInHeader.Range;
|
|
NoteInHeaderDRI.location.uri = HeaderFile;
|
|
MainLSP.relatedInformation = {NoteInMainDRI, NoteInHeaderDRI};
|
|
EXPECT_THAT(LSPDiags, ElementsAre(Pair(EqualToLSPDiag(MainLSP),
|
|
ElementsAre(EqualToFix(F)))));
|
|
}
|
|
|
|
struct SymbolWithHeader {
|
|
std::string QName;
|
|
std::string DeclaringFile;
|
|
std::string IncludeHeader;
|
|
};
|
|
|
|
std::unique_ptr<SymbolIndex>
|
|
buildIndexWithSymbol(llvm::ArrayRef<SymbolWithHeader> Syms) {
|
|
SymbolSlab::Builder Slab;
|
|
for (const auto &S : Syms) {
|
|
Symbol Sym = cls(S.QName);
|
|
Sym.Flags |= Symbol::IndexedForCodeCompletion;
|
|
Sym.CanonicalDeclaration.FileURI = S.DeclaringFile.c_str();
|
|
Sym.Definition.FileURI = S.DeclaringFile.c_str();
|
|
Sym.IncludeHeaders.emplace_back(S.IncludeHeader, 1);
|
|
Slab.insert(Sym);
|
|
}
|
|
return MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
|
|
}
|
|
|
|
TEST(IncludeFixerTest, IncompleteType) {
|
|
Annotations Test(R"cpp(// error-ok
|
|
$insert[[]]namespace ns {
|
|
class X;
|
|
$nested[[X::]]Nested n;
|
|
}
|
|
class Y : $base[[public ns::X]] {};
|
|
int main() {
|
|
ns::X *x;
|
|
x$access[[->]]f();
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
auto Index = buildIndexWithSymbol(
|
|
{SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""}});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
AllOf(Diag(Test.range("nested"),
|
|
"incomplete type 'ns::X' named in nested name specifier"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol ns::X"))),
|
|
AllOf(Diag(Test.range("base"), "base class has incomplete type"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol ns::X"))),
|
|
AllOf(Diag(Test.range("access"),
|
|
"member access into incomplete type 'ns::X'"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol ns::X")))));
|
|
}
|
|
|
|
TEST(IncludeFixerTest, NoSuggestIncludeWhenNoDefinitionInHeader) {
|
|
Annotations Test(R"cpp(// error-ok
|
|
$insert[[]]namespace ns {
|
|
class X;
|
|
}
|
|
class Y : $base[[public ns::X]] {};
|
|
int main() {
|
|
ns::X *x;
|
|
x$access[[->]]f();
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
Symbol Sym = cls("ns::X");
|
|
Sym.Flags |= Symbol::IndexedForCodeCompletion;
|
|
Sym.CanonicalDeclaration.FileURI = "unittest:///x.h";
|
|
Sym.Definition.FileURI = "unittest:///x.cc";
|
|
Sym.IncludeHeaders.emplace_back("\"x.h\"", 1);
|
|
|
|
SymbolSlab::Builder Slab;
|
|
Slab.insert(Sym);
|
|
auto Index =
|
|
MemIndex::build(std::move(Slab).build(), RefSlab(), RelationSlab());
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
Diag(Test.range("base"), "base class has incomplete type"),
|
|
Diag(Test.range("access"),
|
|
"member access into incomplete type 'ns::X'")));
|
|
}
|
|
|
|
TEST(IncludeFixerTest, Typo) {
|
|
Annotations Test(R"cpp(// error-ok
|
|
$insert[[]]namespace ns {
|
|
void foo() {
|
|
$unqualified1[[X]] x;
|
|
// No fix if the unresolved type is used as specifier. (ns::)X::Nested will be
|
|
// considered the unresolved type.
|
|
$unqualified2[[X]]::Nested n;
|
|
}
|
|
}
|
|
void bar() {
|
|
ns::$qualified1[[X]] x; // ns:: is valid.
|
|
ns::$qualified2[[X]](); // Error: no member in namespace
|
|
|
|
::$global[[Global]] glob;
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
auto Index = buildIndexWithSymbol(
|
|
{SymbolWithHeader{"ns::X", "unittest:///x.h", "\"x.h\""},
|
|
SymbolWithHeader{"Global", "unittest:///global.h", "\"global.h\""}});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
AllOf(Diag(Test.range("unqualified1"), "unknown type name 'X'"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol ns::X"))),
|
|
Diag(Test.range("unqualified2"), "use of undeclared identifier 'X'"),
|
|
AllOf(Diag(Test.range("qualified1"),
|
|
"no type named 'X' in namespace 'ns'"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol ns::X"))),
|
|
AllOf(Diag(Test.range("qualified2"),
|
|
"no member named 'X' in namespace 'ns'"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol ns::X"))),
|
|
AllOf(Diag(Test.range("global"),
|
|
"no type named 'Global' in the global namespace"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"global.h\"\n",
|
|
"Add include \"global.h\" for symbol Global")))));
|
|
}
|
|
|
|
TEST(IncludeFixerTest, MultipleMatchedSymbols) {
|
|
Annotations Test(R"cpp(// error-ok
|
|
$insert[[]]namespace na {
|
|
namespace nb {
|
|
void foo() {
|
|
$unqualified[[X]] x;
|
|
}
|
|
}
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
auto Index = buildIndexWithSymbol(
|
|
{SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""},
|
|
SymbolWithHeader{"na::nb::X", "unittest:///b.h", "\"b.h\""}});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(AllOf(
|
|
Diag(Test.range("unqualified"), "unknown type name 'X'"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"a.h\"\n",
|
|
"Add include \"a.h\" for symbol na::X"),
|
|
Fix(Test.range("insert"), "#include \"b.h\"\n",
|
|
"Add include \"b.h\" for symbol na::nb::X")))));
|
|
}
|
|
|
|
TEST(IncludeFixerTest, NoCrashMemebrAccess) {
|
|
Annotations Test(R"cpp(// error-ok
|
|
struct X { int xyz; };
|
|
void g() { X x; x.$[[xy]] }
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
auto Index = buildIndexWithSymbol(
|
|
SymbolWithHeader{"na::X", "unittest:///a.h", "\"a.h\""});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(Diag(Test.range(), "no member named 'xy' in 'X'")));
|
|
}
|
|
|
|
TEST(IncludeFixerTest, UseCachedIndexResults) {
|
|
// As index results for the identical request are cached, more than 5 fixes
|
|
// are generated.
|
|
Annotations Test(R"cpp(// error-ok
|
|
$insert[[]]void foo() {
|
|
$x1[[X]] x;
|
|
$x2[[X]] x;
|
|
$x3[[X]] x;
|
|
$x4[[X]] x;
|
|
$x5[[X]] x;
|
|
$x6[[X]] x;
|
|
$x7[[X]] x;
|
|
}
|
|
|
|
class X;
|
|
void bar(X *x) {
|
|
x$a1[[->]]f();
|
|
x$a2[[->]]f();
|
|
x$a3[[->]]f();
|
|
x$a4[[->]]f();
|
|
x$a5[[->]]f();
|
|
x$a6[[->]]f();
|
|
x$a7[[->]]f();
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
auto Index =
|
|
buildIndexWithSymbol(SymbolWithHeader{"X", "unittest:///a.h", "\"a.h\""});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
auto Parsed = TU.build();
|
|
for (const auto &D : Parsed.getDiagnostics()) {
|
|
if (D.Fixes.size() != 1) {
|
|
ADD_FAILURE() << "D.Fixes.size() != 1";
|
|
continue;
|
|
}
|
|
EXPECT_EQ(D.Fixes[0].Message,
|
|
std::string("Add include \"a.h\" for symbol X"));
|
|
}
|
|
}
|
|
|
|
TEST(IncludeFixerTest, UnresolvedNameAsSpecifier) {
|
|
Annotations Test(R"cpp(// error-ok
|
|
$insert[[]]namespace ns {
|
|
}
|
|
void g() { ns::$[[scope]]::X_Y(); }
|
|
)cpp");
|
|
TestTU TU;
|
|
TU.Code = Test.code();
|
|
// FIXME: Figure out why this is needed and remove it, PR43662.
|
|
TU.ExtraArgs.push_back("-fno-ms-compatibility");
|
|
auto Index = buildIndexWithSymbol(
|
|
SymbolWithHeader{"ns::scope::X_Y", "unittest:///x.h", "\"x.h\""});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(AllOf(
|
|
Diag(Test.range(), "no member named 'scope' in namespace 'ns'"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol ns::scope::X_Y")))));
|
|
}
|
|
|
|
TEST(IncludeFixerTest, UnresolvedSpecifierWithSemaCorrection) {
|
|
Annotations Test(R"cpp(// error-ok
|
|
$insert[[]]namespace clang {
|
|
void f() {
|
|
// "clangd::" will be corrected to "clang::" by Sema.
|
|
$q1[[clangd]]::$x[[X]] x;
|
|
$q2[[clangd]]::$ns[[ns]]::Y y;
|
|
}
|
|
}
|
|
)cpp");
|
|
TestTU TU;
|
|
TU.Code = Test.code();
|
|
// FIXME: Figure out why this is needed and remove it, PR43662.
|
|
TU.ExtraArgs.push_back("-fno-ms-compatibility");
|
|
auto Index = buildIndexWithSymbol(
|
|
{SymbolWithHeader{"clang::clangd::X", "unittest:///x.h", "\"x.h\""},
|
|
SymbolWithHeader{"clang::clangd::ns::Y", "unittest:///y.h", "\"y.h\""}});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(
|
|
TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
AllOf(
|
|
Diag(Test.range("q1"), "use of undeclared identifier 'clangd'; "
|
|
"did you mean 'clang'?"),
|
|
WithFix(_, // change clangd to clang
|
|
Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol clang::clangd::X"))),
|
|
AllOf(
|
|
Diag(Test.range("x"), "no type named 'X' in namespace 'clang'"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol clang::clangd::X"))),
|
|
AllOf(
|
|
Diag(Test.range("q2"), "use of undeclared identifier 'clangd'; "
|
|
"did you mean 'clang'?"),
|
|
WithFix(
|
|
_, // change clangd to clangd
|
|
Fix(Test.range("insert"), "#include \"y.h\"\n",
|
|
"Add include \"y.h\" for symbol clang::clangd::ns::Y"))),
|
|
AllOf(Diag(Test.range("ns"),
|
|
"no member named 'ns' in namespace 'clang'"),
|
|
WithFix(Fix(
|
|
Test.range("insert"), "#include \"y.h\"\n",
|
|
"Add include \"y.h\" for symbol clang::clangd::ns::Y")))));
|
|
}
|
|
|
|
TEST(IncludeFixerTest, SpecifiedScopeIsNamespaceAlias) {
|
|
Annotations Test(R"cpp(// error-ok
|
|
$insert[[]]namespace a {}
|
|
namespace b = a;
|
|
namespace c {
|
|
b::$[[X]] x;
|
|
}
|
|
)cpp");
|
|
auto TU = TestTU::withCode(Test.code());
|
|
auto Index = buildIndexWithSymbol(
|
|
SymbolWithHeader{"a::X", "unittest:///x.h", "\"x.h\""});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(AllOf(
|
|
Diag(Test.range(), "no type named 'X' in namespace 'a'"),
|
|
WithFix(Fix(Test.range("insert"), "#include \"x.h\"\n",
|
|
"Add include \"x.h\" for symbol a::X")))));
|
|
}
|
|
|
|
TEST(IncludeFixerTest, NoCrashOnTemplateInstantiations) {
|
|
Annotations Test(R"cpp(
|
|
template <typename T> struct Templ {
|
|
template <typename U>
|
|
typename U::type operator=(const U &);
|
|
};
|
|
|
|
struct A {
|
|
Templ<char> s;
|
|
A() { [[a]]; /*error-ok*/ } // crash if we compute scopes lazily.
|
|
};
|
|
)cpp");
|
|
|
|
auto TU = TestTU::withCode(Test.code());
|
|
auto Index = buildIndexWithSymbol({});
|
|
TU.ExternalIndex = Index.get();
|
|
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
ElementsAre(Diag(Test.range(), "use of undeclared identifier 'a'")));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, DiagInsideHeader) {
|
|
Annotations Main(R"cpp(
|
|
#include [["a.h"]]
|
|
void foo() {})cpp");
|
|
Annotations Header("[[no_type_spec]]; // error-ok");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {{"a.h", Header.code()}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(AllOf(
|
|
Diag(Main.range(), "in included file: C++ requires a "
|
|
"type specifier for all declarations"),
|
|
WithNote(Diag(Header.range(), "error occurred here")))));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, DiagInTransitiveInclude) {
|
|
Annotations Main(R"cpp(
|
|
#include [["a.h"]]
|
|
void foo() {})cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {{"a.h", "#include \"b.h\""},
|
|
{"b.h", "no_type_spec; // error-ok"}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
Diag(Main.range(), "in included file: C++ requires a "
|
|
"type specifier for all declarations")));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, DiagInMultipleHeaders) {
|
|
Annotations Main(R"cpp(
|
|
#include $a[["a.h"]]
|
|
#include $b[["b.h"]]
|
|
void foo() {})cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {{"a.h", "no_type_spec; // error-ok"},
|
|
{"b.h", "no_type_spec; // error-ok"}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
Diag(Main.range("a"), "in included file: C++ requires a type "
|
|
"specifier for all declarations"),
|
|
Diag(Main.range("b"), "in included file: C++ requires a type "
|
|
"specifier for all declarations")));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, PreferExpansionLocation) {
|
|
Annotations Main(R"cpp(
|
|
#include [["a.h"]]
|
|
#include "b.h"
|
|
void foo() {})cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {
|
|
{"a.h", "#include \"b.h\"\n"},
|
|
{"b.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(Diag(Main.range(),
|
|
"in included file: C++ requires a type "
|
|
"specifier for all declarations")));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, PreferExpansionLocationMacros) {
|
|
Annotations Main(R"cpp(
|
|
#define X
|
|
#include "a.h"
|
|
#undef X
|
|
#include [["b.h"]]
|
|
void foo() {})cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {
|
|
{"a.h", "#include \"c.h\"\n"},
|
|
{"b.h", "#include \"c.h\"\n"},
|
|
{"c.h", "#ifndef X\n#define X\nno_type_spec; // error-ok\n#endif"}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
Diag(Main.range(), "in included file: C++ requires a "
|
|
"type specifier for all declarations")));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, LimitDiagsOutsideMainFile) {
|
|
Annotations Main(R"cpp(
|
|
#include [["a.h"]]
|
|
#include "b.h"
|
|
void foo() {})cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {{"a.h", "#include \"c.h\"\n"},
|
|
{"b.h", "#include \"c.h\"\n"},
|
|
{"c.h", R"cpp(
|
|
#ifndef X
|
|
#define X
|
|
no_type_spec_0; // error-ok
|
|
no_type_spec_1;
|
|
no_type_spec_2;
|
|
no_type_spec_3;
|
|
no_type_spec_4;
|
|
no_type_spec_5;
|
|
no_type_spec_6;
|
|
no_type_spec_7;
|
|
no_type_spec_8;
|
|
no_type_spec_9;
|
|
no_type_spec_10;
|
|
#endif)cpp"}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
Diag(Main.range(), "in included file: C++ requires a "
|
|
"type specifier for all declarations")));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, OnlyErrorOrFatal) {
|
|
Annotations Main(R"cpp(
|
|
#include [["a.h"]]
|
|
void foo() {})cpp");
|
|
Annotations Header(R"cpp(
|
|
[[no_type_spec]]; // error-ok
|
|
int x = 5/0;)cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {{"a.h", Header.code()}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(AllOf(
|
|
Diag(Main.range(), "in included file: C++ requires "
|
|
"a type specifier for all declarations"),
|
|
WithNote(Diag(Header.range(), "error occurred here")))));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, FromNonWrittenSources) {
|
|
Annotations Main(R"cpp(
|
|
#include [["a.h"]]
|
|
void foo() {})cpp");
|
|
Annotations Header(R"cpp(
|
|
int x = 5/0;
|
|
int b = [[FOO]]; // error-ok)cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {{"a.h", Header.code()}};
|
|
TU.ExtraArgs = {"-DFOO=NOOO"};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(AllOf(
|
|
Diag(Main.range(),
|
|
"in included file: use of undeclared identifier 'NOOO'"),
|
|
WithNote(Diag(Header.range(), "error occurred here")))));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, ErrorFromMacroExpansion) {
|
|
Annotations Main(R"cpp(
|
|
void bar() {
|
|
int fo; // error-ok
|
|
#include [["a.h"]]
|
|
})cpp");
|
|
Annotations Header(R"cpp(
|
|
#define X foo
|
|
X;)cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {{"a.h", Header.code()}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
Diag(Main.range(), "in included file: use of undeclared "
|
|
"identifier 'foo'; did you mean 'fo'?")));
|
|
}
|
|
|
|
TEST(DiagsInHeaders, ErrorFromMacroArgument) {
|
|
Annotations Main(R"cpp(
|
|
void bar() {
|
|
int fo; // error-ok
|
|
#include [["a.h"]]
|
|
})cpp");
|
|
Annotations Header(R"cpp(
|
|
#define X(arg) arg
|
|
X(foo);)cpp");
|
|
TestTU TU = TestTU::withCode(Main.code());
|
|
TU.AdditionalFiles = {{"a.h", Header.code()}};
|
|
EXPECT_THAT(TU.build().getDiagnostics(),
|
|
UnorderedElementsAre(
|
|
Diag(Main.range(), "in included file: use of undeclared "
|
|
"identifier 'foo'; did you mean 'fo'?")));
|
|
}
|
|
|
|
TEST(IgnoreDiags, FromNonWrittenInclude) {
|
|
TestTU TU;
|
|
TU.ExtraArgs.push_back("--include=a.h");
|
|
TU.AdditionalFiles = {{"a.h", "void main();"}};
|
|
// The diagnostic "main must return int" is from the header, we don't attempt
|
|
// to render it in the main file as there is no written location there.
|
|
EXPECT_THAT(TU.build().getDiagnostics(), UnorderedElementsAre());
|
|
}
|
|
|
|
TEST(ToLSPDiag, RangeIsInMain) {
|
|
ClangdDiagnosticOptions Opts;
|
|
clangd::Diag D;
|
|
D.Range = {pos(1, 2), pos(3, 4)};
|
|
D.Notes.emplace_back();
|
|
Note &N = D.Notes.back();
|
|
N.Range = {pos(2, 3), pos(3, 4)};
|
|
|
|
D.InsideMainFile = true;
|
|
N.InsideMainFile = false;
|
|
toLSPDiags(D, {}, Opts,
|
|
[&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
|
|
EXPECT_EQ(LSPDiag.range, D.Range);
|
|
});
|
|
|
|
D.InsideMainFile = false;
|
|
N.InsideMainFile = true;
|
|
toLSPDiags(D, {}, Opts,
|
|
[&](clangd::Diagnostic LSPDiag, ArrayRef<clangd::Fix>) {
|
|
EXPECT_EQ(LSPDiag.range, N.Range);
|
|
});
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace clangd
|
|
} // namespace clang
|