forked from OSchip/llvm-project
[clangd] Add ClangdUnit diagnostics tests using annotated code.
Summary: This adds checks that our diagnostics emit correct ranges in a bunch of cases, as promised in D41118. The diagnostics-preamble test is also converted and extended to be a little more precise. diagnostics.test stays around as the smoke test for this feature. Reviewers: ilya-biryukov Subscribers: klimek, mgorny, cfe-commits Differential Revision: https://reviews.llvm.org/D41454 llvm-svn: 323448
This commit is contained in:
parent
102d4b59f9
commit
034e11aca5
|
@ -137,6 +137,12 @@ json::Expr toJSON(const TextEdit &P) {
|
|||
};
|
||||
}
|
||||
|
||||
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const TextEdit &TE) {
|
||||
OS << TE.range << " => \"";
|
||||
PrintEscapedString(TE.newText, OS);
|
||||
return OS << '"';
|
||||
}
|
||||
|
||||
bool fromJSON(const json::Expr &E, TraceLevel &Out) {
|
||||
if (auto S = E.asString()) {
|
||||
if (*S == "off") {
|
||||
|
@ -255,6 +261,28 @@ bool fromJSON(const json::Expr &Params, CodeActionContext &R) {
|
|||
return O && O.map("diagnostics", R.diagnostics);
|
||||
}
|
||||
|
||||
llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Diagnostic &D) {
|
||||
OS << D.range << " [";
|
||||
switch (D.severity) {
|
||||
case 1:
|
||||
OS << "error";
|
||||
break;
|
||||
case 2:
|
||||
OS << "warning";
|
||||
break;
|
||||
case 3:
|
||||
OS << "note";
|
||||
break;
|
||||
case 4:
|
||||
OS << "remark";
|
||||
break;
|
||||
default:
|
||||
OS << "diagnostic";
|
||||
break;
|
||||
}
|
||||
return OS << '(' << D.severity << "): " << D.message << "]";
|
||||
}
|
||||
|
||||
bool fromJSON(const json::Expr &Params, CodeActionParams &R) {
|
||||
json::ObjectMapper O(Params);
|
||||
return O && O.map("textDocument", R.textDocument) &&
|
||||
|
|
|
@ -150,6 +150,7 @@ struct TextEdit {
|
|||
};
|
||||
bool fromJSON(const json::Expr &, TextEdit &);
|
||||
json::Expr toJSON(const TextEdit &);
|
||||
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const TextEdit &);
|
||||
|
||||
struct TextDocumentItem {
|
||||
/// The text document's URI.
|
||||
|
@ -341,6 +342,7 @@ struct LSPDiagnosticCompare {
|
|||
}
|
||||
};
|
||||
bool fromJSON(const json::Expr &, Diagnostic &);
|
||||
llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Diagnostic &);
|
||||
|
||||
struct CodeActionContext {
|
||||
/// An array of diagnostics.
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
|
||||
# RUN: clangd -pretty -run-synchronously -pch-storage=memory < %s | FileCheck -strict-whitespace %s
|
||||
# It is absolutely vital that this file has CRLF line endings.
|
||||
#
|
||||
Content-Length: 125
|
||||
|
||||
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
|
||||
|
||||
Content-Length: 206
|
||||
|
||||
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"#ifndef FOO\n#define FOO\nint a;\n#else\nint a = b;#endif\n\n\n"}}}
|
||||
# CHECK: "method": "textDocument/publishDiagnostics",
|
||||
# CHECK-NEXT: "params": {
|
||||
# CHECK-NEXT: "diagnostics": [],
|
||||
# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
|
||||
# CHECK-NEXT: }
|
||||
Content-Length: 58
|
||||
|
||||
{"jsonrpc":"2.0","id":2,"method":"shutdown","params":null}
|
||||
Content-Length: 33
|
||||
|
||||
{"jsonrpc":"2.0":"method":"exit"}
|
|
@ -11,6 +11,7 @@ include_directories(
|
|||
add_extra_unittest(ClangdTests
|
||||
Annotations.cpp
|
||||
ClangdTests.cpp
|
||||
ClangdUnitTests.cpp
|
||||
CodeCompleteTests.cpp
|
||||
CodeCompletionStringsTests.cpp
|
||||
ContextTests.cpp
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
//===-- ClangdUnitTests.cpp - ClangdUnit tests ------------------*- C++ -*-===//
|
||||
//
|
||||
// The LLVM Compiler Infrastructure
|
||||
//
|
||||
// This file is distributed under the University of Illinois Open Source
|
||||
// License. See LICENSE.TXT for details.
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "ClangdUnit.h"
|
||||
#include "Annotations.h"
|
||||
#include "TestFS.h"
|
||||
#include "clang/Frontend/CompilerInvocation.h"
|
||||
#include "clang/Frontend/PCHContainerOperations.h"
|
||||
#include "clang/Frontend/Utils.h"
|
||||
#include "llvm/Support/ScopedPrinter.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace clang {
|
||||
namespace clangd {
|
||||
using namespace llvm;
|
||||
void PrintTo(const DiagWithFixIts &D, std::ostream *O) {
|
||||
llvm::raw_os_ostream OS(*O);
|
||||
OS << D.Diag;
|
||||
if (!D.FixIts.empty()) {
|
||||
OS << " {";
|
||||
const char *Sep = "";
|
||||
for (const auto &F : D.FixIts) {
|
||||
OS << Sep << F;
|
||||
Sep = ", ";
|
||||
}
|
||||
OS << "}";
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
using testing::ElementsAre;
|
||||
|
||||
// FIXME: this is duplicated with FileIndexTests. Share it.
|
||||
ParsedAST build(StringRef Code, std::vector<const char*> Flags = {}) {
|
||||
std::vector<const char*> Cmd = {"clang", "main.cpp"};
|
||||
Cmd.insert(Cmd.begin() + 1, Flags.begin(), Flags.end());
|
||||
auto CI = createInvocationFromCommandLine(Cmd);
|
||||
auto Buf = MemoryBuffer::getMemBuffer(Code);
|
||||
auto AST = ParsedAST::Build(
|
||||
Context::empty(), std::move(CI), nullptr, std::move(Buf),
|
||||
std::make_shared<PCHContainerOperations>(), vfs::getRealFileSystem());
|
||||
assert(AST.hasValue());
|
||||
return std::move(*AST);
|
||||
}
|
||||
|
||||
MATCHER_P2(Diag, Range, Message,
|
||||
"Diagnostic at " + llvm::to_string(Range) + " = [" + Message + "]") {
|
||||
return arg.Diag.range == Range && arg.Diag.message == Message &&
|
||||
arg.FixIts.empty();
|
||||
}
|
||||
|
||||
MATCHER_P3(Fix, Range, Replacement, Message,
|
||||
"Fix " + llvm::to_string(Range) + " => " +
|
||||
testing::PrintToString(Replacement) + " = [" + Message + "]") {
|
||||
return arg.Diag.range == Range && arg.Diag.message == Message &&
|
||||
arg.FixIts.size() == 1 && arg.FixIts[0].range == Range &&
|
||||
arg.FixIts[0].newText == Replacement;
|
||||
}
|
||||
|
||||
TEST(DiagnosticsTest, DiagnosticRanges) {
|
||||
// Check we report correct ranges, including various edge-cases.
|
||||
Annotations Test(R"cpp(
|
||||
void $decl[[foo]]();
|
||||
int main() {
|
||||
$typo[[go\
|
||||
o]]();
|
||||
foo()$semicolon[[]]
|
||||
$unk[[unknown]]();
|
||||
}
|
||||
)cpp");
|
||||
llvm::errs() << Test.code();
|
||||
EXPECT_THAT(
|
||||
build(Test.code()).getDiagnostics(),
|
||||
ElementsAre(
|
||||
// This range spans lines.
|
||||
Fix(Test.range("typo"), "foo",
|
||||
"use of undeclared identifier 'goo'; did you mean 'foo'?"),
|
||||
// This is a pretty normal range.
|
||||
Diag(Test.range("decl"), "'foo' declared here"),
|
||||
// This range is zero-width, and at the end of a line.
|
||||
Fix(Test.range("semicolon"), ";",
|
||||
"expected ';' after expression"),
|
||||
// This range isn't provided by clang, we expand to the token.
|
||||
Diag(Test.range("unk"),
|
||||
"use of undeclared identifier 'unknown'")));
|
||||
}
|
||||
|
||||
TEST(DiagnosticsTest, FlagsMatter) {
|
||||
Annotations Test("[[void]] main() {}");
|
||||
EXPECT_THAT(
|
||||
build(Test.code()).getDiagnostics(),
|
||||
ElementsAre(Fix(Test.range(), "int", "'main' must return 'int'")));
|
||||
// Same code built as C gets different diagnostics.
|
||||
EXPECT_THAT(
|
||||
build(Test.code(), {"-x", "c"}).getDiagnostics(),
|
||||
ElementsAre(
|
||||
// FIXME: ideally this would be one diagnostic with a named FixIt.
|
||||
Diag(Test.range(), "return type of 'main' is not 'int'"),
|
||||
Fix(Test.range(), "int", "change return type to 'int'")));
|
||||
}
|
||||
|
||||
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]];
|
||||
#else
|
||||
int x = y;
|
||||
#endif
|
||||
)cpp");
|
||||
EXPECT_THAT(
|
||||
build(Test.code()).getDiagnostics(),
|
||||
ElementsAre(Diag(Test.range(), "use of undeclared identifier 'b'")));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace clangd
|
||||
} // namespace clang
|
Loading…
Reference in New Issue