aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid L. Jones <dlj@google.com>2017-11-15 01:40:05 +0000
committerDavid L. Jones <dlj@google.com>2017-11-15 01:40:05 +0000
commite116b2cbca67fd5860351c74e296cc5ce72c7980 (patch)
tree080e959bbba8787e252efa998568def298147e2d
parent81f5ab7c488b645ad1c1587fc4a30451d12aaac1 (diff)
parentd68d476fc2b4e565efc28b4a29f2ab11a2730241 (diff)
Creating branches/google/testing and tags/google/testing/2017-11-14 from r317716testing
git-svn-id: https://llvm.org/svn/llvm-project/clang-tools-extra/branches/google/testing@318248 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r--clang-tidy/google/CMakeLists.txt1
-rw-r--r--clang-tidy/google/GlobalVariableDeclarationCheck.cpp90
-rw-r--r--clang-tidy/google/GlobalVariableDeclarationCheck.h39
-rw-r--r--clang-tidy/google/GoogleTidyModule.cpp15
-rw-r--r--clang-tidy/misc/RedundantExpressionCheck.cpp287
-rw-r--r--clang-tidy/misc/RedundantExpressionCheck.h3
-rwxr-xr-xclang-tidy/tool/run-clang-tidy.py9
-rw-r--r--clangd/CMakeLists.txt1
-rw-r--r--clangd/ClangdLSPServer.cpp183
-rw-r--r--clangd/ClangdLSPServer.h1
-rw-r--r--clangd/ClangdUnit.cpp14
-rw-r--r--clangd/JSONExpr.cpp221
-rw-r--r--clangd/JSONExpr.h247
-rw-r--r--clangd/JSONRPCDispatcher.cpp78
-rw-r--r--clangd/JSONRPCDispatcher.h22
-rw-r--r--clangd/Protocol.cpp298
-rw-r--r--clangd/Protocol.h78
-rw-r--r--clangd/ProtocolHandlers.cpp1
-rw-r--r--clangd/ProtocolHandlers.h1
-rw-r--r--clangd/clients/clangd-vscode/src/extension.ts20
-rw-r--r--clangd/tool/ClangdMain.cpp7
-rw-r--r--docs/ReleaseNotes.rst10
-rw-r--r--docs/clang-tidy/checks/google-objc-global-variable-declaration.rst42
-rw-r--r--docs/clang-tidy/checks/list.rst1
-rw-r--r--docs/clang-tidy/checks/misc-redundant-expression.rst8
-rw-r--r--docs/clang-tidy/checks/modernize-replace-random-shuffle.rst13
-rw-r--r--test/clang-tidy/google-objc-global-variable-declaration.m41
-rw-r--r--test/clang-tidy/misc-redundant-expression.cpp208
-rw-r--r--test/clangd/authority-less-uri.test30
-rw-r--r--test/clangd/completion-items-kinds.test17
-rw-r--r--test/clangd/completion-priorities.test80
-rw-r--r--test/clangd/completion-qualifiers.test41
-rw-r--r--test/clangd/completion-snippet.test111
-rw-r--r--test/clangd/completion.test201
-rw-r--r--test/clangd/definitions.test322
-rw-r--r--test/clangd/diagnostics-preamble.test10
-rw-r--r--test/clangd/diagnostics.test41
-rw-r--r--test/clangd/did-change-watch-files.test13
-rw-r--r--test/clangd/execute-command.test112
-rw-r--r--test/clangd/extra-flags.test75
-rw-r--r--test/clangd/fixits.test241
-rw-r--r--test/clangd/formatting.test188
-rw-r--r--test/clangd/initialize-params-invalid.test49
-rw-r--r--test/clangd/initialize-params.test52
-rw-r--r--test/clangd/input-mirror.test3
-rw-r--r--test/clangd/protocol.test57
-rw-r--r--test/clangd/signature-help.test11
-rw-r--r--test/clangd/unsupported-method.test10
-rw-r--r--tool-template/CMakeLists.txt1
-rw-r--r--tool-template/ToolTemplate.cpp50
-rw-r--r--unittests/clangd/CMakeLists.txt1
-rw-r--r--unittests/clangd/JSONExprTests.cpp116
52 files changed, 3112 insertions, 659 deletions
diff --git a/clang-tidy/google/CMakeLists.txt b/clang-tidy/google/CMakeLists.txt
index ff48b63d..9a919154 100644
--- a/clang-tidy/google/CMakeLists.txt
+++ b/clang-tidy/google/CMakeLists.txt
@@ -6,6 +6,7 @@ add_clang_library(clangTidyGoogleModule
ExplicitConstructorCheck.cpp
ExplicitMakePairCheck.cpp
GlobalNamesInHeadersCheck.cpp
+ GlobalVariableDeclarationCheck.cpp
GoogleTidyModule.cpp
IntegerTypesCheck.cpp
NonConstReferences.cpp
diff --git a/clang-tidy/google/GlobalVariableDeclarationCheck.cpp b/clang-tidy/google/GlobalVariableDeclarationCheck.cpp
new file mode 100644
index 00000000..a278be14
--- /dev/null
+++ b/clang-tidy/google/GlobalVariableDeclarationCheck.cpp
@@ -0,0 +1,90 @@
+//===--- GlobalVariableDeclarationCheck.cpp - clang-tidy-------------------===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "GlobalVariableDeclarationCheck.h"
+#include "clang/AST/ASTContext.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+
+#include <string>
+
+using namespace clang::ast_matchers;
+
+namespace clang {
+namespace tidy {
+namespace google {
+namespace objc {
+
+namespace {
+
+FixItHint generateFixItHint(const VarDecl *Decl, bool IsConst) {
+ char FC = Decl->getName()[0];
+ if (!llvm::isAlpha(FC) || Decl->getName().size() == 1) {
+ // No fix available if first character is not alphabetical character, or it
+ // is a single-character variable, since it is difficult to determine the
+ // proper fix in this case. Users should create a proper variable name by
+ // their own.
+ return FixItHint();
+ }
+ char SC = Decl->getName()[1];
+ if ((FC == 'k' || FC == 'g') && !llvm::isAlpha(SC)) {
+ // No fix available if the prefix is correct but the second character is not
+ // alphabetical, since it is difficult to determine the proper fix in this
+ // case.
+ return FixItHint();
+ }
+ auto NewName = (IsConst ? "k" : "g") +
+ llvm::StringRef(std::string(1, FC)).upper() +
+ Decl->getName().substr(1).str();
+ return FixItHint::CreateReplacement(
+ CharSourceRange::getTokenRange(SourceRange(Decl->getLocation())),
+ llvm::StringRef(NewName));
+}
+} // namespace
+
+void GlobalVariableDeclarationCheck::registerMatchers(MatchFinder *Finder) {
+ // The relevant Style Guide rule only applies to Objective-C.
+ if (!getLangOpts().ObjC1 && !getLangOpts().ObjC2) {
+ return;
+ }
+ // need to add two matchers since we need to bind different ids to distinguish
+ // constants and variables. Since bind() can only be called on node matchers,
+ // we cannot make it in one matcher.
+ Finder->addMatcher(
+ varDecl(hasGlobalStorage(), unless(hasType(isConstQualified())),
+ unless(matchesName("::g[A-Z]")))
+ .bind("global_var"),
+ this);
+ Finder->addMatcher(varDecl(hasGlobalStorage(), hasType(isConstQualified()),
+ unless(matchesName("::k[A-Z]")))
+ .bind("global_const"),
+ this);
+}
+
+void GlobalVariableDeclarationCheck::check(
+ const MatchFinder::MatchResult &Result) {
+ if (const auto *Decl = Result.Nodes.getNodeAs<VarDecl>("global_var")) {
+ diag(Decl->getLocation(),
+ "non-const global variable '%0' must have a name which starts with "
+ "'g[A-Z]'")
+ << Decl->getName() << generateFixItHint(Decl, false);
+ }
+ if (const auto *Decl = Result.Nodes.getNodeAs<VarDecl>("global_const")) {
+ diag(Decl->getLocation(),
+ "const global variable '%0' must have a name which starts with "
+ "'k[A-Z]'")
+ << Decl->getName() << generateFixItHint(Decl, true);
+ }
+}
+
+} // namespace objc
+} // namespace google
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/google/GlobalVariableDeclarationCheck.h b/clang-tidy/google/GlobalVariableDeclarationCheck.h
new file mode 100644
index 00000000..ed0352bb
--- /dev/null
+++ b/clang-tidy/google/GlobalVariableDeclarationCheck.h
@@ -0,0 +1,39 @@
+//===--- GlobalVariableDeclarationCheck.h - clang-tidy-----------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_GLOBAL_VARIABLE_DECLARATION_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_GLOBAL_VARIABLE_DECLARATION_H
+
+#include "../ClangTidy.h"
+
+namespace clang {
+namespace tidy {
+namespace google {
+namespace objc {
+
+/// The check for Objective-C global variables and constants naming convention.
+/// The declaration should follow the patterns of 'k[A-Z].*' (constants) or
+/// 'g[A-Z].*' (variables).
+///
+/// For the user-facing documentation see:
+/// http://clang.llvm.org/extra/clang-tidy/checks/google-objc-global-variable-declaration.html
+class GlobalVariableDeclarationCheck : public ClangTidyCheck {
+ public:
+ GlobalVariableDeclarationCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+};
+
+} // namespace objc
+} // namespace google
+} // namespace tidy
+} // namespace clang
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_GOOGLE_OBJC_GLOBAL_VARIABLE_DECLARATION_H
diff --git a/clang-tidy/google/GoogleTidyModule.cpp b/clang-tidy/google/GoogleTidyModule.cpp
index 1328463e..45d7de58 100644
--- a/clang-tidy/google/GoogleTidyModule.cpp
+++ b/clang-tidy/google/GoogleTidyModule.cpp
@@ -19,6 +19,7 @@
#include "ExplicitConstructorCheck.h"
#include "ExplicitMakePairCheck.h"
#include "GlobalNamesInHeadersCheck.h"
+#include "GlobalVariableDeclarationCheck.h"
#include "IntegerTypesCheck.h"
#include "NonConstReferences.h"
#include "OverloadedUnaryAndCheck.h"
@@ -34,7 +35,7 @@ namespace tidy {
namespace google {
class GoogleModule : public ClangTidyModule {
-public:
+ public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<build::ExplicitMakePairCheck>(
"google-build-explicit-make-pair");
@@ -46,6 +47,10 @@ public:
"google-default-arguments");
CheckFactories.registerCheck<ExplicitConstructorCheck>(
"google-explicit-constructor");
+ CheckFactories.registerCheck<readability::GlobalNamesInHeadersCheck>(
+ "google-global-names-in-headers");
+ CheckFactories.registerCheck<objc::GlobalVariableDeclarationCheck>(
+ "google-objc-global-variable-declaration");
CheckFactories.registerCheck<runtime::IntegerTypesCheck>(
"google-runtime-int");
CheckFactories.registerCheck<runtime::OverloadedUnaryAndCheck>(
@@ -61,8 +66,6 @@ public:
CheckFactories
.registerCheck<clang::tidy::readability::BracesAroundStatementsCheck>(
"google-readability-braces-around-statements");
- CheckFactories.registerCheck<readability::GlobalNamesInHeadersCheck>(
- "google-global-names-in-headers");
CheckFactories.registerCheck<clang::tidy::readability::FunctionSizeCheck>(
"google-readability-function-size");
CheckFactories
@@ -89,11 +92,11 @@ public:
static ClangTidyModuleRegistry::Add<GoogleModule> X("google-module",
"Adds Google lint checks.");
-} // namespace google
+} // namespace google
// This anchor is used to force the linker to link in the generated object file
// and thus register the GoogleModule.
volatile int GoogleModuleAnchorSource = 0;
-} // namespace tidy
-} // namespace clang
+} // namespace tidy
+} // namespace clang
diff --git a/clang-tidy/misc/RedundantExpressionCheck.cpp b/clang-tidy/misc/RedundantExpressionCheck.cpp
index 901b54cb..265cb385 100644
--- a/clang-tidy/misc/RedundantExpressionCheck.cpp
+++ b/clang-tidy/misc/RedundantExpressionCheck.cpp
@@ -23,7 +23,6 @@
#include <algorithm>
#include <cassert>
#include <cstdint>
-#include <set>
#include <string>
#include <vector>
@@ -38,8 +37,8 @@ namespace {
using llvm::APSInt;
} // namespace
-static const char KnownBannedMacroNames[] =
- "EAGAIN;EWOULDBLOCK;SIGCLD;SIGCHLD;";
+static const llvm::StringSet<> KnownBannedMacroNames = {"EAGAIN", "EWOULDBLOCK",
+ "SIGCLD", "SIGCHLD"};
static bool incrementWithoutOverflow(const APSInt &Value, APSInt &Result) {
Result = Value;
@@ -99,7 +98,6 @@ static bool areEquivalentExpr(const Expr *Left, const Expr *Right) {
case Stmt::StringLiteralClass:
return cast<StringLiteral>(Left)->getBytes() ==
cast<StringLiteral>(Right)->getBytes();
-
case Stmt::DependentScopeDeclRefExprClass:
if (cast<DependentScopeDeclRefExpr>(Left)->getDeclName() !=
cast<DependentScopeDeclRefExpr>(Right)->getDeclName())
@@ -113,16 +111,14 @@ static bool areEquivalentExpr(const Expr *Left, const Expr *Right) {
case Stmt::MemberExprClass:
return cast<MemberExpr>(Left)->getMemberDecl() ==
cast<MemberExpr>(Right)->getMemberDecl();
-
+ case Stmt::CXXFunctionalCastExprClass:
case Stmt::CStyleCastExprClass:
- return cast<CStyleCastExpr>(Left)->getTypeAsWritten() ==
- cast<CStyleCastExpr>(Right)->getTypeAsWritten();
-
+ return cast<ExplicitCastExpr>(Left)->getTypeAsWritten() ==
+ cast<ExplicitCastExpr>(Right)->getTypeAsWritten();
case Stmt::CallExprClass:
case Stmt::ImplicitCastExprClass:
case Stmt::ArraySubscriptExprClass:
return true;
-
case Stmt::UnaryOperatorClass:
if (cast<UnaryOperator>(Left)->isIncrementDecrementOp())
return false;
@@ -282,7 +278,8 @@ static bool rangeSubsumesRange(BinaryOperatorKind OpcodeLHS,
}
}
-static void canonicalNegateExpr(BinaryOperatorKind &Opcode, APSInt &Value) {
+static void transformSubToCanonicalAddExpr(BinaryOperatorKind &Opcode,
+ APSInt &Value) {
if (Opcode == BO_Sub) {
Opcode = BO_Add;
Value = -Value;
@@ -295,32 +292,77 @@ AST_MATCHER(Expr, isIntegerConstantExpr) {
return Node.isIntegerConstantExpr(Finder->getASTContext());
}
-// Returns a matcher for integer constant expression.
+AST_MATCHER(BinaryOperator, operandsAreEquivalent) {
+ return areEquivalentExpr(Node.getLHS(), Node.getRHS());
+}
+
+AST_MATCHER(ConditionalOperator, expressionsAreEquivalent) {
+ return areEquivalentExpr(Node.getTrueExpr(), Node.getFalseExpr());
+}
+
+AST_MATCHER(CallExpr, parametersAreEquivalent) {
+ return Node.getNumArgs() == 2 &&
+ areEquivalentExpr(Node.getArg(0), Node.getArg(1));
+}
+
+AST_MATCHER(BinaryOperator, binaryOperatorIsInMacro) {
+ return Node.getOperatorLoc().isMacroID();
+}
+
+AST_MATCHER(ConditionalOperator, conditionalOperatorIsInMacro) {
+ return Node.getQuestionLoc().isMacroID() || Node.getColonLoc().isMacroID();
+}
+
+AST_MATCHER(Expr, isMacro) { return Node.getExprLoc().isMacroID(); }
+
+AST_MATCHER_P(Expr, expandedByMacro, llvm::StringSet<>, Names) {
+ const SourceManager &SM = Finder->getASTContext().getSourceManager();
+ const LangOptions &LO = Finder->getASTContext().getLangOpts();
+ SourceLocation Loc = Node.getExprLoc();
+ while (Loc.isMacroID()) {
+ StringRef MacroName = Lexer::getImmediateMacroName(Loc, SM, LO);
+ if (Names.count(MacroName))
+ return true;
+ Loc = SM.getImmediateMacroCallerLoc(Loc);
+ }
+ return false;
+}
+
+// Returns a matcher for integer constant expressions.
static ast_matchers::internal::Matcher<Expr>
matchIntegerConstantExpr(StringRef Id) {
std::string CstId = (Id + "-const").str();
return expr(isIntegerConstantExpr()).bind(CstId);
}
-// Retrieve the integer value matched by 'matchIntegerConstantExpr' with name
-// 'Id' and store it into 'Value'.
+// Retrieves the integer expression matched by 'matchIntegerConstantExpr' with
+// name 'Id' and stores it into 'ConstExpr', the value of the expression is
+// stored into `Value`.
static bool retrieveIntegerConstantExpr(const MatchFinder::MatchResult &Result,
- StringRef Id, APSInt &Value) {
+ StringRef Id, APSInt &Value,
+ const Expr *&ConstExpr) {
std::string CstId = (Id + "-const").str();
- const auto *CstExpr = Result.Nodes.getNodeAs<Expr>(CstId);
- return CstExpr && CstExpr->isIntegerConstantExpr(Value, *Result.Context);
+ ConstExpr = Result.Nodes.getNodeAs<Expr>(CstId);
+ return ConstExpr && ConstExpr->isIntegerConstantExpr(Value, *Result.Context);
+}
+
+// Overloaded `retrieveIntegerConstantExpr` for compatibility.
+static bool retrieveIntegerConstantExpr(const MatchFinder::MatchResult &Result,
+ StringRef Id, APSInt &Value) {
+ const Expr *ConstExpr = nullptr;
+ return retrieveIntegerConstantExpr(Result, Id, Value, ConstExpr);
}
-// Returns a matcher for a symbolic expression (any expression except ingeter
-// constant expression).
+// Returns a matcher for symbolic expressions (matches every expression except
+// ingeter constant expressions).
static ast_matchers::internal::Matcher<Expr> matchSymbolicExpr(StringRef Id) {
std::string SymId = (Id + "-sym").str();
return ignoringParenImpCasts(
expr(unless(isIntegerConstantExpr())).bind(SymId));
}
-// Retrieve the expression matched by 'matchSymbolicExpr' with name 'Id' and
-// store it into 'SymExpr'.
+// Retrieves the expression matched by 'matchSymbolicExpr' with name 'Id' and
+// stores it into 'SymExpr'.
static bool retrieveSymbolicExpr(const MatchFinder::MatchResult &Result,
StringRef Id, const Expr *&SymExpr) {
std::string SymId = (Id + "-sym").str();
@@ -348,7 +390,7 @@ matchBinOpIntegerConstantExpr(StringRef Id) {
return ignoringParenImpCasts(BinOpCstExpr);
}
-// Retrieve sub-expressions matched by 'matchBinOpIntegerConstantExpr' with
+// Retrieves sub-expressions matched by 'matchBinOpIntegerConstantExpr' with
// name 'Id'.
static bool
retrieveBinOpIntegerConstantExpr(const MatchFinder::MatchResult &Result,
@@ -362,7 +404,7 @@ retrieveBinOpIntegerConstantExpr(const MatchFinder::MatchResult &Result,
return false;
}
-// Matches relational expression: 'Expr <op> k' (i.e. x < 2, x != 3, 12 <= x).
+// Matches relational expressions: 'Expr <op> k' (i.e. x < 2, x != 3, 12 <= x).
static ast_matchers::internal::Matcher<Expr>
matchRelationalIntegerConstantExpr(StringRef Id) {
std::string CastId = (Id + "-cast").str();
@@ -388,6 +430,7 @@ matchRelationalIntegerConstantExpr(StringRef Id) {
hasUnaryOperand(anyOf(CastExpr, RelationalExpr)))
.bind(NegateId);
+ // Do not bind to double negation.
const auto NegateNegateRelationalExpr =
unaryOperator(hasOperatorName("!"),
hasUnaryOperand(unaryOperator(
@@ -398,13 +441,12 @@ matchRelationalIntegerConstantExpr(StringRef Id) {
NegateNegateRelationalExpr);
}
-// Retrieve sub-expressions matched by 'matchRelationalIntegerConstantExpr' with
+// Retrieves sub-expressions matched by 'matchRelationalIntegerConstantExpr' with
// name 'Id'.
-static bool
-retrieveRelationalIntegerConstantExpr(const MatchFinder::MatchResult &Result,
- StringRef Id, const Expr *&OperandExpr,
- BinaryOperatorKind &Opcode,
- const Expr *&Symbol, APSInt &Value) {
+static bool retrieveRelationalIntegerConstantExpr(
+ const MatchFinder::MatchResult &Result, StringRef Id,
+ const Expr *&OperandExpr, BinaryOperatorKind &Opcode, const Expr *&Symbol,
+ APSInt &Value, const Expr *&ConstExpr) {
std::string CastId = (Id + "-cast").str();
std::string SwapId = (Id + "-swap").str();
std::string NegateId = (Id + "-negate").str();
@@ -413,8 +455,10 @@ retrieveRelationalIntegerConstantExpr(const MatchFinder::MatchResult &Result,
// Operand received with explicit comparator.
Opcode = Bin->getOpcode();
OperandExpr = Bin;
- if (!retrieveIntegerConstantExpr(Result, Id, Value))
+
+ if (!retrieveIntegerConstantExpr(Result, Id, Value, ConstExpr))
return false;
+
} else if (const auto *Cast = Result.Nodes.getNodeAs<CastExpr>(CastId)) {
// Operand received with implicit comparator (cast).
Opcode = BO_NE;
@@ -431,56 +475,96 @@ retrieveRelationalIntegerConstantExpr(const MatchFinder::MatchResult &Result,
Opcode = BinaryOperator::reverseComparisonOp(Opcode);
if (Result.Nodes.getNodeAs<Expr>(NegateId))
Opcode = BinaryOperator::negateComparisonOp(Opcode);
-
return true;
}
-AST_MATCHER(BinaryOperator, operandsAreEquivalent) {
- return areEquivalentExpr(Node.getLHS(), Node.getRHS());
-}
+// Checks for expressions like (X == 4) && (Y != 9)
+static bool areSidesBinaryConstExpressions(const BinaryOperator *&BinOp, const ASTContext *AstCtx) {
+ const auto *LhsBinOp = dyn_cast<BinaryOperator>(BinOp->getLHS());
+ const auto *RhsBinOp = dyn_cast<BinaryOperator>(BinOp->getRHS());
-AST_MATCHER(ConditionalOperator, expressionsAreEquivalent) {
- return areEquivalentExpr(Node.getTrueExpr(), Node.getFalseExpr());
-}
+ if (!LhsBinOp || !RhsBinOp)
+ return false;
-AST_MATCHER(CallExpr, parametersAreEquivalent) {
- return Node.getNumArgs() == 2 &&
- areEquivalentExpr(Node.getArg(0), Node.getArg(1));
+ if ((LhsBinOp->getLHS()->isIntegerConstantExpr(*AstCtx) ||
+ LhsBinOp->getRHS()->isIntegerConstantExpr(*AstCtx)) &&
+ (RhsBinOp->getLHS()->isIntegerConstantExpr(*AstCtx) ||
+ RhsBinOp->getRHS()->isIntegerConstantExpr(*AstCtx)))
+ return true;
+ return false;
}
-AST_MATCHER(BinaryOperator, binaryOperatorIsInMacro) {
- return Node.getOperatorLoc().isMacroID();
+// Retrieves integer constant subexpressions from binary operator expressions
+// that have two equivalent sides
+// E.g.: from (X == 5) && (X == 5) retrieves 5 and 5.
+static bool retrieveConstExprFromBothSides(const BinaryOperator *&BinOp,
+ BinaryOperatorKind &MainOpcode,
+ BinaryOperatorKind &SideOpcode,
+ const Expr *&LhsConst,
+ const Expr *&RhsConst,
+ const ASTContext *AstCtx) {
+ assert(areSidesBinaryConstExpressions(BinOp, AstCtx) &&
+ "Both sides of binary operator must be constant expressions!");
+
+ MainOpcode = BinOp->getOpcode();
+
+ const auto *BinOpLhs = cast<BinaryOperator>(BinOp->getLHS());
+ const auto *BinOpRhs = cast<BinaryOperator>(BinOp->getRHS());
+
+ LhsConst = BinOpLhs->getLHS()->isIntegerConstantExpr(*AstCtx)
+ ? BinOpLhs->getLHS()
+ : BinOpLhs->getRHS();
+ RhsConst = BinOpRhs->getLHS()->isIntegerConstantExpr(*AstCtx)
+ ? BinOpRhs->getLHS()
+ : BinOpRhs->getRHS();
+
+ if (!LhsConst || !RhsConst)
+ return false;
+
+ assert(BinOpLhs->getOpcode() == BinOpRhs->getOpcode() &&
+ "Sides of the binary operator must be equivalent expressions!");
+
+ SideOpcode = BinOpLhs->getOpcode();
+
+ return true;
}
-AST_MATCHER(ConditionalOperator, conditionalOperatorIsInMacro) {
- return Node.getQuestionLoc().isMacroID() || Node.getColonLoc().isMacroID();
+static bool areExprsFromDifferentMacros(const Expr *LhsExpr,
+ const Expr *RhsExpr,
+ const ASTContext *AstCtx) {
+ if (!LhsExpr || !RhsExpr)
+ return false;
+
+ SourceLocation LhsLoc = LhsExpr->getExprLoc();
+ SourceLocation RhsLoc = RhsExpr->getExprLoc();
+
+ if (!LhsLoc.isMacroID() || !RhsLoc.isMacroID())
+ return false;
+
+ const SourceManager &SM = AstCtx->getSourceManager();
+ const LangOptions &LO = AstCtx->getLangOpts();
+
+ return !(Lexer::getImmediateMacroName(LhsLoc, SM, LO) ==
+ Lexer::getImmediateMacroName(RhsLoc, SM, LO));
}
-AST_MATCHER(Expr, isMacro) { return Node.getExprLoc().isMacroID(); }
+static bool areExprsMacroAndNonMacro(const Expr *&LhsExpr, const Expr *&RhsExpr) {
+ if (!LhsExpr || !RhsExpr)
+ return false;
-AST_MATCHER_P(Expr, expandedByMacro, std::set<std::string>, Names) {
- const SourceManager &SM = Finder->getASTContext().getSourceManager();
- const LangOptions &LO = Finder->getASTContext().getLangOpts();
- SourceLocation Loc = Node.getExprLoc();
- while (Loc.isMacroID()) {
- std::string MacroName = Lexer::getImmediateMacroName(Loc, SM, LO);
- if (Names.find(MacroName) != Names.end())
- return true;
- Loc = SM.getImmediateMacroCallerLoc(Loc);
- }
- return false;
+ SourceLocation LhsLoc = LhsExpr->getExprLoc();
+ SourceLocation RhsLoc = RhsExpr->getExprLoc();
+
+ return LhsLoc.isMacroID() != RhsLoc.isMacroID();
}
void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
const auto AnyLiteralExpr = ignoringParenImpCasts(
anyOf(cxxBoolLiteral(), characterLiteral(), integerLiteral()));
- std::vector<std::string> MacroNames =
- utils::options::parseStringList(KnownBannedMacroNames);
- std::set<std::string> Names(MacroNames.begin(), MacroNames.end());
-
- const auto BannedIntegerLiteral = integerLiteral(expandedByMacro(Names));
+ const auto BannedIntegerLiteral = integerLiteral(expandedByMacro(KnownBannedMacroNames));
+ // Binary with equivalent operands, like (X != 2 && X != 2).
Finder->addMatcher(
binaryOperator(anyOf(hasOperatorName("-"), hasOperatorName("/"),
hasOperatorName("%"), hasOperatorName("|"),
@@ -499,15 +583,16 @@ void RedundantExpressionCheck::registerMatchers(MatchFinder *Finder) {
.bind("binary"),
this);
+ // Conditional (trenary) operator with equivalent operands, like (Y ? X : X).
Finder->addMatcher(
conditionalOperator(expressionsAreEquivalent(),
// Filter noisy false positives.
unless(conditionalOperatorIsInMacro()),
- unless(hasTrueExpression(AnyLiteralExpr)),
unless(isInTemplateInstantiation()))
.bind("cond"),
this);
+ // Overloaded operators with equivalent operands.
Finder->addMatcher(
cxxOperatorCallExpr(
anyOf(
@@ -613,8 +698,8 @@ void RedundantExpressionCheck::checkArithmeticExpr(
!areEquivalentExpr(LhsSymbol, RhsSymbol))
return;
- canonicalNegateExpr(LhsOpcode, LhsValue);
- canonicalNegateExpr(RhsOpcode, RhsValue);
+ transformSubToCanonicalAddExpr(LhsOpcode, LhsValue);
+ transformSubToCanonicalAddExpr(RhsOpcode, RhsValue);
// Check expressions: x + 1 == x + 2 or x + 1 != x + 2.
if (LhsOpcode == BO_Add && RhsOpcode == BO_Add) {
@@ -674,20 +759,23 @@ void RedundantExpressionCheck::checkRelationalExpr(
if (const auto *ComparisonOperator = Result.Nodes.getNodeAs<BinaryOperator>(
"comparisons-of-symbol-and-const")) {
// Matched expressions are: (x <op> k1) <REL> (x <op> k2).
+ // E.g.: (X < 2) && (X > 4)
BinaryOperatorKind Opcode = ComparisonOperator->getOpcode();
const Expr *LhsExpr = nullptr, *RhsExpr = nullptr;
- APSInt LhsValue, RhsValue;
const Expr *LhsSymbol = nullptr, *RhsSymbol = nullptr;
+ const Expr *LhsConst = nullptr, *RhsConst = nullptr;
BinaryOperatorKind LhsOpcode, RhsOpcode;
+ APSInt LhsValue, RhsValue;
+
if (!retrieveRelationalIntegerConstantExpr(
- Result, "lhs", LhsExpr, LhsOpcode, LhsSymbol, LhsValue) ||
+ Result, "lhs", LhsExpr, LhsOpcode, LhsSymbol, LhsValue, LhsConst) ||
!retrieveRelationalIntegerConstantExpr(
- Result, "rhs", RhsExpr, RhsOpcode, RhsSymbol, RhsValue) ||
+ Result, "rhs", RhsExpr, RhsOpcode, RhsSymbol, RhsValue, RhsConst) ||
!areEquivalentExpr(LhsSymbol, RhsSymbol))
return;
- // Bring to a canonical form: smallest constant must be on the left side.
+ // Bring expr to a canonical form: smallest constant must be on the left.
if (APSInt::compareValues(LhsValue, RhsValue) > 0) {
std::swap(LhsExpr, RhsExpr);
std::swap(LhsValue, RhsValue);
@@ -695,10 +783,15 @@ void RedundantExpressionCheck::checkRelationalExpr(
std::swap(LhsOpcode, RhsOpcode);
}
+ // Constants come from two different macros, or one of them is a macro.
+ if (areExprsFromDifferentMacros(LhsConst, RhsConst, Result.Context) ||
+ areExprsMacroAndNonMacro(LhsConst, RhsConst))
+ return;
+
if ((Opcode == BO_LAnd || Opcode == BO_LOr) &&
areEquivalentRanges(LhsOpcode, LhsValue, RhsOpcode, RhsValue)) {
diag(ComparisonOperator->getOperatorLoc(),
- "equivalent expression on both side of logical operator");
+ "equivalent expression on both sides of logical operator");
return;
}
@@ -727,16 +820,62 @@ void RedundantExpressionCheck::checkRelationalExpr(
}
void RedundantExpressionCheck::check(const MatchFinder::MatchResult &Result) {
- if (const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binary"))
- diag(BinOp->getOperatorLoc(), "both side of operator are equivalent");
- if (const auto *CondOp = Result.Nodes.getNodeAs<ConditionalOperator>("cond"))
- diag(CondOp->getColonLoc(), "'true' and 'false' expression are equivalent");
- if (const auto *Call = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("call"))
+ if (const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>("binary")) {
+
+ // If the expression's constants are macros, check whether they are
+ // intentional.
+ if (areSidesBinaryConstExpressions(BinOp, Result.Context)) {
+ const Expr *LhsConst = nullptr, *RhsConst = nullptr;
+ BinaryOperatorKind MainOpcode, SideOpcode;
+
+ if(!retrieveConstExprFromBothSides(BinOp, MainOpcode, SideOpcode, LhsConst,
+ RhsConst, Result.Context))
+ return;
+
+ if (areExprsFromDifferentMacros(LhsConst, RhsConst, Result.Context) ||
+ areExprsMacroAndNonMacro(LhsConst, RhsConst))
+ return;
+ }
+
+ diag(BinOp->getOperatorLoc(), "both sides of operator are equivalent");
+ }
+
+ if (const auto *CondOp =
+ Result.Nodes.getNodeAs<ConditionalOperator>("cond")) {
+ const Expr *TrueExpr = CondOp->getTrueExpr();
+ const Expr *FalseExpr = CondOp->getFalseExpr();
+
+ if (areExprsFromDifferentMacros(TrueExpr, FalseExpr, Result.Context) ||
+ areExprsMacroAndNonMacro(TrueExpr, FalseExpr))
+ return;
+ diag(CondOp->getColonLoc(),
+ "'true' and 'false' expressions are equivalent");
+ }
+
+ if (const auto *Call = Result.Nodes.getNodeAs<CXXOperatorCallExpr>("call")) {
diag(Call->getOperatorLoc(),
- "both side of overloaded operator are equivalent");
+ "both sides of overloaded operator are equivalent");
+ }
+ // Check for the following bound expressions:
+ // - "binop-const-compare-to-sym",
+ // - "binop-const-compare-to-binop-const",
+ // Produced message:
+ // -> "logical expression is always false/true"
checkArithmeticExpr(Result);
+
+ // Check for the following bound expression:
+ // - "binop-const-compare-to-const",
+ // Produced message:
+ // -> "logical expression is always false/true"
checkBitwiseExpr(Result);
+
+ // Check for te following bound expression:
+ // - "comparisons-of-symbol-and-const",
+ // Produced messages:
+ // -> "equivalent expression on both sides of logical operator",
+ // -> "logical expression is always false/true"
+ // -> "expression is redundant"
checkRelationalExpr(Result);
}
diff --git a/clang-tidy/misc/RedundantExpressionCheck.h b/clang-tidy/misc/RedundantExpressionCheck.h
index 59d2c8f9..c0f8bf5e 100644
--- a/clang-tidy/misc/RedundantExpressionCheck.h
+++ b/clang-tidy/misc/RedundantExpressionCheck.h
@@ -16,7 +16,8 @@ namespace clang {
namespace tidy {
namespace misc {
-/// Detect useless or suspicious redundant expressions.
+/// The checker detects expressions that are redundant, because they contain
+/// ineffective, useless parts.
///
/// For the user-facing documentation see:
/// http://clang.llvm.org/extra/clang-tidy/checks/misc-redundant-expression.html
diff --git a/clang-tidy/tool/run-clang-tidy.py b/clang-tidy/tool/run-clang-tidy.py
index 7792c5a1..15b21d76 100755
--- a/clang-tidy/tool/run-clang-tidy.py
+++ b/clang-tidy/tool/run-clang-tidy.py
@@ -68,6 +68,12 @@ def find_compilation_database(path):
return os.path.realpath(result)
+def make_absolute(f, directory):
+ if os.path.isabs(f):
+ return f
+ return os.path.normpath(os.path.join(directory, f))
+
+
def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path,
header_filter, extra_arg, extra_arg_before, quiet):
"""Gets a command line for clang-tidy."""
@@ -223,7 +229,8 @@ def main():
# Load the database and extract all files.
database = json.load(open(os.path.join(build_path, db_path)))
- files = [entry['file'] for entry in database]
+ files = [make_absolute(entry['file'], entry['directory'])
+ for entry in database]
max_task = args.j
if max_task == 0:
diff --git a/clangd/CMakeLists.txt b/clangd/CMakeLists.txt
index 1825d110..a81da615 100644
--- a/clangd/CMakeLists.txt
+++ b/clangd/CMakeLists.txt
@@ -10,6 +10,7 @@ add_clang_library(clangDaemon
DraftStore.cpp
GlobalCompilationDatabase.cpp
JSONRPCDispatcher.cpp
+ JSONExpr.cpp
Logger.cpp
Protocol.cpp
ProtocolHandlers.cpp
diff --git a/clangd/ClangdLSPServer.cpp b/clangd/ClangdLSPServer.cpp
index 1689a5fd..aa73e737 100644
--- a/clangd/ClangdLSPServer.cpp
+++ b/clangd/ClangdLSPServer.cpp
@@ -10,45 +10,58 @@
#include "ClangdLSPServer.h"
#include "JSONRPCDispatcher.h"
+#include "llvm/Support/FormatVariadic.h"
+
using namespace clang::clangd;
using namespace clang;
namespace {
-std::string
+std::vector<TextEdit>
replacementsToEdits(StringRef Code,
const std::vector<tooling::Replacement> &Replacements) {
// Turn the replacements into the format specified by the Language Server
// Protocol. Fuse them into one big JSON array.
- std::string Edits;
+ std::vector<TextEdit> Edits;
for (auto &R : Replacements) {
Range ReplacementRange = {
offsetToPosition(Code, R.getOffset()),
offsetToPosition(Code, R.getOffset() + R.getLength())};
- TextEdit TE = {ReplacementRange, R.getReplacementText()};
- Edits += TextEdit::unparse(TE);
- Edits += ',';
+ Edits.push_back({ReplacementRange, R.getReplacementText()});
}
- if (!Edits.empty())
- Edits.pop_back();
-
return Edits;
}
} // namespace
void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
- C.reply(
- R"({"capabilities":{
- "textDocumentSync": 1,
- "documentFormattingProvider": true,
- "documentRangeFormattingProvider": true,
- "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
- "codeActionProvider": true,
- "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
- "signatureHelpProvider": {"triggerCharacters": ["(",","]},
- "definitionProvider": true
- }})");
+ C.reply(json::obj{
+ {{"capabilities",
+ json::obj{
+ {"textDocumentSync", 1},
+ {"documentFormattingProvider", true},
+ {"documentRangeFormattingProvider", true},
+ {"documentOnTypeFormattingProvider",
+ json::obj{
+ {"firstTriggerCharacter", "}"},
+ {"moreTriggerCharacter", {}},
+ }},
+ {"codeActionProvider", true},
+ {"completionProvider",
+ json::obj{
+ {"resolveProvider", false},
+ {"triggerCharacters", {".", ">", ":"}},
+ }},
+ {"signatureHelpProvider",
+ json::obj{
+ {"triggerCharacters", {"(", ","}},
+ }},
+ {"definitionProvider", true},
+ {"executeCommandProvider",
+ json::obj{
+ {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}},
+ }},
+ }}}});
if (Params.rootUri && !Params.rootUri->file.empty())
Server.setRootPath(Params.rootUri->file);
else if (Params.rootPath && !Params.rootPath->empty())
@@ -58,7 +71,7 @@ void ClangdLSPServer::onInitialize(Ctx C, InitializeParams &Params) {
void ClangdLSPServer::onShutdown(Ctx C, ShutdownParams &Params) {
// Do essentially nothing, just say we're ready to exit.
ShutdownRequestReceived = true;
- C.reply("null");
+ C.reply(nullptr);
}
void ClangdLSPServer::onExit(Ctx C, ExitParams &Params) { IsDone = true; }
@@ -74,7 +87,8 @@ void ClangdLSPServer::onDocumentDidOpen(Ctx C,
void ClangdLSPServer::onDocumentDidChange(Ctx C,
DidChangeTextDocumentParams &Params) {
if (Params.contentChanges.size() != 1)
- return C.replyError(-32602, "can only apply one change at a time");
+ return C.replyError(ErrorCode::InvalidParams,
+ "can only apply one change at a time");
// We only support full syncing right now.
Server.addDocument(Params.textDocument.uri.file,
Params.contentChanges[0].text);
@@ -84,6 +98,35 @@ void ClangdLSPServer::onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) {
Server.onFileEvent(Params);
}
+void ClangdLSPServer::onCommand(Ctx C, ExecuteCommandParams &Params) {
+ if (Params.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND &&
+ Params.workspaceEdit) {
+ // The flow for "apply-fix" :
+ // 1. We publish a diagnostic, including fixits
+ // 2. The user clicks on the diagnostic, the editor asks us for code actions
+ // 3. We send code actions, with the fixit embedded as context
+ // 4. The user selects the fixit, the editor asks us to apply it
+ // 5. We unwrap the changes and send them back to the editor
+ // 6. The editor applies the changes (applyEdit), and sends us a reply (but
+ // we ignore it)
+
+ ApplyWorkspaceEditParams ApplyEdit;
+ ApplyEdit.edit = *Params.workspaceEdit;
+ C.reply("Fix applied.");
+ // We don't need the response so id == 1 is OK.
+ // Ideally, we would wait for the response and if there is no error, we
+ // would reply success/failure to the original RPC.
+ C.call("workspace/applyEdit", ApplyWorkspaceEditParams::unparse(ApplyEdit));
+ } else {
+ // We should not get here because ExecuteCommandParams would not have
+ // parsed in the first place and this handler should not be called. But if
+ // more commands are added, this will be here has a safe guard.
+ C.replyError(
+ ErrorCode::InvalidParams,
+ llvm::formatv("Unsupported command \"{0}\".", Params.command).str());
+ }
+}
+
void ClangdLSPServer::onDocumentDidClose(Ctx C,
DidCloseTextDocumentParams &Params) {
Server.removeDocument(Params.textDocument.uri.file);
@@ -93,49 +136,45 @@ void ClangdLSPServer::onDocumentOnTypeFormatting(
Ctx C, DocumentOnTypeFormattingParams &Params) {
auto File = Params.textDocument.uri.file;
std::string Code = Server.getDocument(File);
- std::string Edits =
- replacementsToEdits(Code, Server.formatOnType(File, Params.position));
- C.reply("[" + Edits + "]");
+ C.reply(json::ary(
+ replacementsToEdits(Code, Server.formatOnType(File, Params.position))));
}
void ClangdLSPServer::onDocumentRangeFormatting(
Ctx C, DocumentRangeFormattingParams &Params) {
auto File = Params.textDocument.uri.file;
std::string Code = Server.getDocument(File);
- std::string Edits =
- replacementsToEdits(Code, Server.formatRange(File, Params.range));
- C.reply("[" + Edits + "]");
+ C.reply(json::ary(
+ replacementsToEdits(Code, Server.formatRange(File, Params.range))));
}
void ClangdLSPServer::onDocumentFormatting(Ctx C,
DocumentFormattingParams &Params) {
auto File = Params.textDocument.uri.file;
std::string Code = Server.getDocument(File);
- std::string Edits = replacementsToEdits(Code, Server.formatFile(File));
- C.reply("[" + Edits + "]");
+ C.reply(json::ary(replacementsToEdits(Code, Server.formatFile(File))));
}
void ClangdLSPServer::onCodeAction(Ctx C, CodeActionParams &Params) {
// We provide a code action for each diagnostic at the requested location
// which has FixIts available.
std::string Code = Server.getDocument(Params.textDocument.uri.file);
- std::string Commands;
+ json::ary Commands;
for (Diagnostic &D : Params.context.diagnostics) {
std::vector<clang::tooling::Replacement> Fixes =
getFixIts(Params.textDocument.uri.file, D);
- std::string Edits = replacementsToEdits(Code, Fixes);
-
- if (!Edits.empty())
- Commands +=
- R"({"title":"Apply FixIt ')" + llvm::yaml::escape(D.message) +
- R"('", "command": "clangd.applyFix", "arguments": [")" +
- llvm::yaml::escape(Params.textDocument.uri.uri) +
- R"(", [)" + Edits +
- R"(]]},)";
+ auto Edits = replacementsToEdits(Code, Fixes);
+ if (!Edits.empty()) {
+ WorkspaceEdit WE;
+ WE.changes = {{Params.textDocument.uri.uri, std::move(Edits)}};
+ Commands.push_back(json::obj{
+ {"title", llvm::formatv("Apply FixIt {0}", D.message)},
+ {"command", ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND},
+ {"arguments", {WE}},
+ });
+ }
}
- if (!Commands.empty())
- Commands.pop_back();
- C.reply("[" + Commands + "]");
+ C.reply(std::move(Commands));
}
void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
@@ -147,15 +186,7 @@ void ClangdLSPServer::onCompletion(Ctx C, TextDocumentPositionParams &Params) {
// had an API that would allow to attach callbacks to
// futures returned by ClangdServer.
.Value;
-
- std::string Completions;
- for (const auto &Item : Items) {
- Completions += CompletionItem::unparse(Item);
- Completions += ",";
- }
- if (!Completions.empty())
- Completions.pop_back();
- C.reply("[" + Completions + "]");
+ C.reply(json::ary(Items));
}
void ClangdLSPServer::onSignatureHelp(Ctx C,
@@ -164,8 +195,9 @@ void ClangdLSPServer::onSignatureHelp(Ctx C,
Params.textDocument.uri.file,
Position{Params.position.line, Params.position.character});
if (!SignatureHelp)
- return C.replyError(-32602, llvm::toString(SignatureHelp.takeError()));
- C.reply(SignatureHelp::unparse(SignatureHelp->Value));
+ return C.replyError(ErrorCode::InvalidParams,
+ llvm::toString(SignatureHelp.takeError()));
+ C.reply(SignatureHelp->Value);
}
void ClangdLSPServer::onGoToDefinition(Ctx C,
@@ -174,23 +206,16 @@ void ClangdLSPServer::onGoToDefinition(Ctx C,
Params.textDocument.uri.file,
Position{Params.position.line, Params.position.character});
if (!Items)
- return C.replyError(-32602, llvm::toString(Items.takeError()));
-
- std::string Locations;
- for (const auto &Item : Items->Value) {
- Locations += Location::unparse(Item);
- Locations += ",";
- }
- if (!Locations.empty())
- Locations.pop_back();
- C.reply("[" + Locations + "]");
+ return C.replyError(ErrorCode::InvalidParams,
+ llvm::toString(Items.takeError()));
+ C.reply(json::ary(Items->Value));
}
void ClangdLSPServer::onSwitchSourceHeader(Ctx C,
TextDocumentIdentifier &Params) {
llvm::Optional<Path> Result = Server.switchSourceHeader(Params.uri.file);
std::string ResultUri;
- C.reply(Result ? URI::unparse(URI::fromFile(*Result)) : R"("")");
+ C.reply(Result ? URI::fromFile(*Result).uri : "");
}
ClangdLSPServer::ClangdLSPServer(JSONOutput &Out, unsigned AsyncThreadsCount,
@@ -209,7 +234,7 @@ bool ClangdLSPServer::run(std::istream &In) {
// Set up JSONRPCDispatcher.
JSONRPCDispatcher Dispatcher(
[](RequestContext Ctx, llvm::yaml::MappingNode *Params) {
- Ctx.replyError(-32601, "method not found");
+ Ctx.replyError(ErrorCode::MethodNotFound, "method not found");
});
registerCallbackHandlers(Dispatcher, Out, /*Callbacks=*/*this);
@@ -240,17 +265,16 @@ ClangdLSPServer::getFixIts(StringRef File, const clangd::Diagnostic &D) {
void ClangdLSPServer::onDiagnosticsReady(
PathRef File, Tagged<std::vector<DiagWithFixIts>> Diagnostics) {
- std::string DiagnosticsJSON;
+ json::ary DiagnosticsJSON;
DiagnosticToReplacementMap LocalFixIts; // Temporary storage
for (auto &DiagWithFixes : Diagnostics.Value) {
auto Diag = DiagWithFixes.Diag;
- DiagnosticsJSON +=
- R"({"range":)" + Range::unparse(Diag.range) +
- R"(,"severity":)" + std::to_string(Diag.severity) +
- R"(,"message":")" + llvm::yaml::escape(Diag.message) +
- R"("},)";
-
+ DiagnosticsJSON.push_back(json::obj{
+ {"range", Diag.range},
+ {"severity", Diag.severity},
+ {"message", Diag.message},
+ });
// We convert to Replacements to become independent of the SourceManager.
auto &FixItsForDiagnostic = LocalFixIts[Diag];
std::copy(DiagWithFixes.FixIts.begin(), DiagWithFixes.FixIts.end(),
@@ -265,10 +289,13 @@ void ClangdLSPServer::onDiagnosticsReady(
}
// Publish diagnostics.
- if (!DiagnosticsJSON.empty())
- DiagnosticsJSON.pop_back(); // Drop trailing comma.
- Out.writeMessage(
- R"({"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":")" +
- URI::fromFile(File).uri + R"(","diagnostics":[)" + DiagnosticsJSON +
- R"(]}})");
+ Out.writeMessage(json::obj{
+ {"jsonrpc", "2.0"},
+ {"method", "textDocument/publishDiagnostics"},
+ {"params",
+ json::obj{
+ {"uri", URI::fromFile(File)},
+ {"diagnostics", std::move(DiagnosticsJSON)},
+ }},
+ });
}
diff --git a/clangd/ClangdLSPServer.h b/clangd/ClangdLSPServer.h
index 261ff611..22f73cb5 100644
--- a/clangd/ClangdLSPServer.h
+++ b/clangd/ClangdLSPServer.h
@@ -69,6 +69,7 @@ private:
void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) override;
void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) override;
void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) override;
+ void onCommand(Ctx C, ExecuteCommandParams &Params) override;
std::vector<clang::tooling::Replacement>
getFixIts(StringRef File, const clangd::Diagnostic &D);
diff --git a/clangd/ClangdUnit.cpp b/clangd/ClangdUnit.cpp
index 82ea54d0..aeeb0e77 100644
--- a/clangd/ClangdUnit.cpp
+++ b/clangd/ClangdUnit.cpp
@@ -389,6 +389,7 @@ public:
assert(CCS && "Expected the CodeCompletionString to be non-null");
Items.push_back(ProcessCodeCompleteResult(Result, *CCS));
}
+ std::sort(Items.begin(), Items.end());
}
GlobalCodeCompletionAllocator &getAllocator() override { return *Allocator; }
@@ -965,10 +966,15 @@ private:
End.character = SourceMgr.getSpellingColumnNumber(LocEnd) - 1;
Range R = {Begin, End};
Location L;
- L.uri = URI::fromFile(
- SourceMgr.getFilename(SourceMgr.getSpellingLoc(LocStart)));
- L.range = R;
- DeclarationLocations.push_back(L);
+ if (const FileEntry *F =
+ SourceMgr.getFileEntryForID(SourceMgr.getFileID(LocStart))) {
+ StringRef FilePath = F->tryGetRealPathName();
+ if (FilePath.empty())
+ FilePath = F->getName();
+ L.uri = URI::fromFile(FilePath);
+ L.range = R;
+ DeclarationLocations.push_back(L);
+ }
}
void finish() override {
diff --git a/clangd/JSONExpr.cpp b/clangd/JSONExpr.cpp
new file mode 100644
index 00000000..968892c2
--- /dev/null
+++ b/clangd/JSONExpr.cpp
@@ -0,0 +1,221 @@
+#include "JSONExpr.h"
+
+#include "llvm/Support/Format.h"
+
+using namespace llvm;
+namespace clang {
+namespace clangd {
+namespace json {
+
+void Expr::copyFrom(const Expr &M) {
+ Type = M.Type;
+ switch (Type) {
+ case T_Null:
+ case T_Boolean:
+ case T_Number:
+ memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
+ break;
+ case T_StringRef:
+ create<StringRef>(M.as<StringRef>());
+ break;
+ case T_String:
+ create<std::string>(M.as<std::string>());
+ break;
+ case T_Object:
+ create<Object>(M.as<Object>());
+ break;
+ case T_Array:
+ create<Array>(M.as<Array>());
+ break;
+ }
+}
+
+void Expr::moveFrom(const Expr &&M) {
+ Type = M.Type;
+ switch (Type) {
+ case T_Null:
+ case T_Boolean:
+ case T_Number:
+ memcpy(Union.buffer, M.Union.buffer, sizeof(Union.buffer));
+ break;
+ case T_StringRef:
+ create<StringRef>(M.as<StringRef>());
+ break;
+ case T_String:
+ create<std::string>(std::move(M.as<std::string>()));
+ M.Type = T_Null;
+ break;
+ case T_Object:
+ create<Object>(std::move(M.as<Object>()));
+ M.Type = T_Null;
+ break;
+ case T_Array:
+ create<Array>(std::move(M.as<Array>()));
+ M.Type = T_Null;
+ break;
+ }
+}
+
+void Expr::destroy() {
+ switch (Type) {
+ case T_Null:
+ case T_Boolean:
+ case T_Number:
+ break;
+ case T_StringRef:
+ as<StringRef>().~StringRef();
+ break;
+ case T_String:
+ as<std::string>().~basic_string();
+ break;
+ case T_Object:
+ as<Object>().~Object();
+ break;
+ case T_Array:
+ as<Array>().~Array();
+ break;
+ }
+}
+
+} // namespace json
+} // namespace clangd
+} // namespace clang
+
+namespace {
+void quote(llvm::raw_ostream &OS, llvm::StringRef S) {
+ OS << '\"';
+ for (unsigned char C : S) {
+ if (C == 0x22 || C == 0x5C)
+ OS << '\\';
+ if (C >= 0x20) {
+ OS << C;
+ continue;
+ }
+ OS << '\\';
+ switch (C) {
+ // A few characters are common enough to make short escapes worthwhile.
+ case '\t':
+ OS << 't';
+ break;
+ case '\n':
+ OS << 'n';
+ break;
+ case '\r':
+ OS << 'r';
+ break;
+ default:
+ OS << 'u';
+ llvm::write_hex(OS, C, llvm::HexPrintStyle::Lower, 4);
+ break;
+ }
+ }
+ OS << '\"';
+}
+
+enum IndenterAction {
+ Indent,
+ Outdent,
+ Newline,
+ Space,
+};
+} // namespace
+
+// Prints JSON. The indenter can be used to control formatting.
+template <typename Indenter>
+void clang::clangd::json::Expr::print(raw_ostream &OS,
+ const Indenter &I) const {
+ switch (Type) {
+ case T_Null:
+ OS << "null";
+ break;
+ case T_Boolean:
+ OS << (as<bool>() ? "true" : "false");
+ break;
+ case T_Number:
+ OS << format("%g", as<double>());
+ break;
+ case T_StringRef:
+ quote(OS, as<StringRef>());
+ break;
+ case T_String:
+ quote(OS, as<std::string>());
+ break;
+ case T_Object: {
+ bool Comma = false;
+ OS << '{';
+ I(Indent);
+ for (const auto &P : as<Expr::Object>()) {
+ if (Comma)
+ OS << ',';
+ Comma = true;
+ I(Newline);
+ quote(OS, P.first);
+ OS << ':';
+ I(Space);
+ P.second.print(OS, I);
+ }
+ I(Outdent);
+ if (Comma)
+ I(Newline);
+ OS << '}';
+ break;
+ }
+ case T_Array: {
+ bool Comma = false;
+ OS << '[';
+ I(Indent);
+ for (const auto &E : as<Expr::Array>()) {
+ if (Comma)
+ OS << ',';
+ Comma = true;
+ I(Newline);
+ E.print(OS, I);
+ }
+ I(Outdent);
+ if (Comma)
+ I(Newline);
+ OS << ']';
+ break;
+ }
+ }
+}
+
+namespace clang {
+namespace clangd {
+namespace json {
+llvm::raw_ostream &operator<<(raw_ostream &OS, const Expr &E) {
+ E.print(OS, [](IndenterAction A) { /*ignore*/ });
+ return OS;
+}
+} // namespace json
+} // namespace clangd
+} // namespace clang
+
+void llvm::format_provider<clang::clangd::json::Expr>::format(
+ const clang::clangd::json::Expr &E, raw_ostream &OS, StringRef Options) {
+ if (Options.empty()) {
+ OS << E;
+ return;
+ }
+ unsigned IndentAmount = 0;
+ if (Options.getAsInteger(/*Radix=*/10, IndentAmount))
+ assert(false && "json::Expr format options should be an integer");
+ unsigned IndentLevel = 0;
+ E.print(OS, [&](IndenterAction A) {
+ switch (A) {
+ case Newline:
+ OS << '\n';
+ OS.indent(IndentLevel);
+ break;
+ case Space:
+ OS << ' ';
+ break;
+ case Indent:
+ IndentLevel += IndentAmount;
+ break;
+ case Outdent:
+ IndentLevel -= IndentAmount;
+ break;
+ };
+ });
+}
diff --git a/clangd/JSONExpr.h b/clangd/JSONExpr.h
new file mode 100644
index 00000000..b9cf808b
--- /dev/null
+++ b/clangd/JSONExpr.h
@@ -0,0 +1,247 @@
+//===--- JSONExpr.h - composable JSON expressions ---------------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===---------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSON_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSON_H
+
+#include <map>
+
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/raw_ostream.h"
+
+namespace clang {
+namespace clangd {
+namespace json {
+
+// An Expr is an opaque temporary JSON structure used to compose documents.
+// They can be copied, but should generally be moved.
+//
+// You can implicitly construct literals from:
+// - strings: std::string, SmallString, formatv, StringRef, char*
+// (char*, and StringRef are references, not copies!)
+// - numbers
+// - booleans
+// - null: nullptr
+// - arrays: {"foo", 42.0, false}
+// - serializable things: any T with a T::unparse(const T&) -> Expr
+//
+// They can also be constructed from object/array helpers:
+// - json::obj is a type like map<StringExpr, Expr>
+// - json::ary is a type like vector<Expr>
+// These can be list-initialized, or used to build up collections in a loop.
+// json::ary(Collection) converts all items in a collection to Exprs.
+//
+// Exprs can be serialized to JSON:
+// 1) raw_ostream << Expr // Basic formatting.
+// 2) raw_ostream << formatv("{0}", Expr) // Basic formatting.
+// 3) raw_ostream << formatv("{0:2}", Expr) // Pretty-print with indent 2.
+class Expr {
+public:
+ class Object;
+ class ObjectKey;
+ class Array;
+
+ // It would be nice to have Expr() be null. But that would make {} null too...
+ Expr(const Expr &M) { copyFrom(M); }
+ Expr(Expr &&M) { moveFrom(std::move(M)); }
+ // "cheating" move-constructor for moving from initializer_list.
+ Expr(const Expr &&M) { moveFrom(std::move(M)); }
+ Expr(std::initializer_list<Expr> Elements) : Expr(Array(Elements)) {}
+ Expr(Array &&Elements) : Type(T_Array) { create<Array>(std::move(Elements)); }
+ Expr(Object &&Properties) : Type(T_Object) {
+ create<Object>(std::move(Properties));
+ }
+ // Strings: types with value semantics.
+ Expr(std::string &&V) : Type(T_String) { create<std::string>(std::move(V)); }
+ Expr(const std::string &V) : Type(T_String) { create<std::string>(V); }
+ Expr(const llvm::SmallVectorImpl<char> &V) : Type(T_String) {
+ create<std::string>(V.begin(), V.end());
+ }
+ Expr(const llvm::formatv_object_base &V) : Expr(V.str()){};
+ // Strings: types with reference semantics.
+ Expr(llvm::StringRef V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
+ Expr(const char *V) : Type(T_StringRef) { create<llvm::StringRef>(V); }
+ Expr(std::nullptr_t) : Type(T_Null) {}
+ // Prevent implicit conversions to boolean.
+ template <typename T, typename = typename std::enable_if<
+ std::is_same<T, bool>::value>::type>
+ Expr(T B) : Type(T_Boolean) {
+ create<bool>(B);
+ }
+ // Numbers: arithmetic types that are not boolean.
+ template <
+ typename T,
+ typename = typename std::enable_if<std::is_arithmetic<T>::value>::type,
+ typename = typename std::enable_if<std::integral_constant<
+ bool, !std::is_same<T, bool>::value>::value>::type>
+ Expr(T D) : Type(T_Number) {
+ create<double>(D);
+ }
+ // Types with a static T::unparse function returning an Expr.
+ // FIXME: should this be a free unparse() function found by ADL?
+ template <typename T,
+ typename = typename std::enable_if<std::is_same<
+ Expr, decltype(T::unparse(*(const T *)nullptr))>::value>>
+ Expr(const T &V) : Expr(T::unparse(V)) {}
+
+ Expr &operator=(const Expr &M) {
+ destroy();
+ copyFrom(M);
+ return *this;
+ }
+ Expr &operator=(Expr &&M) {
+ destroy();
+ moveFrom(std::move(M));
+ return *this;
+ }
+ ~Expr() { destroy(); }
+
+ friend llvm::raw_ostream &operator<<(llvm::raw_ostream &, const Expr &);
+
+private:
+ void destroy();
+ void copyFrom(const Expr &M);
+ // We allow moving from *const* Exprs, by marking all members as mutable!
+ // This hack is needed to support initializer-list syntax efficiently.
+ // (std::initializer_list<T> is a container of const T).
+ void moveFrom(const Expr &&M);
+
+ template <typename T, typename... U> void create(U &&... V) {
+ new (&as<T>()) T(std::forward<U>(V)...);
+ }
+ template <typename T> T &as() const {
+ return *reinterpret_cast<T *>(Union.buffer);
+ }
+
+ template <typename Indenter>
+ void print(llvm::raw_ostream &, const Indenter &) const;
+ friend struct llvm::format_provider<clang::clangd::json::Expr>;
+
+ enum ExprType : char {
+ T_Null,
+ T_Boolean,
+ T_Number,
+ T_StringRef,
+ T_String,
+ T_Object,
+ T_Array,
+ };
+ mutable ExprType Type;
+
+public:
+ // ObjectKey is a used to capture keys in Expr::Objects. It's like Expr but:
+ // - only strings are allowed
+ // - it's copyable (for std::map)
+ // - we're slightly more eager to copy, to allow efficient key compares
+ // - it's optimized for the string literal case (Owned == nullptr)
+ class ObjectKey {
+ public:
+ ObjectKey(const char *S) : Data(S) {}
+ ObjectKey(llvm::StringRef S) : Data(S) {}
+ ObjectKey(std::string &&V)
+ : Owned(new std::string(std::move(V))), Data(*Owned) {}
+ ObjectKey(const std::string &V) : Owned(new std::string(V)), Data(*Owned) {}
+ ObjectKey(const llvm::SmallVectorImpl<char> &V)
+ : ObjectKey(std::string(V.begin(), V.end())) {}
+ ObjectKey(const llvm::formatv_object_base &V) : ObjectKey(V.str()) {}
+
+ ObjectKey(const ObjectKey &C) { *this = C; }
+ ObjectKey(ObjectKey &&C) : ObjectKey(static_cast<const ObjectKey &&>(C)) {}
+ ObjectKey &operator=(const ObjectKey &C) {
+ if (C.Owned) {
+ Owned.reset(new std::string(*C.Owned));
+ Data = *Owned;
+ } else {
+ Data = C.Data;
+ }
+ return *this;
+ }
+ ObjectKey &operator=(ObjectKey &&) = default;
+
+ operator llvm::StringRef() const { return Data; }
+
+ friend bool operator<(const ObjectKey &L, const ObjectKey &R) {
+ return L.Data < R.Data;
+ }
+
+ // "cheating" move-constructor for moving from initializer_list.
+ ObjectKey(const ObjectKey &&V) {
+ Owned = std::move(V.Owned);
+ Data = V.Data;
+ }
+
+ private:
+ mutable std::unique_ptr<std::string> Owned; // mutable for cheating.
+ llvm::StringRef Data;
+ };
+
+ class Object : public std::map<ObjectKey, Expr> {
+ public:
+ explicit Object() {}
+ // Use a custom struct for list-init, because pair forces extra copies.
+ struct KV;
+ explicit Object(std::initializer_list<KV> Properties);
+
+ // Allow [] as if Expr was default-constructible as null.
+ Expr &operator[](const ObjectKey &K) {
+ return emplace(K, Expr(nullptr)).first->second;
+ }
+ Expr &operator[](ObjectKey &&K) {
+ return emplace(std::move(K), Expr(nullptr)).first->second;
+ }
+ };
+
+ class Array : public std::vector<Expr> {
+ public:
+ explicit Array() {}
+ explicit Array(std::initializer_list<Expr> Elements) {
+ reserve(Elements.size());
+ for (const Expr &V : Elements)
+ emplace_back(std::move(V));
+ };
+ template <typename Collection> explicit Array(const Collection &C) {
+ for (const auto &V : C)
+ emplace_back(V);
+ }
+ };
+
+private:
+ mutable llvm::AlignedCharArrayUnion<bool, double, llvm::StringRef,
+ std::string, Array, Object>
+ Union;
+};
+
+struct Expr::Object::KV {
+ ObjectKey K;
+ Expr V;
+};
+
+inline Expr::Object::Object(std::initializer_list<KV> Properties) {
+ for (const auto &P : Properties)
+ emplace(std::move(P.K), std::move(P.V));
+}
+
+// Give Expr::{Object,Array} more convenient names for literal use.
+using obj = Expr::Object;
+using ary = Expr::Array;
+
+} // namespace json
+} // namespace clangd
+} // namespace clang
+
+namespace llvm {
+template <> struct format_provider<clang::clangd::json::Expr> {
+ static void format(const clang::clangd::json::Expr &, raw_ostream &,
+ StringRef);
+};
+} // namespace llvm
+
+#endif
diff --git a/clangd/JSONRPCDispatcher.cpp b/clangd/JSONRPCDispatcher.cpp
index 121ddb9b..557fadba 100644
--- a/clangd/JSONRPCDispatcher.cpp
+++ b/clangd/JSONRPCDispatcher.cpp
@@ -8,9 +8,11 @@
//===----------------------------------------------------------------------===//
#include "JSONRPCDispatcher.h"
+#include "JSONExpr.h"
#include "ProtocolHandlers.h"
#include "Trace.h"
#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/YAMLParser.h"
#include <istream>
@@ -18,17 +20,22 @@
using namespace clang;
using namespace clangd;
-void JSONOutput::writeMessage(const Twine &Message) {
- llvm::SmallString<128> Storage;
- StringRef M = Message.toStringRef(Storage);
+void JSONOutput::writeMessage(const json::Expr &Message) {
+ std::string S;
+ llvm::raw_string_ostream OS(S);
+ if (Pretty)
+ OS << llvm::formatv("{0:2}", Message);
+ else
+ OS << Message;
+ OS.flush();
std::lock_guard<std::mutex> Guard(StreamMutex);
// Log without headers.
- Logs << "--> " << M << '\n';
+ Logs << "--> " << S << '\n';
Logs.flush();
// Emit message with header.
- Outs << "Content-Length: " << M.size() << "\r\n\r\n" << M;
+ Outs << "Content-Length: " << S.size() << "\r\n\r\n" << S;
Outs.flush();
}
@@ -47,25 +54,40 @@ void JSONOutput::mirrorInput(const Twine &Message) {
InputMirror->flush();
}
-void RequestContext::reply(const llvm::Twine &Result) {
- if (ID.empty()) {
+void RequestContext::reply(json::Expr &&Result) {
+ if (!ID) {
Out.log("Attempted to reply to a notification!\n");
return;
}
- Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":)") + ID +
- R"(,"result":)" + Result + "}");
+ Out.writeMessage(json::obj{
+ {"jsonrpc", "2.0"},
+ {"id", *ID},
+ {"result", std::move(Result)},
+ });
}
-void RequestContext::replyError(int code, const llvm::StringRef &Message) {
- Out.log("Error " + llvm::Twine(code) + ": " + Message + "\n");
- if (!ID.empty()) {
- Out.writeMessage(llvm::Twine(R"({"jsonrpc":"2.0","id":)") + ID +
- R"(,"error":{"code":)" + llvm::Twine(code) +
- R"(,"message":")" + llvm::yaml::escape(Message) +
- R"("}})");
+void RequestContext::replyError(ErrorCode code, const llvm::StringRef &Message) {
+ Out.log("Error " + Twine(static_cast<int>(code)) + ": " + Message + "\n");
+ if (ID) {
+ Out.writeMessage(json::obj{
+ {"jsonrpc", "2.0"},
+ {"id", *ID},
+ {"error", json::obj{{"code", static_cast<int>(code)}, {"message", Message}}},
+ });
}
}
+void RequestContext::call(StringRef Method, json::Expr &&Params) {
+ // FIXME: Generate/Increment IDs for every request so that we can get proper
+ // replies once we need to.
+ Out.writeMessage(json::obj{
+ {"jsonrpc", "2.0"},
+ {"id", 1},
+ {"method", Method},
+ {"params", std::move(Params)},
+ });
+}
+
void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) {
assert(!Handlers.count(Method) && "Handler already registered!");
Handlers[Method] = std::move(H);
@@ -73,7 +95,7 @@ void JSONRPCDispatcher::registerHandler(StringRef Method, Handler H) {
static void
callHandler(const llvm::StringMap<JSONRPCDispatcher::Handler> &Handlers,
- llvm::yaml::ScalarNode *Method, llvm::yaml::ScalarNode *Id,
+ llvm::yaml::ScalarNode *Method, llvm::Optional<json::Expr> ID,
llvm::yaml::MappingNode *Params,
const JSONRPCDispatcher::Handler &UnknownHandler, JSONOutput &Out) {
llvm::SmallString<64> MethodStorage;
@@ -81,7 +103,7 @@ callHandler(const llvm::StringMap<JSONRPCDispatcher::Handler> &Handlers,
auto I = Handlers.find(MethodStr);
auto &Handler = I != Handlers.end() ? I->second : UnknownHandler;
trace::Span Tracer(MethodStr);
- Handler(RequestContext(Out, Id ? Id->getRawValue() : ""), Params);
+ Handler(RequestContext(Out, std::move(ID)), Params);
}
bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
@@ -99,7 +121,7 @@ bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
llvm::yaml::ScalarNode *Version = nullptr;
llvm::yaml::ScalarNode *Method = nullptr;
llvm::yaml::MappingNode *Params = nullptr;
- llvm::yaml::ScalarNode *Id = nullptr;
+ llvm::Optional<json::Expr> ID;
for (auto &NextKeyValue : *Object) {
auto *KeyString =
dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
@@ -120,7 +142,19 @@ bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
} else if (KeyValue == "method") {
Method = dyn_cast<llvm::yaml::ScalarNode>(Value);
} else if (KeyValue == "id") {
- Id = dyn_cast<llvm::yaml::ScalarNode>(Value);
+ // ID may be either a string or a number.
+ if (auto *IdNode = dyn_cast<llvm::yaml::ScalarNode>(Value)) {
+ llvm::SmallString<32> S;
+ llvm::StringRef V = IdNode->getValue(S);
+ if (IdNode->getRawValue().startswith("\"")) {
+ ID.emplace(V.str());
+ } else {
+ double D;
+ // FIXME: this is locale-sensitive.
+ if (llvm::to_float(V, D))
+ ID.emplace(D);
+ }
+ }
} else if (KeyValue == "params") {
if (!Method)
return false;
@@ -129,7 +163,7 @@ bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
// because it will break clients that put the id after params. A possible
// fix would be to split the parsing and execution phases.
Params = dyn_cast<llvm::yaml::MappingNode>(Value);
- callHandler(Handlers, Method, Id, Params, UnknownHandler, Out);
+ callHandler(Handlers, Method, std::move(ID), Params, UnknownHandler, Out);
return true;
} else {
return false;
@@ -140,7 +174,7 @@ bool JSONRPCDispatcher::call(StringRef Content, JSONOutput &Out) const {
// leftovers.
if (!Method)
return false;
- callHandler(Handlers, Method, Id, nullptr, UnknownHandler, Out);
+ callHandler(Handlers, Method, std::move(ID), nullptr, UnknownHandler, Out);
return true;
}
diff --git a/clangd/JSONRPCDispatcher.h b/clangd/JSONRPCDispatcher.h
index 9071e426..afc787a2 100644
--- a/clangd/JSONRPCDispatcher.h
+++ b/clangd/JSONRPCDispatcher.h
@@ -10,7 +10,9 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_JSONRPCDISPATCHER_H
+#include "JSONExpr.h"
#include "Logger.h"
+#include "Protocol.h"
#include "clang/Basic/LLVM.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringMap.h"
@@ -26,11 +28,11 @@ namespace clangd {
class JSONOutput : public Logger {
public:
JSONOutput(llvm::raw_ostream &Outs, llvm::raw_ostream &Logs,
- llvm::raw_ostream *InputMirror = nullptr)
- : Outs(Outs), Logs(Logs), InputMirror(InputMirror) {}
+ llvm::raw_ostream *InputMirror = nullptr, bool Pretty = false)
+ : Outs(Outs), Logs(Logs), InputMirror(InputMirror), Pretty(Pretty) {}
/// Emit a JSONRPC message.
- void writeMessage(const Twine &Message);
+ void writeMessage(const json::Expr &Result);
/// Write to the logging stream.
/// No newline is implicitly added. (TODO: we should fix this!)
@@ -45,6 +47,7 @@ private:
llvm::raw_ostream &Outs;
llvm::raw_ostream &Logs;
llvm::raw_ostream *InputMirror;
+ bool Pretty;
std::mutex StreamMutex;
};
@@ -52,16 +55,19 @@ private:
/// Context object passed to handlers to allow replies.
class RequestContext {
public:
- RequestContext(JSONOutput &Out, StringRef ID) : Out(Out), ID(ID) {}
+ RequestContext(JSONOutput &Out, llvm::Optional<json::Expr> ID)
+ : Out(Out), ID(std::move(ID)) {}
- /// Sends a successful reply. Result should be well-formed JSON.
- void reply(const Twine &Result);
+ /// Sends a successful reply.
+ void reply(json::Expr &&Result);
/// Sends an error response to the client, and logs it.
- void replyError(int code, const llvm::StringRef &Message);
+ void replyError(ErrorCode code, const llvm::StringRef &Message);
+ /// Sends a request to the client.
+ void call(llvm::StringRef Method, json::Expr &&Params);
private:
JSONOutput &Out;
- llvm::SmallString<64> ID; // Valid JSON, or empty for a notification.
+ llvm::Optional<json::Expr> ID;
};
/// Main JSONRPC entry point. This parses the JSONRPC "header" and calls the
diff --git a/clangd/Protocol.cpp b/clangd/Protocol.cpp
index 509e5964..8fa23bfd 100644
--- a/clangd/Protocol.cpp
+++ b/clangd/Protocol.cpp
@@ -63,7 +63,7 @@ URI URI::parse(llvm::yaml::ScalarNode *Param) {
return URI::fromUri(Param->getValue(Storage));
}
-std::string URI::unparse(const URI &U) { return "\"" + U.uri + "\""; }
+json::Expr URI::unparse(const URI &U) { return U.uri; }
llvm::Optional<TextDocumentIdentifier>
TextDocumentIdentifier::parse(llvm::yaml::MappingNode *Params,
@@ -125,11 +125,11 @@ llvm::Optional<Position> Position::parse(llvm::yaml::MappingNode *Params,
return Result;
}
-std::string Position::unparse(const Position &P) {
- std::string Result;
- llvm::raw_string_ostream(Result)
- << llvm::format(R"({"line": %d, "character": %d})", P.line, P.character);
- return Result;
+json::Expr Position::unparse(const Position &P) {
+ return json::obj{
+ {"line", P.line},
+ {"character", P.character},
+ };
}
llvm::Optional<Range> Range::parse(llvm::yaml::MappingNode *Params,
@@ -165,20 +165,18 @@ llvm::Optional<Range> Range::parse(llvm::yaml::MappingNode *Params,
return Result;
}
-std::string Range::unparse(const Range &P) {
- std::string Result;
- llvm::raw_string_ostream(Result) << llvm::format(
- R"({"start": %s, "end": %s})", Position::unparse(P.start).c_str(),
- Position::unparse(P.end).c_str());
- return Result;
+json::Expr Range::unparse(const Range &P) {
+ return json::obj{
+ {"start", P.start},
+ {"end", P.end},
+ };
}
-std::string Location::unparse(const Location &P) {
- std::string Result;
- llvm::raw_string_ostream(Result) << llvm::format(
- R"({"uri": %s, "range": %s})", URI::unparse(P.uri).c_str(),
- Range::unparse(P.range).c_str());
- return Result;
+json::Expr Location::unparse(const Location &P) {
+ return json::obj{
+ {"uri", P.uri},
+ {"range", P.range},
+ };
}
llvm::Optional<TextDocumentItem>
@@ -279,12 +277,11 @@ llvm::Optional<TextEdit> TextEdit::parse(llvm::yaml::MappingNode *Params,
return Result;
}
-std::string TextEdit::unparse(const TextEdit &P) {
- std::string Result;
- llvm::raw_string_ostream(Result) << llvm::format(
- R"({"range": %s, "newText": "%s"})", Range::unparse(P.range).c_str(),
- llvm::yaml::escape(P.newText).c_str());
- return Result;
+json::Expr TextEdit::unparse(const TextEdit &P) {
+ return json::obj{
+ {"range", P.range},
+ {"newText", P.newText},
+ };
}
namespace {
@@ -598,11 +595,11 @@ FormattingOptions::parse(llvm::yaml::MappingNode *Params,
return Result;
}
-std::string FormattingOptions::unparse(const FormattingOptions &P) {
- std::string Result;
- llvm::raw_string_ostream(Result) << llvm::format(
- R"({"tabSize": %d, "insertSpaces": %d})", P.tabSize, P.insertSpaces);
- return Result;
+json::Expr FormattingOptions::unparse(const FormattingOptions &P) {
+ return json::obj{
+ {"tabSize", P.tabSize},
+ {"insertSpaces", P.insertSpaces},
+ };
}
llvm::Optional<DocumentRangeFormattingParams>
@@ -846,6 +843,143 @@ CodeActionParams::parse(llvm::yaml::MappingNode *Params,
return Result;
}
+llvm::Optional<std::map<std::string, std::vector<TextEdit>>>
+parseWorkspaceEditChange(llvm::yaml::MappingNode *Params,
+ clangd::Logger &Logger) {
+ std::map<std::string, std::vector<TextEdit>> Result;
+ for (auto &NextKeyValue : *Params) {
+ auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+ if (!KeyString)
+ return llvm::None;
+
+ llvm::SmallString<10> KeyStorage;
+ StringRef KeyValue = KeyString->getValue(KeyStorage);
+ if (Result.count(KeyValue)) {
+ logIgnoredField(KeyValue, Logger);
+ continue;
+ }
+
+ auto *Value =
+ dyn_cast_or_null<llvm::yaml::SequenceNode>(NextKeyValue.getValue());
+ if (!Value)
+ return llvm::None;
+ for (auto &Item : *Value) {
+ auto *ItemValue = dyn_cast_or_null<llvm::yaml::MappingNode>(&Item);
+ if (!ItemValue)
+ return llvm::None;
+ auto Parsed = TextEdit::parse(ItemValue, Logger);
+ if (!Parsed)
+ return llvm::None;
+
+ Result[KeyValue].push_back(*Parsed);
+ }
+ }
+
+ return Result;
+}
+
+llvm::Optional<WorkspaceEdit>
+WorkspaceEdit::parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger) {
+ WorkspaceEdit Result;
+ for (auto &NextKeyValue : *Params) {
+ auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+ if (!KeyString)
+ return llvm::None;
+
+ llvm::SmallString<10> KeyStorage;
+ StringRef KeyValue = KeyString->getValue(KeyStorage);
+
+ llvm::SmallString<10> Storage;
+ if (KeyValue == "changes") {
+ auto *Value =
+ dyn_cast_or_null<llvm::yaml::MappingNode>(NextKeyValue.getValue());
+ if (!Value)
+ return llvm::None;
+ auto Parsed = parseWorkspaceEditChange(Value, Logger);
+ if (!Parsed)
+ return llvm::None;
+ Result.changes = std::move(*Parsed);
+ } else {
+ logIgnoredField(KeyValue, Logger);
+ }
+ }
+ return Result;
+}
+
+const std::string ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND =
+ "clangd.applyFix";
+
+llvm::Optional<ExecuteCommandParams>
+ExecuteCommandParams::parse(llvm::yaml::MappingNode *Params,
+ clangd::Logger &Logger) {
+ ExecuteCommandParams Result;
+ // Depending on which "command" we parse, we will use this function to parse
+ // the command "arguments".
+ std::function<bool(llvm::yaml::MappingNode * Params)> ArgParser = nullptr;
+
+ for (auto &NextKeyValue : *Params) {
+ auto *KeyString = dyn_cast<llvm::yaml::ScalarNode>(NextKeyValue.getKey());
+ if (!KeyString)
+ return llvm::None;
+
+ llvm::SmallString<10> KeyStorage;
+ StringRef KeyValue = KeyString->getValue(KeyStorage);
+
+ // Note that "commands" has to be parsed before "arguments" for this to
+ // work properly.
+ if (KeyValue == "command") {
+ auto *ScalarValue =
+ dyn_cast_or_null<llvm::yaml::ScalarNode>(NextKeyValue.getValue());
+ if (!ScalarValue)
+ return llvm::None;
+ llvm::SmallString<10> Storage;
+ Result.command = ScalarValue->getValue(Storage);
+ if (Result.command == ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND) {
+ ArgParser = [&Result, &Logger](llvm::yaml::MappingNode *Params) {
+ auto WE = WorkspaceEdit::parse(Params, Logger);
+ if (WE)
+ Result.workspaceEdit = WE;
+ return WE.hasValue();
+ };
+ } else {
+ return llvm::None;
+ }
+ } else if (KeyValue == "arguments") {
+ auto *Value = NextKeyValue.getValue();
+ auto *Seq = dyn_cast<llvm::yaml::SequenceNode>(Value);
+ if (!Seq)
+ return llvm::None;
+ for (auto &Item : *Seq) {
+ auto *ItemValue = dyn_cast_or_null<llvm::yaml::MappingNode>(&Item);
+ if (!ItemValue || !ArgParser)
+ return llvm::None;
+ if (!ArgParser(ItemValue))
+ return llvm::None;
+ }
+ } else {
+ logIgnoredField(KeyValue, Logger);
+ }
+ }
+ if (Result.command.empty())
+ return llvm::None;
+
+ return Result;
+}
+
+json::Expr WorkspaceEdit::unparse(const WorkspaceEdit &WE) {
+ if (!WE.changes)
+ return json::obj{};
+ json::obj FileChanges;
+ for (auto &Change : *WE.changes)
+ FileChanges[Change.first] = json::ary(Change.second);
+ return json::obj{{"changes", std::move(FileChanges)}};
+}
+
+json::Expr
+ApplyWorkspaceEditParams::unparse(const ApplyWorkspaceEditParams &Params) {
+ return json::obj{{"edit", Params.edit}};
+}
+
llvm::Optional<TextDocumentPositionParams>
TextDocumentPositionParams::parse(llvm::yaml::MappingNode *Params,
clangd::Logger &Logger) {
@@ -880,96 +1014,62 @@ TextDocumentPositionParams::parse(llvm::yaml::MappingNode *Params,
return Result;
}
-std::string CompletionItem::unparse(const CompletionItem &CI) {
- std::string Result = "{";
- llvm::raw_string_ostream Os(Result);
+json::Expr CompletionItem::unparse(const CompletionItem &CI) {
assert(!CI.label.empty() && "completion item label is required");
- Os << R"("label":")" << llvm::yaml::escape(CI.label) << R"(",)";
+ json::obj Result{{"label", CI.label}};
if (CI.kind != CompletionItemKind::Missing)
- Os << R"("kind":)" << static_cast<int>(CI.kind) << R"(,)";
+ Result["kind"] = static_cast<int>(CI.kind);
if (!CI.detail.empty())
- Os << R"("detail":")" << llvm::yaml::escape(CI.detail) << R"(",)";
+ Result["detail"] = CI.detail;
if (!CI.documentation.empty())
- Os << R"("documentation":")" << llvm::yaml::escape(CI.documentation)
- << R"(",)";
+ Result["documentation"] = CI.documentation;
if (!CI.sortText.empty())
- Os << R"("sortText":")" << llvm::yaml::escape(CI.sortText) << R"(",)";
+ Result["sortText"] = CI.sortText;
if (!CI.filterText.empty())
- Os << R"("filterText":")" << llvm::yaml::escape(CI.filterText) << R"(",)";
+ Result["filterText"] = CI.filterText;
if (!CI.insertText.empty())
- Os << R"("insertText":")" << llvm::yaml::escape(CI.insertText) << R"(",)";
- if (CI.insertTextFormat != InsertTextFormat::Missing) {
- Os << R"("insertTextFormat":)" << static_cast<int>(CI.insertTextFormat)
- << R"(,)";
- }
+ Result["insertText"] = CI.insertText;
+ if (CI.insertTextFormat != InsertTextFormat::Missing)
+ Result["insertTextFormat"] = static_cast<int>(CI.insertTextFormat);
if (CI.textEdit)
- Os << R"("textEdit":)" << TextEdit::unparse(*CI.textEdit) << ',';
- if (!CI.additionalTextEdits.empty()) {
- Os << R"("additionalTextEdits":[)";
- for (const auto &Edit : CI.additionalTextEdits)
- Os << TextEdit::unparse(Edit) << ",";
- Os.flush();
- // The list additionalTextEdits is guaranteed nonempty at this point.
- // Replace the trailing comma with right brace.
- Result.back() = ']';
- }
- Os.flush();
- // Label is required, so Result is guaranteed to have a trailing comma.
- Result.back() = '}';
- return Result;
+ Result["textEdit"] = *CI.textEdit;
+ if (!CI.additionalTextEdits.empty())
+ Result["additionalTextEdits"] = json::ary(CI.additionalTextEdits);
+ return std::move(Result);
}
-std::string ParameterInformation::unparse(const ParameterInformation &PI) {
- std::string Result = "{";
- llvm::raw_string_ostream Os(Result);
+bool clangd::operator<(const CompletionItem &L, const CompletionItem &R) {
+ return (L.sortText.empty() ? L.label : L.sortText) <
+ (R.sortText.empty() ? R.label : R.sortText);
+}
+
+json::Expr ParameterInformation::unparse(const ParameterInformation &PI) {
assert(!PI.label.empty() && "parameter information label is required");
- Os << R"("label":")" << llvm::yaml::escape(PI.label) << '\"';
+ json::obj Result{{"label", PI.label}};
if (!PI.documentation.empty())
- Os << R"(,"documentation":")" << llvm::yaml::escape(PI.documentation)
- << '\"';
- Os << '}';
- Os.flush();
- return Result;
+ Result["documentation"] = PI.documentation;
+ return std::move(Result);
}
-std::string SignatureInformation::unparse(const SignatureInformation &SI) {
- std::string Result = "{";
- llvm::raw_string_ostream Os(Result);
+json::Expr SignatureInformation::unparse(const SignatureInformation &SI) {
assert(!SI.label.empty() && "signature information label is required");
- Os << R"("label":")" << llvm::yaml::escape(SI.label) << '\"';
+ json::obj Result{
+ {"label", SI.label},
+ {"parameters", json::ary(SI.parameters)},
+ };
if (!SI.documentation.empty())
- Os << R"(,"documentation":")" << llvm::yaml::escape(SI.documentation)
- << '\"';
- Os << R"(,"parameters":[)";
- for (const auto &Parameter : SI.parameters) {
- Os << ParameterInformation::unparse(Parameter) << ',';
- }
- Os.flush();
- if (SI.parameters.empty())
- Result.push_back(']');
- else
- Result.back() = ']'; // Replace the last `,` with an `]`.
- Result.push_back('}');
- return Result;
+ Result["documentation"] = SI.documentation;
+ return std::move(Result);
}
-std::string SignatureHelp::unparse(const SignatureHelp &SH) {
- std::string Result = "{";
- llvm::raw_string_ostream Os(Result);
+json::Expr SignatureHelp::unparse(const SignatureHelp &SH) {
assert(SH.activeSignature >= 0 &&
"Unexpected negative value for number of active signatures.");
assert(SH.activeParameter >= 0 &&
"Unexpected negative value for active parameter index");
- Os << R"("activeSignature":)" << SH.activeSignature
- << R"(,"activeParameter":)" << SH.activeParameter << R"(,"signatures":[)";
- for (const auto &Signature : SH.signatures) {
- Os << SignatureInformation::unparse(Signature) << ',';
- }
- Os.flush();
- if (SH.signatures.empty())
- Result.push_back(']');
- else
- Result.back() = ']'; // Replace the last `,` with an `]`.
- Result.push_back('}');
- return Result;
+ return json::obj{
+ {"activeSignature", SH.activeSignature},
+ {"activeParameter", SH.activeParameter},
+ {"signatures", json::ary(SH.signatures)},
+ };
}
diff --git a/clangd/Protocol.h b/clangd/Protocol.h
index 421fa028..a53f72f9 100644
--- a/clangd/Protocol.h
+++ b/clangd/Protocol.h
@@ -21,6 +21,7 @@
#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_PROTOCOL_H
+#include "JSONExpr.h"
#include "llvm/ADT/Optional.h"
#include "llvm/Support/YAMLParser.h"
#include <string>
@@ -31,6 +32,21 @@ namespace clangd {
class Logger;
+enum class ErrorCode {
+ // Defined by JSON RPC.
+ ParseError = -32700,
+ InvalidRequest = -32600,
+ MethodNotFound = -32601,
+ InvalidParams = -32602,
+ InternalError = -32603,
+
+ ServerNotInitialized = -32002,
+ UnknownErrorCode = -32001,
+
+ // Defined by the protocol.
+ RequestCancelled = -32800,
+};
+
struct URI {
std::string uri;
std::string file;
@@ -39,7 +55,7 @@ struct URI {
static URI fromFile(llvm::StringRef file);
static URI parse(llvm::yaml::ScalarNode *Param);
- static std::string unparse(const URI &U);
+ static json::Expr unparse(const URI &U);
friend bool operator==(const URI &LHS, const URI &RHS) {
return LHS.uri == RHS.uri;
@@ -80,7 +96,7 @@ struct Position {
static llvm::Optional<Position> parse(llvm::yaml::MappingNode *Params,
clangd::Logger &Logger);
- static std::string unparse(const Position &P);
+ static json::Expr unparse(const Position &P);
};
struct Range {
@@ -99,7 +115,7 @@ struct Range {
static llvm::Optional<Range> parse(llvm::yaml::MappingNode *Params,
clangd::Logger &Logger);
- static std::string unparse(const Range &P);
+ static json::Expr unparse(const Range &P);
};
struct Location {
@@ -119,7 +135,7 @@ struct Location {
return std::tie(LHS.uri, LHS.range) < std::tie(RHS.uri, RHS.range);
}
- static std::string unparse(const Location &P);
+ static json::Expr unparse(const Location &P);
};
struct Metadata {
@@ -140,7 +156,7 @@ struct TextEdit {
static llvm::Optional<TextEdit> parse(llvm::yaml::MappingNode *Params,
clangd::Logger &Logger);
- static std::string unparse(const TextEdit &P);
+ static json::Expr unparse(const TextEdit &P);
};
struct TextDocumentItem {
@@ -282,7 +298,7 @@ struct FormattingOptions {
static llvm::Optional<FormattingOptions>
parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger);
- static std::string unparse(const FormattingOptions &P);
+ static json::Expr unparse(const FormattingOptions &P);
};
struct DocumentRangeFormattingParams {
@@ -382,6 +398,46 @@ struct CodeActionParams {
clangd::Logger &Logger);
};
+struct WorkspaceEdit {
+ /// Holds changes to existing resources.
+ llvm::Optional<std::map<std::string, std::vector<TextEdit>>> changes;
+
+ /// Note: "documentChanges" is not currently used because currently there is
+ /// no support for versioned edits.
+
+ static llvm::Optional<WorkspaceEdit> parse(llvm::yaml::MappingNode *Params,
+ clangd::Logger &Logger);
+ static json::Expr unparse(const WorkspaceEdit &WE);
+};
+
+/// Exact commands are not specified in the protocol so we define the
+/// ones supported by Clangd here. The protocol specifies the command arguments
+/// to be "any[]" but to make this safer and more manageable, each command we
+/// handle maps to a certain llvm::Optional of some struct to contain its
+/// arguments. Different commands could reuse the same llvm::Optional as
+/// arguments but a command that needs different arguments would simply add a
+/// new llvm::Optional and not use any other ones. In practice this means only
+/// one argument type will be parsed and set.
+struct ExecuteCommandParams {
+ // Command to apply fix-its. Uses WorkspaceEdit as argument.
+ const static std::string CLANGD_APPLY_FIX_COMMAND;
+
+ /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND
+ std::string command;
+
+ // Arguments
+
+ llvm::Optional<WorkspaceEdit> workspaceEdit;
+
+ static llvm::Optional<ExecuteCommandParams>
+ parse(llvm::yaml::MappingNode *Params, clangd::Logger &Logger);
+};
+
+struct ApplyWorkspaceEditParams {
+ WorkspaceEdit edit;
+ static json::Expr unparse(const ApplyWorkspaceEditParams &Params);
+};
+
struct TextDocumentPositionParams {
/// The text document.
TextDocumentIdentifier textDocument;
@@ -486,9 +542,11 @@ struct CompletionItem {
//
// data?: any - A data entry field that is preserved on a completion item
// between a completion and a completion resolve request.
- static std::string unparse(const CompletionItem &P);
+ static json::Expr unparse(const CompletionItem &P);
};
+bool operator<(const CompletionItem &, const CompletionItem &);
+
/// A single parameter of a particular signature.
struct ParameterInformation {
@@ -498,7 +556,7 @@ struct ParameterInformation {
/// The documentation of this parameter. Optional.
std::string documentation;
- static std::string unparse(const ParameterInformation &);
+ static json::Expr unparse(const ParameterInformation &);
};
/// Represents the signature of something callable.
@@ -513,7 +571,7 @@ struct SignatureInformation {
/// The parameters of this signature.
std::vector<ParameterInformation> parameters;
- static std::string unparse(const SignatureInformation &);
+ static json::Expr unparse(const SignatureInformation &);
};
/// Represents the signature of a callable.
@@ -528,7 +586,7 @@ struct SignatureHelp {
/// The active parameter of the active signature.
int activeParameter = 0;
- static std::string unparse(const SignatureHelp &);
+ static json::Expr unparse(const SignatureHelp &);
};
} // namespace clangd
diff --git a/clangd/ProtocolHandlers.cpp b/clangd/ProtocolHandlers.cpp
index 507fc421..4ca6ee0b 100644
--- a/clangd/ProtocolHandlers.cpp
+++ b/clangd/ProtocolHandlers.cpp
@@ -72,4 +72,5 @@ void clangd::registerCallbackHandlers(JSONRPCDispatcher &Dispatcher,
Register("textDocument/switchSourceHeader",
&ProtocolCallbacks::onSwitchSourceHeader);
Register("workspace/didChangeWatchedFiles", &ProtocolCallbacks::onFileEvent);
+ Register("workspace/executeCommand", &ProtocolCallbacks::onCommand);
}
diff --git a/clangd/ProtocolHandlers.h b/clangd/ProtocolHandlers.h
index bf307c84..f82bd291 100644
--- a/clangd/ProtocolHandlers.h
+++ b/clangd/ProtocolHandlers.h
@@ -52,6 +52,7 @@ public:
virtual void onGoToDefinition(Ctx C, TextDocumentPositionParams &Params) = 0;
virtual void onSwitchSourceHeader(Ctx C, TextDocumentIdentifier &Params) = 0;
virtual void onFileEvent(Ctx C, DidChangeWatchedFilesParams &Params) = 0;
+ virtual void onCommand(Ctx C, ExecuteCommandParams &Params) = 0;
};
void registerCallbackHandlers(JSONRPCDispatcher &Dispatcher, JSONOutput &Out,
diff --git a/clangd/clients/clangd-vscode/src/extension.ts b/clangd/clients/clangd-vscode/src/extension.ts
index f89ddc97..47c13a18 100644
--- a/clangd/clients/clangd-vscode/src/extension.ts
+++ b/clangd/clients/clangd-vscode/src/extension.ts
@@ -40,27 +40,7 @@ export function activate(context: vscode.ExtensionContext) {
};
const clangdClient = new vscodelc.LanguageClient('Clang Language Server', serverOptions, clientOptions);
-
- function applyTextEdits(uri: string, edits: vscodelc.TextEdit[]) {
- let textEditor = vscode.window.activeTextEditor;
-
- // FIXME: vscode expects that uri will be percent encoded
- if (textEditor && textEditor.document.uri.toString(true) === uri) {
- textEditor.edit(mutator => {
- for (const edit of edits) {
- mutator.replace(clangdClient.protocol2CodeConverter.asRange(edit.range), edit.newText);
- }
- }).then((success) => {
- if (!success) {
- vscode.window.showErrorMessage('Failed to apply fixes to the document.');
- }
- });
- }
- }
-
console.log('Clang Language Server is now active!');
const disposable = clangdClient.start();
-
- context.subscriptions.push(disposable, vscode.commands.registerCommand('clangd.applyFix', applyTextEdits));
}
diff --git a/clangd/tool/ClangdMain.cpp b/clangd/tool/ClangdMain.cpp
index 2808d055..dc8969aa 100644
--- a/clangd/tool/ClangdMain.cpp
+++ b/clangd/tool/ClangdMain.cpp
@@ -41,6 +41,10 @@ static llvm::cl::opt<bool> EnableSnippets(
"Present snippet completions instead of plaintext completions"),
llvm::cl::init(false));
+static llvm::cl::opt<bool>
+ PrettyPrint("pretty", llvm::cl::desc("Pretty-print JSON output"),
+ llvm::cl::init(false));
+
static llvm::cl::opt<bool> RunSynchronously(
"run-synchronously",
llvm::cl::desc("Parse on main thread. If set, -j is ignored"),
@@ -104,7 +108,8 @@ int main(int argc, char *argv[]) {
llvm::raw_ostream &Outs = llvm::outs();
llvm::raw_ostream &Logs = llvm::errs();
JSONOutput Out(Outs, Logs,
- InputMirrorStream ? InputMirrorStream.getPointer() : nullptr);
+ InputMirrorStream ? InputMirrorStream.getPointer() : nullptr,
+ PrettyPrint);
// If --compile-commands-dir arg was invoked, check value and override default
// path.
diff --git a/docs/ReleaseNotes.rst b/docs/ReleaseNotes.rst
index b36217ad..eed18010 100644
--- a/docs/ReleaseNotes.rst
+++ b/docs/ReleaseNotes.rst
@@ -57,13 +57,21 @@ The improvements are...
Improvements to clang-tidy
--------------------------
+- New `google-objc-global-variable-declaration
+ <http://clang.llvm.org/extra/clang-tidy/checks/google-global-variable-declaration.html>`_ check
+
+ Add new check for Objective-C code to ensure global
+ variables follow the naming convention of 'k[A-Z].*' (for constants)
+ or 'g[A-Z].*' (for variables).
+
- New module `objc` for Objective-C style checks.
- New `objc-forbidden-subclassing
<http://clang.llvm.org/extra/clang-tidy/checks/objc-forbidden-subclassing.html>`_ check
Ensures Objective-C classes do not subclass any classes which are
- not intended to be subclassed.
+ not intended to be subclassed. Includes a list of classes from Foundation
+ and UIKit which are documented as not supporting subclassing.
- Renamed checks to use correct term "implicit conversion" instead of "implicit
cast" and modified messages and option names accordingly:
diff --git a/docs/clang-tidy/checks/google-objc-global-variable-declaration.rst b/docs/clang-tidy/checks/google-objc-global-variable-declaration.rst
new file mode 100644
index 00000000..ae2b1ee3
--- /dev/null
+++ b/docs/clang-tidy/checks/google-objc-global-variable-declaration.rst
@@ -0,0 +1,42 @@
+.. title:: clang-tidy - google-objc-global-variable-declaration
+
+google-objc-global-variable-declaration
+=======================================
+
+Finds global variable declarations in Objective-C files that are not follow the pattern
+of variable names in Google's Objective-C Style Guide.
+
+The corresponding style guide rule:
+http://google.github.io/styleguide/objcguide.html#variable-names
+
+All the global variables should follow the pattern of `g[A-Z].*` (variables) or
+`k[A-Z].*` (constants). The check will suggest a variable name that follows the pattern
+if it can be inferred from the original name.
+
+For code:
+
+.. code-block:: objc
+ static NSString* myString = @"hello";
+
+The fix will be:
+
+.. code-block:: objc
+ static NSString* gMyString = @"hello";
+
+Another example of constant:
+
+.. code-block:: objc
+ static NSString* const myConstString = @"hello";
+
+The fix will be:
+
+.. code-block:: objc
+ static NSString* const kMyConstString = @"hello";
+
+However for code that prefixed with non-alphabetical characters like:
+
+.. code-block:: objc
+ static NSString* __anotherString = @"world";
+
+The check will give a warning message but will not be able to suggest a fix. The user
+need to fix it on his own.
diff --git a/docs/clang-tidy/checks/list.rst b/docs/clang-tidy/checks/list.rst
index 38ff9cb7..1b74c007 100644
--- a/docs/clang-tidy/checks/list.rst
+++ b/docs/clang-tidy/checks/list.rst
@@ -60,6 +60,7 @@ Clang-Tidy Checks
google-default-arguments
google-explicit-constructor
google-global-names-in-headers
+ google-objc-global-variable-declaration
google-readability-braces-around-statements (redirects to readability-braces-around-statements) <google-readability-braces-around-statements>
google-readability-casting
google-readability-function-size (redirects to readability-function-size) <google-readability-function-size>
diff --git a/docs/clang-tidy/checks/misc-redundant-expression.rst b/docs/clang-tidy/checks/misc-redundant-expression.rst
index ddef9af9..83c29bd7 100644
--- a/docs/clang-tidy/checks/misc-redundant-expression.rst
+++ b/docs/clang-tidy/checks/misc-redundant-expression.rst
@@ -9,13 +9,13 @@ Depending on the operator expressions may be
- redundant,
-- always be ``true``,
+- always ``true``,
-- always be ``false``,
+- always ``false``,
-- always be a constant (zero or one).
+- always a constant (zero or one).
-Example:
+Examples:
.. code-block:: c++
diff --git a/docs/clang-tidy/checks/modernize-replace-random-shuffle.rst b/docs/clang-tidy/checks/modernize-replace-random-shuffle.rst
index 353f35a8..50674d42 100644
--- a/docs/clang-tidy/checks/modernize-replace-random-shuffle.rst
+++ b/docs/clang-tidy/checks/modernize-replace-random-shuffle.rst
@@ -26,3 +26,16 @@ Both of these examples will be replaced with:
The second example will also receive a warning that ``randomFunc`` is no longer supported in the same way as before so if the user wants the same functionality, the user will need to change the implementation of the ``randomFunc``.
One thing to be aware of here is that ``std::random_device`` is quite expensive to initialize. So if you are using the code in a performance critical place, you probably want to initialize it elsewhere.
+Another thing is that the seeding quality of the suggested fix is quite poor: ``std::mt19937`` has an internal state of 624 32-bit integers, but is only seeded with a single integer. So if you require
+higher quality randomness, you should consider seeding better, for example:
+
+.. code-block:: c++
+
+ std::shuffle(v.begin(), v.end(), []() {
+ std::mt19937::result_type seeds[std::mt19937::state_size];
+ std::random_device device;
+ std::uniform_int_distribution<typename std::mt19937::result_type> dist;
+ std::generate(std::begin(seeds), std::end(seeds), [&] { return dist(device); });
+ std::seed_seq seq(std::begin(seeds), std::end(seeds));
+ return std::mt19937(seq);
+ }());
diff --git a/test/clang-tidy/google-objc-global-variable-declaration.m b/test/clang-tidy/google-objc-global-variable-declaration.m
new file mode 100644
index 00000000..cbe5017b
--- /dev/null
+++ b/test/clang-tidy/google-objc-global-variable-declaration.m
@@ -0,0 +1,41 @@
+// RUN: %check_clang_tidy %s google-objc-global-variable-declaration %t
+
+@class NSString;
+static NSString* const myConstString = @"hello";
+// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: const global variable 'myConstString' must have a name which starts with 'k[A-Z]' [google-objc-global-variable-declaration]
+// CHECK-FIXES: static NSString* const kMyConstString = @"hello";
+
+static NSString* MyString = @"hi";
+// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: non-const global variable 'MyString' must have a name which starts with 'g[A-Z]' [google-objc-global-variable-declaration]
+// CHECK-FIXES: static NSString* gMyString = @"hi";
+
+NSString* globalString = @"test";
+// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: non-const global variable 'globalString' must have a name which starts with 'g[A-Z]' [google-objc-global-variable-declaration]
+// CHECK-FIXES: NSString* gGlobalString = @"test";
+
+static NSString* a = @"too simple";
+// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: non-const global variable 'a' must have a name which starts with 'g[A-Z]' [google-objc-global-variable-declaration]
+// CHECK-FIXES: static NSString* a = @"too simple";
+
+static NSString* noDef;
+// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: non-const global variable 'noDef' must have a name which starts with 'g[A-Z]' [google-objc-global-variable-declaration]
+// CHECK-FIXES: static NSString* gNoDef;
+
+static NSString* const _notAlpha = @"NotBeginWithAlpha";
+// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: const global variable '_notAlpha' must have a name which starts with 'k[A-Z]' [google-objc-global-variable-declaration]
+// CHECK-FIXES: static NSString* const _notAlpha = @"NotBeginWithAlpha";
+
+static NSString* const k_Alpha = @"SecondNotAlpha";
+// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: const global variable 'k_Alpha' must have a name which starts with 'k[A-Z]' [google-objc-global-variable-declaration]
+// CHECK-FIXES: static NSString* const k_Alpha = @"SecondNotAlpha";
+
+static NSString* const kGood = @"hello";
+// CHECK-MESSAGES-NOT: :[[@LINE-1]]:24: warning: const global variable 'kGood' must have a name which starts with 'k[A-Z]' [google-objc-global-variable-declaration]
+static NSString* gMyIntGood = 0;
+// CHECK-MESSAGES-NOT: :[[@LINE-1]]:18: warning: non-const global variable 'gMyIntGood' must have a name which starts with 'g[A-Z]' [google-objc-global-variable-declaration]
+@implementation Foo
+- (void)f {
+ int x = 0;
+// CHECK-MESSAGES-NOT: :[[@LINE-1]]:9: warning: non-const global variable 'gMyIntGood' must have a name which starts with 'g[A-Z]' [google-objc-global-variable-declaration]
+}
+@end
diff --git a/test/clang-tidy/misc-redundant-expression.cpp b/test/clang-tidy/misc-redundant-expression.cpp
index e865ba0e..5192f1fb 100644
--- a/test/clang-tidy/misc-redundant-expression.cpp
+++ b/test/clang-tidy/misc-redundant-expression.cpp
@@ -15,69 +15,71 @@ extern int foo(int x);
extern int bar(int x);
extern int bat(int x, int y);
-int Test(int X, int Y) {
+int TestSimpleEquivalent(int X, int Y) {
if (X - X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent [misc-redundant-expression]
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent [misc-redundant-expression]
if (X / X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X % X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X & X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X | X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X ^ X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X < X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X <= X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X > X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X >= X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X && X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X || X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X != (((X)))) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of operator are equivalent
if (X + 1 == X + 1) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both sides of operator are equivalent
if (X + 1 != X + 1) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both sides of operator are equivalent
if (X + 1 <= X + 1) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both sides of operator are equivalent
if (X + 1 >= X + 1) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both sides of operator are equivalent
if ((X != 1 || Y != 1) && (X != 1 || Y != 1)) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: both sides of operator are equivalent
if (P.a[X - P.x] != P.a[X - P.x]) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: both sides of operator are equivalent
if ((int)X < (int)X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: both sides of operator are equivalent
+ if (int(X) < int(X)) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: both sides of operator are equivalent
if ( + "dummy" == + "dummy") return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: both sides of operator are equivalent
if (L"abc" == L"abc") return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: both sides of operator are equivalent
if (foo(0) - 2 < foo(0) - 2) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: both sides of operator are equivalent
if (foo(bar(0)) < (foo(bar((0))))) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: both sides of operator are equivalent
if (P1.x < P2.x && P1.x < P2.x) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:19: warning: both sides of operator are equivalent
if (P2.a[P1.x + 2] < P2.x && P2.a[(P1.x) + (2)] < (P2.x)) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:29: warning: both sides of operator are equivalent
return 0;
}
@@ -102,23 +104,36 @@ int Valid(int X, int Y) {
return 0;
}
+#define COND_OP_MACRO 9
+#define COND_OP_OTHER_MACRO 9
int TestConditional(int x, int y) {
int k = 0;
k += (y < 0) ? x : x;
- // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 'true' and 'false' expression are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 'true' and 'false' expressions are equivalent
k += (y < 0) ? x + 1 : x + 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: 'true' and 'false' expression are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:24: warning: 'true' and 'false' expressions are equivalent
+ k += (y < 0) ? COND_OP_MACRO : COND_OP_MACRO;
+ // CHECK-MESSAGES: :[[@LINE-1]]:32: warning: 'true' and 'false' expressions are equivalent
+
+ // Do not match for conditional operators with a macro and a const.
+ k += (y < 0) ? COND_OP_MACRO : 9;
+ // Do not match for conditional operators with expressions from different macros.
+ k += (y < 0) ? COND_OP_MACRO : COND_OP_OTHER_MACRO;
return k;
}
+#undef COND_OP_MACRO
+#undef COND_OP_OTHER_MACRO
struct MyStruct {
int x;
} Q;
+
bool operator==(const MyStruct& lhs, const MyStruct& rhs) { return lhs.x == rhs.x; }
-bool TestOperator(const MyStruct& S) {
+
+bool TestOperator(MyStruct& S) {
if (S == Q) return false;
- return S == S;
- // CHECK-MESSAGES: :[[@LINE-1]]:12: warning: both side of overloaded operator are equivalent
+ if (S == S) return true;
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: both sides of overloaded operator are equivalent
}
#define LT(x, y) (void)((x) < (y))
@@ -132,6 +147,7 @@ int TestMacro(int X, int Y) {
LT(X+1, X + 1);
COND(X < Y, X, X);
EQUALS(Q, Q);
+ return 0;
}
int TestFalsePositive(int* A, int X, float F) {
@@ -149,7 +165,8 @@ int TestBannedMacros() {
#define NOT_EAGAIN 3
if (EAGAIN == 0 | EAGAIN == 0) return 0;
if (NOT_EAGAIN == 0 | NOT_EAGAIN == 0) return 0;
- // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: both sides of operator are equivalent
+ return 0;
}
struct MyClass {
@@ -159,7 +176,7 @@ template <typename T, typename U>
void TemplateCheck() {
static_assert(T::Value == U::Value, "should be identical");
static_assert(T::Value == T::Value, "should be identical");
- // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: both side of overloaded operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: both sides of overloaded operator are equivalent
}
void TestTemplate() { TemplateCheck<MyClass, MyClass>(); }
@@ -218,6 +235,7 @@ int TestArithmetic(int X, int Y) {
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: logical expression is always true
if (X + 1 == X - (~0U)) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: logical expression is always true
+
if (X + 1 == X - (~0ULL)) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: logical expression is always true
@@ -230,7 +248,8 @@ int TestArithmetic(int X, int Y) {
return 0;
}
-int TestBitwise(int X) {
+int TestBitwise(int X, int Y) {
+
if ((X & 0xFF) == 0xF00) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: logical expression is always false
if ((X & 0xFF) != 0xF00) return 1;
@@ -262,7 +281,14 @@ int TestBitwise(int X) {
return 0;
}
-int TestRelational(int X, int Y) {
+int TestLogical(int X, int Y){
+#define CONFIG 0
+ if (CONFIG && X) return 1; // OK, consts from macros are considered intentional
+#undef CONFIG
+#define CONFIG 1
+ if (CONFIG || X) return 1;
+#undef CONFIG
+
if (X == 10 && X != 10) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: logical expression is always false
if (X == 10 && (X != 10)) return 1;
@@ -289,14 +315,25 @@ int TestRelational(int X, int Y) {
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: logical expression is always false
if (X && !!X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: equivalent expression on both side of logical operator
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: equivalent expression on both sides of logical operator
if (X != 0 && X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both side of logical operator
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both sides of logical operator
if (X != 0 && !!X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both side of logical operator
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both sides of logical operator
if (X == 0 && !X) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both side of logical operator
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both sides of logical operator
+
+ // Should not match.
+ if (X == 10 && Y == 10) return 1;
+ if (X != 10 && X != 12) return 1;
+ if (X == 10 || X == 12) return 1;
+ if (!X && !Y) return 1;
+ if (!X && Y) return 1;
+ if (!X && Y == 0) return 1;
+ if (X == 10 && Y != 10) return 1;
+}
+int TestRelational(int X, int Y) {
if (X == 10 && X > 10) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: logical expression is always false
if (X == 10 && X < 10) return 1;
@@ -323,18 +360,22 @@ int TestRelational(int X, int Y) {
// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: logical expression is always true
if (X != 7 || X != 14) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: logical expression is always true
+ if (X == 7 || X != 5) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
+ if (X != 7 || X == 7) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: logical expression is always true
if (X < 7 && X < 6) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
if (X < 7 && X < 7) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both sides of operator are equivalent
if (X < 7 && X < 8) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: expression is redundant
if (X < 7 && X <= 5) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
if (X < 7 && X <= 6) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: equivalent expression on both side of logical operator
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: equivalent expression on both sides of logical operator
if (X < 7 && X <= 7) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: expression is redundant
if (X < 7 && X <= 8) return 1;
@@ -345,14 +386,21 @@ int TestRelational(int X, int Y) {
if (X <= 7 && X < 7) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
if (X <= 7 && X < 8) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both side of logical operator
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both sides of logical operator
+
+ if (X >= 7 && X > 6) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: equivalent expression on both sides of logical operator
+ if (X >= 7 && X > 7) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
+ if (X >= 7 && X > 8) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
if (X <= 7 && X <= 5) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
if (X <= 7 && X <= 6) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
if (X <= 7 && X <= 7) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: both sides of operator are equivalent
if (X <= 7 && X <= 8) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: expression is redundant
@@ -379,14 +427,18 @@ int TestRelational(int X, int Y) {
if (X < 7 || X < 6) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: expression is redundant
if (X < 7 || X < 7) return 1;
- // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both side of operator are equivalent
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both sides of operator are equivalent
if (X < 7 || X < 8) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
+ if (X > 7 || X > 6) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
+ if (X > 7 || X > 7) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: both sides of operator are equivalent
+ if (X > 7 || X > 8) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: expression is redundant
+
// Should not match.
- if (X == 10 && Y == 10) return 1;
- if (X != 10 && X != 12) return 1;
- if (X == 10 || X == 12) return 1;
if (X < 10 || X > 12) return 1;
if (X > 10 && X < 12) return 1;
if (X < 10 || X >= 12) return 1;
@@ -401,12 +453,56 @@ int TestRelational(int X, int Y) {
if (X > 10 && X != 11) return 1;
if (X >= 10 && X <= 10) return 1;
if (X <= 10 && X >= 10) return 1;
- if (!X && !Y) return 1;
- if (!X && Y) return 1;
- if (!X && Y == 0) return 1;
- if (X == 10 && Y != 10) return 1;
if (X < 0 || X > 0) return 1;
+}
+
+int TestRelationalMacros(int X){
+#define SOME_MACRO 3
+#define SOME_MACRO_SAME_VALUE 3
+#define SOME_OTHER_MACRO 9
+ // Do not match for redundant relational macro expressions that can be
+ // considered intentional, and for some particular values, non redundant.
+
+ // Test cases for expressions with the same macro on both sides.
+ if (X < SOME_MACRO && X > SOME_MACRO) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: logical expression is always false
+ if (X < SOME_MACRO && X == SOME_MACRO) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: logical expression is always false
+ if (X < SOME_MACRO || X >= SOME_MACRO) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: logical expression is always true
+ if (X <= SOME_MACRO || X > SOME_MACRO) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:23: warning: logical expression is always true
+ if (X != SOME_MACRO && X > SOME_MACRO) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
+ if (X != SOME_MACRO && X < SOME_MACRO) return 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: expression is redundant
+ // Test cases for two different macros.
+ if (X < SOME_MACRO && X > SOME_OTHER_MACRO) return 1;
+ if (X != SOME_MACRO && X >= SOME_OTHER_MACRO) return 1;
+ if (X != SOME_MACRO && X != SOME_OTHER_MACRO) return 1;
+ if (X == SOME_MACRO || X == SOME_MACRO_SAME_VALUE) return 1;
+ if (X == SOME_MACRO || X <= SOME_MACRO_SAME_VALUE) return 1;
+ if (X == SOME_MACRO || X > SOME_MACRO_SAME_VALUE) return 1;
+ if (X < SOME_MACRO && X <= SOME_OTHER_MACRO) return 1;
+ if (X == SOME_MACRO && X > SOME_OTHER_MACRO) return 1;
+ if (X == SOME_MACRO && X != SOME_OTHER_MACRO) return 1;
+ if (X == SOME_MACRO && X != SOME_MACRO_SAME_VALUE) return 1;
+ if (X == SOME_MACRO_SAME_VALUE && X == SOME_MACRO ) return 1;
+
+ // Test cases for a macro and a const.
+ if (X < SOME_MACRO && X > 9) return 1;
+ if (X != SOME_MACRO && X >= 9) return 1;
+ if (X != SOME_MACRO && X != 9) return 1;
+ if (X == SOME_MACRO || X == 3) return 1;
+ if (X == SOME_MACRO || X <= 3) return 1;
+ if (X < SOME_MACRO && X <= 9) return 1;
+ if (X == SOME_MACRO && X != 9) return 1;
+ if (X == SOME_MACRO && X == 9) return 1;
+
+#undef SOME_OTHER_MACRO
+#undef SOME_MACRO_SAME_VALUE
+#undef SOME_MACRO
return 0;
}
@@ -419,7 +515,7 @@ int TestValidExpression(int X) {
}
enum Color { Red, Yellow, Green };
-int TestRelatiopnalWithEnum(enum Color C) {
+int TestRelationalWithEnum(enum Color C) {
if (C == Red && C == Yellow) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: logical expression is always false
if (C == Red && C != Red) return 1;
@@ -437,7 +533,7 @@ int TestRelatiopnalWithEnum(enum Color C) {
template<class T>
int TestRelationalTemplated(int X) {
// This test causes a corner case with |isIntegerConstantExpr| where the type
- // is dependant. There is an assert failing when evaluating
+ // is dependent. There is an assert failing when evaluating
// sizeof(<incomplet-type>).
if (sizeof(T) == 4 || sizeof(T) == 8) return 1;
@@ -450,10 +546,13 @@ int TestRelationalTemplated(int X) {
int TestWithSignedUnsigned(int X) {
if (X + 1 == X + 1ULL) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: logical expression is always true
+
if ((X & 0xFFU) == 0xF00) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: logical expression is always false
+
if ((X & 0xFF) == 0xF00U) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: logical expression is always false
+
if ((X & 0xFFU) == 0xF00U) return 1;
// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: logical expression is always false
@@ -476,6 +575,7 @@ int TestWithMinMaxInt(int X) {
if (X <= X + 0xFFFFFFFFU) return 1;
if (X <= X + 0x7FFFFFFF) return 1;
if (X <= X + 0x80000000) return 1;
+
if (X <= 0xFFFFFFFFU && X > 0) return 1;
if (X <= 0xFFFFFFFFU && X > 0U) return 1;
diff --git a/test/clangd/authority-less-uri.test b/test/clangd/authority-less-uri.test
index 494f6914..d33a1c27 100644
--- a/test/clangd/authority-less-uri.test
+++ b/test/clangd/authority-less-uri.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
# Test authority-less URI
@@ -15,22 +15,34 @@ Content-Length: 146
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}}
# Test authority-less URI
#
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK: ]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK: "filterText": "fake",
+# CHECK-NEXT: "insertText": "fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fake::",
+# CHECK-NEXT: "sortText": "000075fake"
+# CHECK: ]
Content-Length: 172
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"uri":"file:///main.cpp","position":{"line":3,"character":5}}}
# Test params parsing in the presence of a 1.x-compatible client (inlined "uri")
#
-# CHECK: {"jsonrpc":"2.0","id":2,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK: ]}
+# CHECK: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK: "filterText": "fake",
+# CHECK-NEXT: "insertText": "fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fake::",
+# CHECK-NEXT: "sortText": "000075fake"
+# CHECK: ]
Content-Length: 44
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":3,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/completion-items-kinds.test b/test/clangd/completion-items-kinds.test
index 8ad7cfa7..83d2c7b9 100644
--- a/test/clangd/completion-items-kinds.test
+++ b/test/clangd/completion-items-kinds.test
@@ -11,27 +11,26 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}}
Content-Length: 58
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[
+# CHECK: {"id":1,"jsonrpc":"2.0","result":[
#
# Keyword
-# CHECK-DAG: {"label":"int","kind":14,"sortText":"000050int","filterText":"int","insertText":"int","insertTextFormat":1}
+# CHECK-DAG: {"filterText":"int","insertText":"int","insertTextFormat":1,"kind":14,"label":"int","sortText":"000050int"}
#
# Code pattern
-# CHECK-DAG: {"label":"static_cast<type>(expression)","kind":15,"sortText":"000040static_cast","filterText":"static_cast","insertText":"static_cast<${1:type}>(${2:expression})","insertTextFormat":2}
+# CHECK-DAG: {"filterText":"static_cast","insertText":"static_cast<${1:type}>(${2:expression})","insertTextFormat":2,"kind":15,"label":"static_cast<type>(expression)","sortText":"000040static_cast"}
#
# Struct
-# CHECK-DAG: {"label":"Struct","kind":7,"sortText":"000050Struct","filterText":"Struct","insertText":"Struct","insertTextFormat":1}
+# CHECK-DAG: {"filterText":"Struct","insertText":"Struct","insertTextFormat":1,"kind":7,"label":"Struct","sortText":"000050Struct"}
#
# Macro
-# CHECK-DAG: {"label":"MACRO","kind":1,"sortText":"000070MACRO","filterText":"MACRO","insertText":"MACRO","insertTextFormat":1}
+# CHECK-DAG: {"filterText":"MACRO","insertText":"MACRO","insertTextFormat":1,"kind":1,"label":"MACRO","sortText":"000070MACRO"}
#
# Variable
-# CHECK-DAG: {"label":"variable","kind":6,"detail":"int","sortText":"000012variable","filterText":"variable","insertText":"variable","insertTextFormat":1}
+# CHECK-DAG: {"detail":"int","filterText":"variable","insertText":"variable","insertTextFormat":1,"kind":6,"label":"variable","sortText":"000012variable"}
#
# Function
-# CHECK-DAG: {"label":"function()","kind":3,"detail":"int","sortText":"000012function","filterText":"function","insertText":"function()","insertTextFormat":1}
+# CHECK-DAG: {"detail":"int","filterText":"function","insertText":"function()","insertTextFormat":1,"kind":3,"label":"function()","sortText":"000012function"}
#
-#
-# CHECK: ]}
+# CHECK-SAME: ]}
{"jsonrpc":"2.0","id":3,"method":"shutdown","params":null}
diff --git a/test/clangd/completion-priorities.test b/test/clangd/completion-priorities.test
index 35ecd61b..45501dc4 100644
--- a/test/clangd/completion-priorities.test
+++ b/test/clangd/completion-priorities.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
@@ -13,28 +13,74 @@ Content-Length: 312
Content-Length: 151
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":12,"character":8}}}
-# The order of results returned by codeComplete seems to be
-# nondeterministic, so we check regardless of order.
-#
-# CHECK: {"jsonrpc":"2.0","id":2,"result":[
-# CHECK-DAG: {"label":"pub()","kind":2,"detail":"void","sortText":"000034pub","filterText":"pub","insertText":"pub","insertTextFormat":1}
-# CHECK-DAG: {"label":"prot()","kind":2,"detail":"void","sortText":"000034prot","filterText":"prot","insertText":"prot","insertTextFormat":1}
-# CHECK-DAG: {"label":"priv()","kind":2,"detail":"void","sortText":"000034priv","filterText":"priv","insertText":"priv","insertTextFormat":1}
-# CHECK: ]}
-
+# CHECK: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "void",
+# CHECK-NEXT: "filterText": "priv",
+# CHECK-NEXT: "insertText": "priv",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "priv()",
+# CHECK-NEXT: "sortText": "000034priv"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "void",
+# CHECK-NEXT: "filterText": "prot",
+# CHECK-NEXT: "insertText": "prot",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "prot()",
+# CHECK-NEXT: "sortText": "000034prot"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "void",
+# CHECK-NEXT: "filterText": "pub",
+# CHECK-NEXT: "insertText": "pub",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "pub()",
+# CHECK-NEXT: "sortText": "000034pub"
+# CHECK-NEXT: },
Content-Length: 151
{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":17,"character":4}}}
-# CHECK: {"jsonrpc":"2.0","id":3,"result":[
-# CHECK-DAG: {"label":"pub()","kind":2,"detail":"void","sortText":"000034pub","filterText":"pub","insertText":"pub","insertTextFormat":1}
-# CHECK-DAG: {"label":"prot()","kind":2,"detail":"void","sortText":"200034prot","filterText":"prot","insertText":"prot","insertTextFormat":1}
-# CHECK-DAG: {"label":"priv()","kind":2,"detail":"void","sortText":"200034priv","filterText":"priv","insertText":"priv","insertTextFormat":1}
-# CHECK: ]}
-
+# CHECK: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "void",
+# CHECK-NEXT: "filterText": "pub",
+# CHECK-NEXT: "insertText": "pub",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "pub()",
+# CHECK-NEXT: "sortText": "000034pub"
+# CHECK-NEXT: },
+# priv() and prot() are at the end of the list
+# CHECK-NEXT: {
+# CHECK: "detail": "void",
+# CHECK: "filterText": "priv",
+# CHECK-NEXT: "insertText": "priv",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "priv()",
+# CHECK-NEXT: "sortText": "200034priv"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "void",
+# CHECK-NEXT: "filterText": "prot",
+# CHECK-NEXT: "insertText": "prot",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "prot()",
+# CHECK-NEXT: "sortText": "200034prot"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 58
{"jsonrpc":"2.0","id":4,"method":"shutdown","params":null}
-# CHECK: {"jsonrpc":"2.0","id":4,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/completion-qualifiers.test b/test/clangd/completion-qualifiers.test
index 97cc002d..982d7d93 100644
--- a/test/clangd/completion-qualifiers.test
+++ b/test/clangd/completion-qualifiers.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
Content-Length: 125
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
@@ -8,15 +8,42 @@ Content-Length: 297
Content-Length: 151
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":11,"character":8}}}
-# CHECK: {"jsonrpc":"2.0","id":2,"result":[
-# CHECK-DAG: {"label":"foo() const","kind":2,"detail":"int","sortText":"200035foo","filterText":"foo","insertText":"foo","insertTextFormat":1}
-# CHECK-DAG: {"label":"bar() const","kind":2,"detail":"int","sortText":"000037bar","filterText":"bar","insertText":"bar","insertTextFormat":1}
-# CHECK-DAG: {"label":"Foo::foo() const","kind":2,"detail":"int","sortText":"000037foo","filterText":"foo","insertText":"foo","insertTextFormat":1}
-# CHECK: ]}
+# CHECK: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# Eligible const functions are at the top of the list.
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "bar",
+# CHECK-NEXT: "insertText": "bar",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "bar() const",
+# CHECK-NEXT: "sortText": "000037bar"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "foo",
+# CHECK-NEXT: "insertText": "foo",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "Foo::foo() const",
+# CHECK-NEXT: "sortText": "000037foo"
+# CHECK-NEXT: },
+# Ineligible non-const function is at the bottom of the list.
+# CHECK-NEXT: {
+# CHECK: "detail": "int",
+# CHECK: "filterText": "foo",
+# CHECK-NEXT: "insertText": "foo",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "foo() const",
+# CHECK-NEXT: "sortText": "200035foo"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 44
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":4,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/completion-snippet.test b/test/clangd/completion-snippet.test
index fdf797d6..ec1ac355 100644
--- a/test/clangd/completion-snippet.test
+++ b/test/clangd/completion-snippet.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously -enable-snippets < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously -enable-snippets < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
@@ -12,30 +12,73 @@ Content-Length: 246
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
-# The order of results returned by codeComplete seems to be
-# nondeterministic, so we check regardless of order.
-#
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"000035bb","filterText":"bb","insertText":"bb","insertTextFormat":1}
-# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"000035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1}
-# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"000079operator=","filterText":"operator=","insertText":"operator=(${1:const fake &})","insertTextFormat":2}
-# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"000079~fake","filterText":"~fake","insertText":"~fake()","insertTextFormat":1}
-# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"000035f","filterText":"f","insertText":"f(${1:int i}, ${2:const float f})","insertTextFormat":2}
-# CHECK: ]}
-Content-Length: 148
-
-{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
-# Repeat the completion request, expect the same results.
-#
-# CHECK: {"jsonrpc":"2.0","id":2,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"000035bb","filterText":"bb","insertText":"bb","insertTextFormat":1}
-# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"000035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1}
-# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"000079operator=","filterText":"operator=","insertText":"operator=(${1:const fake &})","insertTextFormat":2}
-# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"000079~fake","filterText":"~fake","insertText":"~fake()","insertTextFormat":1}
-# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"000035f","filterText":"f","insertText":"f(${1:int i}, ${2:const float f})","insertTextFormat":2}
-# CHECK: ]}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "a",
+# CHECK-NEXT: "insertText": "a",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "a",
+# CHECK-NEXT: "sortText": "000035a"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "bb",
+# CHECK-NEXT: "insertText": "bb",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "bb",
+# CHECK-NEXT: "sortText": "000035bb"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "ccc",
+# CHECK-NEXT: "insertText": "ccc",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "ccc",
+# CHECK-NEXT: "sortText": "000035ccc"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "f",
+# CHECK-NEXT: "insertText": "f(${1:int i}, ${2:const float f})",
+# CHECK-NEXT: "insertTextFormat": 2,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "f(int i, const float f) const",
+# CHECK-NEXT: "sortText": "000035f"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "filterText": "fake",
+# CHECK-NEXT: "insertText": "fake::",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fake::",
+# CHECK-NEXT: "sortText": "000075fake"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "fake &",
+# CHECK-NEXT: "filterText": "operator=",
+# CHECK-NEXT: "insertText": "operator=(${1:const fake &})",
+# CHECK-NEXT: "insertTextFormat": 2,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "operator=(const fake &)",
+# CHECK-NEXT: "sortText": "000079operator="
+# CHECK-NEXT: },
+# FIXME: Why do some buildbots show an extra operator==(fake&&) here?
+# CHECK: {
+# CHECK: "detail": "void",
+# CHECK-NEXT: "filterText": "~fake",
+# CHECK-NEXT: "insertText": "~fake()",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 4,
+# CHECK-NEXT: "label": "~fake()",
+# CHECK-NEXT: "sortText": "000079~fake"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
# Update the source file and check for completions again.
Content-Length: 226
@@ -44,15 +87,21 @@ Content-Length: 226
Content-Length: 148
{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
-# Repeat the completion request, expect the same results.
-#
-# CHECK: {"jsonrpc":"2.0","id":3,"result":[
-# CHECK-DAG: {"label":"func()","kind":2,"detail":"int (*)(int, int)","sortText":"000034func","filterText":"func","insertText":"func()","insertTextFormat":1}
-# CHECK: ]}
+# CHECK: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int (*)(int, int)",
+# CHECK-NEXT: "filterText": "func",
+# CHECK-NEXT: "insertText": "func()",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "func()",
+# CHECK-NEXT: "sortText": "000034func"
+# CHECK-NEXT: },
Content-Length: 44
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":4,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/completion.test b/test/clangd/completion.test
index bc2b5302..7ff06523 100644
--- a/test/clangd/completion.test
+++ b/test/clangd/completion.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
@@ -12,30 +12,142 @@ Content-Length: 246
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
-# The order of results returned by codeComplete seems to be
-# nondeterministic, so we check regardless of order.
-#
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"000035bb","filterText":"bb","insertText":"bb","insertTextFormat":1}
-# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"000035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1}
-# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"000079operator=","filterText":"operator=","insertText":"operator=","insertTextFormat":1}
-# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"000079~fake","filterText":"~fake","insertText":"~fake","insertTextFormat":1}
-# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"000035f","filterText":"f","insertText":"f","insertTextFormat":1}
-# CHECK: ]}
+# CHECK: "id": 1
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "a",
+# CHECK-NEXT: "insertText": "a",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "a",
+# CHECK-NEXT: "sortText": "000035a"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "bb",
+# CHECK-NEXT: "insertText": "bb",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "bb",
+# CHECK-NEXT: "sortText": "000035bb"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "ccc",
+# CHECK-NEXT: "insertText": "ccc",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "ccc",
+# CHECK-NEXT: "sortText": "000035ccc"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "f",
+# CHECK-NEXT: "insertText": "f",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "f(int i, const float f) const",
+# CHECK-NEXT: "sortText": "000035f"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "filterText": "fake",
+# CHECK-NEXT: "insertText": "fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fake::",
+# CHECK-NEXT: "sortText": "000075fake"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "fake &",
+# CHECK-NEXT: "filterText": "operator=",
+# CHECK-NEXT: "insertText": "operator=",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "operator=(const fake &)",
+# CHECK-NEXT: "sortText": "000079operator="
+# CHECK-NEXT: },
+# FIXME: Why do some buildbots show an extra operator==(fake&&) here?
+# CHECK: {
+# CHECK: "detail": "void",
+# CHECK-NEXT: "filterText": "~fake",
+# CHECK-NEXT: "insertText": "~fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 4,
+# CHECK-NEXT: "label": "~fake()",
+# CHECK-NEXT: "sortText": "000079~fake"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 148
{"jsonrpc":"2.0","id":2,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
-# Repeat the completion request, expect the same results.
-#
-# CHECK: {"jsonrpc":"2.0","id":2,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK-DAG: {"label":"bb","kind":5,"detail":"int","sortText":"000035bb","filterText":"bb","insertText":"bb","insertTextFormat":1}
-# CHECK-DAG: {"label":"ccc","kind":5,"detail":"int","sortText":"000035ccc","filterText":"ccc","insertText":"ccc","insertTextFormat":1}
-# CHECK-DAG: {"label":"operator=(const fake &)","kind":2,"detail":"fake &","sortText":"000079operator=","filterText":"operator=","insertText":"operator=","insertTextFormat":1}
-# CHECK-DAG: {"label":"~fake()","kind":4,"detail":"void","sortText":"000079~fake","filterText":"~fake","insertText":"~fake","insertTextFormat":1}
-# CHECK-DAG: {"label":"f(int i, const float f) const","kind":2,"detail":"int","sortText":"000035f","filterText":"f","insertText":"f","insertTextFormat":1}
-# CHECK: ]}
+# CHECK: "id": 2
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "a",
+# CHECK-NEXT: "insertText": "a",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "a",
+# CHECK-NEXT: "sortText": "000035a"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "bb",
+# CHECK-NEXT: "insertText": "bb",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "bb",
+# CHECK-NEXT: "sortText": "000035bb"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "ccc",
+# CHECK-NEXT: "insertText": "ccc",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 5,
+# CHECK-NEXT: "label": "ccc",
+# CHECK-NEXT: "sortText": "000035ccc"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int",
+# CHECK-NEXT: "filterText": "f",
+# CHECK-NEXT: "insertText": "f",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "f(int i, const float f) const",
+# CHECK-NEXT: "sortText": "000035f"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "filterText": "fake",
+# CHECK-NEXT: "insertText": "fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fake::",
+# CHECK-NEXT: "sortText": "000075fake"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "fake &",
+# CHECK-NEXT: "filterText": "operator=",
+# CHECK-NEXT: "insertText": "operator=",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "operator=(const fake &)",
+# CHECK-NEXT: "sortText": "000079operator="
+# CHECK-NEXT: },
+# CHECK: {
+# CHECK: "detail": "void",
+# CHECK-NEXT: "filterText": "~fake",
+# CHECK-NEXT: "insertText": "~fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 4,
+# CHECK-NEXT: "label": "~fake()",
+# CHECK-NEXT: "sortText": "000079~fake"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
# Update the source file and check for completions again.
Content-Length: 226
@@ -44,15 +156,48 @@ Content-Length: 226
Content-Length: 148
{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":5}}}
-# Repeat the completion request, expect the same results.
-#
-# CHECK: {"jsonrpc":"2.0","id":3,"result":[
-# CHECK-DAG: {"label":"func()","kind":2,"detail":"int (*)(int, int)","sortText":"000034func","filterText":"func","insertText":"func","insertTextFormat":1}
-# CHECK: ]}
+# CHECK: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "int (*)(int, int)",
+# CHECK-NEXT: "filterText": "func",
+# CHECK-NEXT: "insertText": "func",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "func()",
+# CHECK-NEXT: "sortText": "000034func"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "filterText": "fancy",
+# CHECK-NEXT: "insertText": "fancy",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fancy::",
+# CHECK-NEXT: "sortText": "000075fancy"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "detail": "fancy &",
+# CHECK-NEXT: "filterText": "operator=",
+# CHECK-NEXT: "insertText": "operator=",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 2,
+# CHECK-NEXT: "label": "operator=(const fancy &)",
+# CHECK-NEXT: "sortText": "000079operator="
+# CHECK-NEXT: },
+# CHECK: {
+# CHECK: "detail": "void",
+# CHECK-NEXT: "filterText": "~fancy",
+# CHECK-NEXT: "insertText": "~fancy",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 4,
+# CHECK-NEXT: "label": "~fancy()",
+# CHECK-NEXT: "sortText": "000079~fancy"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 44
{"jsonrpc":"2.0","id":4,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":4,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/definitions.test b/test/clangd/definitions.test
index c52e5dec..a50f058f 100644
--- a/test/clangd/definitions.test
+++ b/test/clangd/definitions.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
@@ -13,14 +13,44 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":0}}}
# Go to local variable
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 5,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":1}}}
# Go to local variable, end of token
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 5,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 214
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":2},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo bar = { x : 1 };\n}\n"}]}}
@@ -29,8 +59,23 @@ Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":14}}}
# Go to field, GNU old-style field designator
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 5,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 215
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":3},"contentChanges":[{"text":"struct Foo {\nint x;\n};\nint main() {\n Foo baz = { .x = 2 };\n}\n"}]}}
@@ -39,8 +84,23 @@ Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":15}}}
# Go to field, field designator
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 0}, "end": {"line": 1, "character": 5}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 5,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 187
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":4},"contentChanges":[{"text":"int main() {\n main();\n return 0;\n}"}]}}
@@ -49,8 +109,23 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":3}}}
# Go to function declaration, function call
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 3, "character": 1}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 3
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 208
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"struct Foo {\n};\nint main() {\n Foo bar;\n return 0;\n}\n"}]}}
@@ -59,8 +134,23 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":3}}}
# Go to struct declaration, new struct instance
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 1, "character": 1}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 231
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":5},"contentChanges":[{"text":"namespace n1 {\nstruct Foo {\n};\n}\nint main() {\n n1::Foo bar;\n return 0;\n}\n"}]}}
@@ -69,8 +159,23 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":4}}}
# Go to struct declaration, new struct instance, qualified name
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 3, "character": 1}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 3
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 215
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":6},"contentChanges":[{"text":"struct Foo {\n int x;\n};\nint main() {\n Foo bar;\n bar.x;\n}\n"}]}}
@@ -79,8 +184,23 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}}
# Go to field declaration, field reference
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 2}, "end": {"line": 1, "character": 7}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 7,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 2,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 220
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n void x();\n};\nint main() {\n Foo bar;\n bar.x();\n}\n"}]}}
@@ -89,8 +209,23 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":5,"character":7}}}
# Go to method declaration, method call
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 1, "character": 2}, "end": {"line": 1, "character": 10}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 10,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 2,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 240
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"struct Foo {\n};\ntypedef Foo TypedefFoo;\nint main() {\n TypedefFoo bar;\n return 0;\n}\n"}]}}
@@ -99,8 +234,23 @@ Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":10}}}
# Go to typedef
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 0}, "end": {"line": 2, "character": 22}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 22,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 254
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"template <typename MyTemplateParam>\nvoid foo() {\n MyTemplateParam a;\n}\nint main() {\n return 0;\n}\n"}]}}
@@ -109,8 +259,9 @@ Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":13}}}
# Go to template type parameter. Fails until clangIndex is modified to handle those.
-# no-CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 10}, "end": {"line": 0, "character": 34}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": []
Content-Length: 256
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\nstatic void bar() {}\n};\n}\nint main() {\n ns::Foo::bar();\n return 0;\n}\n"}]}}
@@ -119,8 +270,23 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":6,"character":4}}}
# Go to namespace, static method call
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 0, "character": 0}, "end": {"line": 4, "character": 1}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 4
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 0,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 265
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"namespace ns {\nstruct Foo {\n int field;\n Foo(int param) : field(param) {}\n};\n}\nint main() {\n return 0;\n}\n"}]}}
@@ -128,9 +294,24 @@ Content-Length: 265
Content-Length: 149
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":21}}}
-# Go to field, member initializer
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 2}, "end": {"line": 2, "character": 11}}}]}
-
+# Go to field, member initializer
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 11,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 2,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 204
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define MY_MACRO 0\nint main() {\n return MY_MACRO;\n}\n"}]}}
@@ -139,8 +320,23 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":2,"character":9}}}
# Go to macro.
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///{{([A-Za-z]:/)?}}main.cpp", "range": {"start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 18}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 18,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 8,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 217
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///main.cpp","version":7},"contentChanges":[{"text":"#define FOO 1\nint a = FOO;\n#define FOO 2\nint b = FOO;\n#undef FOO\n"}]}}
@@ -149,29 +345,77 @@ Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":1,"character":8}}}
# Go to macro, re-defined later
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///{{([A-Za-z]:/)?}}main.cpp", "range": {"start": {"line": 0, "character": 8}, "end": {"line": 0, "character": 13}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 13,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 8,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":3,"character":8}}}
# Go to macro, undefined later
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 8}, "end": {"line": 2, "character": 13}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 13,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 8,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 148
{"jsonrpc":"2.0","id":1,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":4,"character":7}}}
# Go to macro, being undefined
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"uri": "file:///main.cpp", "range": {"start": {"line": 2, "character": 8}, "end": {"line": 2, "character": 13}}}]}
-
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 13,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 8,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "uri": "file:///{{([A-Z]:/)?}}main.cpp"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 156
{"jsonrpc":"2.0","id":2,"method":"textDocument/definition","params":{"textDocument":{"uri":"file:///doesnotexist.cpp"},"position":{"line":4,"character":7}}}
-# CHECK: {"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"findDefinitions called on non-added file"}}
-
+# CHECK: "error": {
+# CHECK-NEXT: "code": -32602,
+# CHECK-NEXT: "message": "findDefinitions called on non-added file"
+# CHECK-NEXT: },
+# CHECK-NEXT: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0"
Content-Length: 48
{"jsonrpc":"2.0","id":10000,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":10000,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/diagnostics-preamble.test b/test/clangd/diagnostics-preamble.test
index c4adbe3c..befb809e 100644
--- a/test/clangd/diagnostics-preamble.test
+++ b/test/clangd/diagnostics-preamble.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
@@ -8,12 +8,14 @@ Content-Length: 125
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: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///main.cpp","diagnostics":[]}}
-
+# 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}
-# CHECK: {"jsonrpc":"2.0","id":2,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/diagnostics.test b/test/clangd/diagnostics.test
index 41838c4c..99c222d8 100644
--- a/test/clangd/diagnostics.test
+++ b/test/clangd/diagnostics.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
@@ -8,14 +8,43 @@ Content-Length: 125
Content-Length: 152
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"void main() {}"}}}
-#
-# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 1}, "end": {"line": 0, "character": 1}},"severity":2,"message":"return type of 'main' is not 'int'"},{"range":{"start": {"line": 0, "character": 1}, "end": {"line": 0, "character": 1}},"severity":3,"message":"change return type to 'int'"}]}}
-#
-#
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "return type of 'main' is not 'int'",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "change return type to 'int'",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 1,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 3
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "uri": "file:///foo.c"
+# CHECK-NEXT: }
Content-Length: 44
{"jsonrpc":"2.0","id":5,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":5,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/did-change-watch-files.test b/test/clangd/did-change-watch-files.test
index d29184d7..717a6dd5 100644
--- a/test/clangd/did-change-watch-files.test
+++ b/test/clangd/did-change-watch-files.test
@@ -5,18 +5,7 @@
Content-Length: 143
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}
-# CHECK: Content-Length: 466
-# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
-# CHECK: "textDocumentSync": 1,
-# CHECK: "documentFormattingProvider": true,
-# CHECK: "documentRangeFormattingProvider": true,
-# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
-# CHECK: "codeActionProvider": true,
-# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
-# CHECK: "definitionProvider": true
-# CHECK: }}}
-#
-#Normal case
+# Normal case.
Content-Length: 217
{"jsonrpc":"2.0","method":"workspace/didChangeWatchedFiles","params":{"changes":[{"uri":"file:///path/to/file.cpp","type":1},{"uri":"file:///path/to/file2.cpp","type":2},{"uri":"file:///path/to/file3.cpp","type":3}]}}
diff --git a/test/clangd/execute-command.test b/test/clangd/execute-command.test
new file mode 100644
index 00000000..833690d6
--- /dev/null
+++ b/test/clangd/execute-command.test
@@ -0,0 +1,112 @@
+# RUN: clangd -pretty -run-synchronously < %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: 180
+
+{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}}
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "using the result of an assignment as a condition without parentheses",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "place parentheses around the assignment to silence this warning",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 3
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "use '==' to turn this assignment into an equality comparison",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 3
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "uri": "file:///foo.c"
+# CHECK-NEXT: }
+Content-Length: 72
+
+{"jsonrpc":"2.0","id":3,"method":"workspace/executeCommand","params":{}}
+# No command name
+Content-Length: 85
+
+{"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command": {}}}
+# Invalid, non-scalar command name
+Content-Length: 345
+
+{"jsonrpc":"2.0","id":5,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","custom":"foo", "arguments":[{"changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}]}}
+Content-Length: 117
+
+{"jsonrpc":"2.0","id":6,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":"foo"}}
+# Arguments not a sequence.
+Content-Length: 93
+
+{"jsonrpc":"2.0","id":7,"method":"workspace/executeCommand","params":{"command":"mycommand"}}
+# Unknown command.
+Content-Length: 132
+
+{"jsonrpc":"2.0","id":8,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","custom":"foo", "arguments":[""]}}
+# ApplyFix argument not a mapping node.
+Content-Length: 345
+
+{"jsonrpc":"2.0","id":9,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"custom":"foo", "changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}]}}
+# Custom field in WorkspaceEdit
+Content-Length: 132
+
+{"jsonrpc":"2.0","id":10,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":"foo"}]}}
+# changes in WorkspaceEdit with no mapping node
+Content-Length: 346
+
+{"jsonrpc":"2.0","id":11,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}], "custom":"foo"}}]}}
+# Custom field in WorkspaceEditChange
+Content-Length: 150
+
+{"jsonrpc":"2.0","id":12,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":"bar"}}]}}
+# No sequence node for TextEdits
+Content-Length: 149
+
+{"jsonrpc":"2.0","id":13,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":[""]}}]}}
+# No mapping node for TextEdit
+Content-Length: 265
+
+{"jsonrpc":"2.0","id":14,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":"","newText":")"}]}}]}}
+# TextEdit not decoded
+Content-Length: 345
+
+{"jsonrpc":"2.0","id":9,"method":"workspace/executeCommand","params":{"arguments":[{"custom":"foo", "changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}],"command":"clangd.applyFix"}}
+# Command name after arguments
+Content-Length: 44
+
+{"jsonrpc":"2.0","id":3,"method":"shutdown"}
diff --git a/test/clangd/extra-flags.test b/test/clangd/extra-flags.test
index fe703796..8defbefa 100644
--- a/test/clangd/extra-flags.test
+++ b/test/clangd/extra-flags.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
@@ -8,17 +8,80 @@ Content-Length: 125
Content-Length: 205
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int main() { int i; return i; }"},"metadata":{"extraFlags":["-Wall"]}}}
-# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 28}, "end": {"line": 0, "character": 28}},"severity":2,"message":"variable 'i' is uninitialized when used here"},{"range":{"start": {"line": 0, "character": 19}, "end": {"line": 0, "character": 19}},"severity":3,"message":"initialize the variable 'i' to silence this warning"}]}}
-#
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "variable 'i' is uninitialized when used here",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 28,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 28,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "initialize the variable 'i' to silence this warning",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 19,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 19,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 3
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "uri": "file:///foo.c"
+# CHECK-NEXT: }
Content-Length: 175
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":2},"contentChanges":[{"text":"int main() { int i; return i; }"}]}}
-# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 28}, "end": {"line": 0, "character": 28}},"severity":2,"message":"variable 'i' is uninitialized when used here"},{"range":{"start": {"line": 0, "character": 19}, "end": {"line": 0, "character": 19}},"severity":3,"message":"initialize the variable 'i' to silence this warning"}]}}
-#
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "variable 'i' is uninitialized when used here",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 28,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 28,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "initialize the variable 'i' to silence this warning",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 19,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 19,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 3
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "uri": "file:///foo.c"
+# CHECK-NEXT: }
Content-Length: 44
{"jsonrpc":"2.0","id":5,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":5,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/fixits.test b/test/clangd/fixits.test
index ab5b71e9..2a16c8c7 100644
--- a/test/clangd/fixits.test
+++ b/test/clangd/fixits.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
@@ -8,25 +8,242 @@ Content-Length: 125
Content-Length: 180
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int main(int i, char **a) { if (i = 2) {}}"}}}
-#
-# CHECK: {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"uri":"file:///foo.c","diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}
-#
+# CHECK: "method": "textDocument/publishDiagnostics",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "diagnostics": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "using the result of an assignment as a condition without parentheses",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "place parentheses around the assignment to silence this warning",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 3
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "message": "use '==' to turn this assignment into an equality comparison",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: "severity": 3
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "uri": "file:///foo.c"
+# CHECK-NEXT: }
Content-Length: 746
- {"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}}
-#
-# CHECK: {"jsonrpc":"2.0","id":2,"result":[{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]]}]
-#
+{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}}
+# CHECK: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "changes": {
+# CHECK-NEXT: "file:///foo.c": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "(",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 32,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 32,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": ")",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 37,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 37,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply FixIt place parentheses around the assignment to silence this warning"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "changes": {
+# CHECK-NEXT: "file:///foo.c": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "==",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 34,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply FixIt use '==' to turn this assignment into an equality comparison"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 771
-{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"code":"1","source":"foo","message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}}
+{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":2,"code":"1","source":"foo","message":"using the result of an assignment as a condition without parentheses"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"place parentheses around the assignment to silence this warning"},{"range":{"start": {"line": 0, "character": 35}, "end": {"line": 0, "character": 35}},"severity":3,"message":"use '==' to turn this assignment into an equality comparison"}]}}}
# Make sure unused "code" and "source" fields ignored gracefully
-# CHECK: {"jsonrpc":"2.0","id":2,"result":[{"title":"Apply FixIt 'place parentheses around the assignment to silence this warning'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 32}}, "newText": "("},{"range": {"start": {"line": 0, "character": 37}, "end": {"line": 0, "character": 37}}, "newText": ")"}]]},{"title":"Apply FixIt 'use '==' to turn this assignment into an equality comparison'", "command": "clangd.applyFix", "arguments": ["file:///foo.c", [{"range": {"start": {"line": 0, "character": 34}, "end": {"line": 0, "character": 35}}, "newText": "=="}]]}]
+# CHECK: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "changes": {
+# CHECK-NEXT: "file:///foo.c": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "(",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 32,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 32,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": ")",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 37,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 37,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply FixIt place parentheses around the assignment to silence this warning"
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "arguments": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "changes": {
+# CHECK-NEXT: "file:///foo.c": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "==",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 35,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 34,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ],
+# CHECK-NEXT: "command": "clangd.applyFix",
+# CHECK-NEXT: "title": "Apply FixIt use '==' to turn this assignment into an equality comparison"
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+Content-Length: 329
+
+{"jsonrpc":"2.0","id":4,"method":"workspace/executeCommand","params":{"command":"clangd.applyFix","arguments":[{"changes":{"file:///foo.c":[{"range":{"start":{"line":0,"character":32},"end":{"line":0,"character":32}},"newText":"("},{"range":{"start":{"line":0,"character":37},"end":{"line":0,"character":37}},"newText":")"}]}}]}}
+# CHECK: "id": 4,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": "Fix applied."
#
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "method": "workspace/applyEdit",
+# CHECK-NEXT: "params": {
+# CHECK-NEXT: "edit": {
+# CHECK-NEXT: "changes": {
+# CHECK-NEXT: "file:///foo.c": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "(",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 32,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 32,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": ")",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 37,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 37,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
Content-Length: 44
-{"jsonrpc":"2.0","id":3,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":3,"result":null}
+{"jsonrpc":"2.0","id":4,"method":"shutdown"}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/formatting.test b/test/clangd/formatting.test
index 09068fc0..87181e21 100644
--- a/test/clangd/formatting.test
+++ b/test/clangd/formatting.test
@@ -1,30 +1,71 @@
-# RUN: clangd < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %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"}}
-# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
-# CHECK: "textDocumentSync": 1,
-# CHECK: "documentFormattingProvider": true,
-# CHECK: "documentRangeFormattingProvider": true,
-# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
-# CHECK: "codeActionProvider": true,
-# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
-# CHECK: "definitionProvider": true
-# CHECK: }}}
-#
Content-Length: 193
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///foo.c","languageId":"c","version":1,"text":"int foo ( int x ) {\n x = x+1;\n return x;\n }"}}}
-#
-#
Content-Length: 233
{"jsonrpc":"2.0","id":1,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":4},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}}
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[{"range": {"start": {"line": 0, "character": 19}, "end": {"line": 1, "character": 4}}, "newText": "\n "},{"range": {"start": {"line": 1, "character": 9}, "end": {"line": 1, "character": 9}}, "newText": " "},{"range": {"start": {"line": 1, "character": 10}, "end": {"line": 1, "character": 10}}, "newText": " "},{"range": {"start": {"line": 1, "character": 12}, "end": {"line": 2, "character": 4}}, "newText": "\n "}]}
-#
-#
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "\n ",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 4,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 19,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": " ",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 9,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 9,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": " ",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 10,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 10,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "\n ",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 4,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 12,
+# CHECK-NEXT: "line": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 197
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n x = x + 1;\n return x;\n }"}]}}
@@ -33,14 +74,68 @@ Content-Length: 197
Content-Length: 233
{"jsonrpc":"2.0","id":2,"method":"textDocument/rangeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"range":{"start":{"line":1,"character":2},"end":{"line":1,"character":12}},"options":{"tabSize":4,"insertSpaces":true}}}
-# CHECK: {"jsonrpc":"2.0","id":2,"result":[]}
-#
+# CHECK: "id": 2,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": []
Content-Length: 153
{"jsonrpc":"2.0","id":3,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}}
-# CHECK: {"jsonrpc":"2.0","id":3,"result":[{"range": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 8}}, "newText": ""},{"range": {"start": {"line": 0, "character": 9}, "end": {"line": 0, "character": 10}}, "newText": ""},{"range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 16}}, "newText": ""},{"range": {"start": {"line": 2, "character": 11}, "end": {"line": 3, "character": 4}}, "newText": "\n"}]}
-#
-#
+# CHECK: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 8,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 7,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 10,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 9,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 16,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 15,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "\n",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 4,
+# CHECK-NEXT: "line": 3
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 11,
+# CHECK-NEXT: "line": 2
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 190
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":9},"contentChanges":[{"text":"int foo(int x) {\n x = x + 1;\n return x;\n}"}]}}
@@ -49,8 +144,9 @@ Content-Length: 190
Content-Length: 153
{"jsonrpc":"2.0","id":4,"method":"textDocument/formatting","params":{"textDocument":{"uri":"file:///foo.c"},"options":{"tabSize":4,"insertSpaces":true}}}
-# CHECK: {"jsonrpc":"2.0","id":4,"result":[]}
-#
+# CHECK: "id": 4,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": []
Content-Length: 193
{"jsonrpc":"2.0","method":"textDocument/didChange","params":{"textDocument":{"uri":"file:///foo.c","version":5},"contentChanges":[{"text":"int foo ( int x ) {\n x = x + 1;\n return x;\n}"}]}}
@@ -59,13 +155,53 @@ Content-Length: 193
Content-Length: 204
{"jsonrpc":"2.0","id":5,"method":"textDocument/onTypeFormatting","params":{"textDocument":{"uri":"file:///foo.c"},"position":{"line":3,"character":1},"ch":"}","options":{"tabSize":4,"insertSpaces":true}}}
-# CHECK: {"jsonrpc":"2.0","id":5,"result":[{"range": {"start": {"line": 0, "character": 7}, "end": {"line": 0, "character": 8}}, "newText": ""},{"range": {"start": {"line": 0, "character": 9}, "end": {"line": 0, "character": 10}}, "newText": ""},{"range": {"start": {"line": 0, "character": 15}, "end": {"line": 0, "character": 16}}, "newText": ""}]}
-#
+# CHECK: "id": 5,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 8,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 7,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 10,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 9,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: },
+# CHECK-NEXT: {
+# CHECK-NEXT: "newText": "",
+# CHECK-NEXT: "range": {
+# CHECK-NEXT: "end": {
+# CHECK-NEXT: "character": 16,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: },
+# CHECK-NEXT: "start": {
+# CHECK-NEXT: "character": 15,
+# CHECK-NEXT: "line": 0
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: }
+# CHECK-NEXT: ]
Content-Length: 44
{"jsonrpc":"2.0","id":6,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":6,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/initialize-params-invalid.test b/test/clangd/initialize-params-invalid.test
index 77520d65..9f9c69d1 100644
--- a/test/clangd/initialize-params-invalid.test
+++ b/test/clangd/initialize-params-invalid.test
@@ -1,26 +1,47 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
# Test with invalid initialize request parameters
Content-Length: 142
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":"","rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}
-# CHECK: Content-Length: 535
-# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
-# CHECK: "textDocumentSync": 1,
-# CHECK: "documentFormattingProvider": true,
-# CHECK: "documentRangeFormattingProvider": true,
-# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
-# CHECK: "codeActionProvider": true,
-# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
-# CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]},
-# CHECK: "definitionProvider": true
-# CHECK: }}}
-#
+# CHECK: "id": 0,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": {
+# CHECK-NEXT: "capabilities": {
+# CHECK-NEXT: "codeActionProvider": true,
+# CHECK-NEXT: "completionProvider": {
+# CHECK-NEXT: "resolveProvider": false,
+# CHECK-NEXT: "triggerCharacters": [
+# CHECK-NEXT: ".",
+# CHECK-NEXT: ">",
+# CHECK-NEXT: ":"
+# CHECK-NEXT: ]
+# CHECK-NEXT: },
+# CHECK-NEXT: "definitionProvider": true,
+# CHECK-NEXT: "documentFormattingProvider": true,
+# CHECK-NEXT: "documentOnTypeFormattingProvider": {
+# CHECK-NEXT: "firstTriggerCharacter": "}",
+# CHECK-NEXT: "moreTriggerCharacter": []
+# CHECK-NEXT: },
+# CHECK-NEXT: "documentRangeFormattingProvider": true,
+# CHECK-NEXT: "executeCommandProvider": {
+# CHECK-NEXT: "commands": [
+# CHECK-NEXT: "clangd.applyFix"
+# CHECK-NEXT: ]
+# CHECK-NEXT: },
+# CHECK-NEXT: "signatureHelpProvider": {
+# CHECK-NEXT: "triggerCharacters": [
+# CHECK-NEXT: "(",
+# CHECK-NEXT: ","
+# CHECK-NEXT: ]
+# CHECK-NEXT: },
+# CHECK-NEXT: "textDocumentSync": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
Content-Length: 44
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":3,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/initialize-params.test b/test/clangd/initialize-params.test
index 57562323..60fc05ec 100644
--- a/test/clangd/initialize-params.test
+++ b/test/clangd/initialize-params.test
@@ -1,26 +1,50 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
# Test initialize request parameters with rootUri
Content-Length: 143
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootUri":"file:///path/to/workspace","capabilities":{},"trace":"off"}}
-# CHECK: Content-Length: 535
-# CHECK: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{
-# CHECK: "textDocumentSync": 1,
-# CHECK: "documentFormattingProvider": true,
-# CHECK: "documentRangeFormattingProvider": true,
-# CHECK: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
-# CHECK: "codeActionProvider": true,
-# CHECK: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
-# CHECK: "signatureHelpProvider": {"triggerCharacters": ["(",","]},
-# CHECK: "definitionProvider": true
-# CHECK: }}}
-#
+# CHECK: "id": 0,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": {
+# CHECK-NEXT: "capabilities": {
+# CHECK-NEXT: "codeActionProvider": true,
+# CHECK-NEXT: "completionProvider": {
+# CHECK-NEXT: "resolveProvider": false,
+# CHECK-NEXT: "triggerCharacters": [
+# CHECK-NEXT: ".",
+# CHECK-NEXT: ">",
+# CHECK-NEXT: ":"
+# CHECK-NEXT: ]
+# CHECK-NEXT: },
+# CHECK-NEXT: "definitionProvider": true,
+# CHECK-NEXT: "documentFormattingProvider": true,
+# CHECK-NEXT: "documentOnTypeFormattingProvider": {
+# CHECK-NEXT: "firstTriggerCharacter": "}",
+# CHECK-NEXT: "moreTriggerCharacter": []
+# CHECK-NEXT: },
+# CHECK-NEXT: "documentRangeFormattingProvider": true,
+# CHECK-NEXT: "executeCommandProvider": {
+# CHECK-NEXT: "commands": [
+# CHECK-NEXT: "clangd.applyFix"
+# CHECK-NEXT: ]
+# CHECK-NEXT: },
+# CHECK-NEXT: "signatureHelpProvider": {
+# CHECK-NEXT: "triggerCharacters": [
+# CHECK-NEXT: "(",
+# CHECK-NEXT: ","
+# CHECK-NEXT: ]
+# CHECK-NEXT: },
+# CHECK-NEXT: "textDocumentSync": 1
+# CHECK-NEXT: }
+# CHECK-NEXT: }
Content-Length: 44
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":3,"result":null}
+# CHECK: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": null
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/input-mirror.test b/test/clangd/input-mirror.test
index db54bcf5..5b07cb53 100644
--- a/test/clangd/input-mirror.test
+++ b/test/clangd/input-mirror.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously -input-mirror-file %t < %s
+# RUN: clangd -pretty -run-synchronously -input-mirror-file %t < %s
# Note that we have to use '-b' as -input-mirror-file does not have a newline at the end of file.
# RUN: diff -b %t %s
# It is absolutely vital that this file has CRLF line endings.
@@ -152,7 +152,6 @@ Content-Length: 148
Content-Length: 44
{"jsonrpc":"2.0","id":3,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":3,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/protocol.test b/test/clangd/protocol.test
index 61fa90c8..7837410d 100644
--- a/test/clangd/protocol.test
+++ b/test/clangd/protocol.test
@@ -1,5 +1,5 @@
-# RUN: not clangd -run-synchronously < %s | FileCheck %s
-# RUN: not clangd -run-synchronously < %s 2>&1 | FileCheck -check-prefix=STDERR %s
+# RUN: not clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
+# RUN: not clangd -pretty -run-synchronously < %s 2>&1 | FileCheck -check-prefix=STDERR %s
# vim: fileformat=dos
# It is absolutely vital that this file has CRLF line endings.
#
@@ -12,16 +12,9 @@ Content-Type: application/vscode-jsonrpc; charset-utf-8
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":123,"rootPath":"clangd","capabilities":{},"trace":"off"}}
# Test message with Content-Type after Content-Length
#
-# CHECK: "jsonrpc":"2.0","id":0,"result":{"capabilities":{
-# CHECK-DAG: "textDocumentSync": 1,
-# CHECK-DAG: "documentFormattingProvider": true,
-# CHECK-DAG: "documentRangeFormattingProvider": true,
-# CHECK-DAG: "documentOnTypeFormattingProvider": {"firstTriggerCharacter":"}","moreTriggerCharacter":[]},
-# CHECK-DAG: "codeActionProvider": true,
-# CHECK-DAG: "completionProvider": {"resolveProvider": false, "triggerCharacters": [".",">",":"]},
-# CHECK-DAG: "definitionProvider": true
-# CHECK: }}
-
+# CHECK: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": {
+# CHECK: }
Content-Length: 246
{"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file:///main.cpp","languageId":"cpp","version":1,"text":"struct fake { int a, bb, ccc; int f(int i, const float f) const; };\nint main() {\n fake f;\n f.\n}\n"}}}
@@ -36,9 +29,16 @@ Content-Length: 146
{"jsonrpc":"2.0","id":1,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}}
# Test message with Content-Type before Content-Length
#
-# CHECK: {"jsonrpc":"2.0","id":1,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK: ]}
+# CHECK: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK: "filterText": "fake",
+# CHECK-NEXT: "insertText": "fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fake::",
+# CHECK-NEXT: "sortText": "000075fake"
+# CHECK: ]
X-Test: Testing
Content-Type: application/vscode-jsonrpc; charset-utf-8
@@ -55,9 +55,16 @@ Content-Length: 146
{"jsonrpc":"2.0","id":3,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}}
# Test message with duplicate Content-Length headers
#
-# CHECK: {"jsonrpc":"2.0","id":3,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK: ]}
+# CHECK: "id": 3,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK: "filterText": "fake",
+# CHECK-NEXT: "insertText": "fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fake::",
+# CHECK-NEXT: "sortText": "000075fake"
+# CHECK: ]
# STDERR: Warning: Duplicate Content-Length header received. The previous value for this message (10) was ignored.
Content-Type: application/vscode-jsonrpc; charset-utf-8
@@ -74,10 +81,16 @@ Content-Length: 146
{"jsonrpc":"2.0","id":5,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}}
# Test message with Content-Type before Content-Length
#
-# CHECK: {"jsonrpc":"2.0","id":5,"result":[
-# CHECK-DAG: {"label":"a","kind":5,"detail":"int","sortText":"000035a","filterText":"a","insertText":"a","insertTextFormat":1}
-# CHECK: ]}
-
+# CHECK: "id": 5,
+# CHECK-NEXT: "jsonrpc": "2.0",
+# CHECK-NEXT: "result": [
+# CHECK: "filterText": "fake",
+# CHECK-NEXT: "insertText": "fake",
+# CHECK-NEXT: "insertTextFormat": 1,
+# CHECK-NEXT: "kind": 7,
+# CHECK-NEXT: "label": "fake::",
+# CHECK-NEXT: "sortText": "000075fake"
+# CHECK: ]
Content-Length: 1024
{"jsonrpc":"2.0","id":5,"method":"textDocument/completion","params":{"textDocument":{"uri":"file:/main.cpp"},"position":{"line":3,"character":5}}}
diff --git a/test/clangd/signature-help.test b/test/clangd/signature-help.test
index c28a309f..d19422b0 100644
--- a/test/clangd/signature-help.test
+++ b/test/clangd/signature-help.test
@@ -15,12 +15,12 @@ Content-Length: 333
Content-Length: 151
{"jsonrpc":"2.0","id":1,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":8,"character":9}}}
-# CHECK: {"jsonrpc":"2.0","id":1,"result":{"activeSignature":0,"activeParameter":0,"signatures":[
+# CHECK: {"id":1,"jsonrpc":"2.0","result":{"activeParameter":0,"activeSignature":0,"signatures":[
# CHECK-DAG: {"label":"foo(float x, float y) -> void","parameters":[{"label":"float x"},{"label":"float y"}]}
# CHECK-DAG: {"label":"foo(float x, int y) -> void","parameters":[{"label":"float x"},{"label":"int y"}]}
# CHECK-DAG: {"label":"foo(int x, float y) -> void","parameters":[{"label":"int x"},{"label":"float y"}]}
# CHECK-DAG: {"label":"foo(int x, int y) -> void","parameters":[{"label":"int x"},{"label":"int y"}]}
-# CHECK: ]}
+# CHECK-SAME: ]}
# Modify the document
Content-Length: 333
@@ -31,21 +31,20 @@ Content-Length: 333
Content-Length: 151
{"jsonrpc":"2.0","id":2,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///main.cpp"},"position":{"line":8,"character":9}}}
-# CHECK: {"jsonrpc":"2.0","id":2,"result":{"activeSignature":0,"activeParameter":0,"signatures":[
+# CHECK: {"id":2,"jsonrpc":"2.0","result":{"activeParameter":0,"activeSignature":0,"signatures":[
# CHECK-DAG: {"label":"bar(int x, int y = 0) -> void","parameters":[{"label":"int x"},{"label":"int y = 0"}]}
# CHECK-DAG: {"label":"bar(float x = 0, int y = 42) -> void","parameters":[{"label":"float x = 0"},{"label":"int y = 42"}]}
-# CHECK: ]}
+# CHECK-SAME: ]}
Content-Length: 159
{"jsonrpc":"2.0","id":3,"method":"textDocument/signatureHelp","params":{"textDocument":{"uri":"file:///doesnotexist.cpp"},"position":{"line":8,"character":9}}}
-# CHECK: {"jsonrpc":"2.0","id":3,"error":{"code":-32602,"message":"signatureHelp is called for non-added document"}}
+# CHECK: {"error":{"code":-32602,"message":"signatureHelp is called for non-added document"},"id":3,"jsonrpc":"2.0"}
# Shutdown.
Content-Length: 49
{"jsonrpc":"2.0","id":100000,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":100000,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/test/clangd/unsupported-method.test b/test/clangd/unsupported-method.test
index cccbb5cc..0ce22bbc 100644
--- a/test/clangd/unsupported-method.test
+++ b/test/clangd/unsupported-method.test
@@ -1,4 +1,4 @@
-# RUN: clangd -run-synchronously < %s | FileCheck %s
+# RUN: clangd -pretty -run-synchronously < %s | FileCheck -strict-whitespace %s
# It is absolutely vital that this file has CRLF line endings.
#
Content-Length: 125
@@ -12,12 +12,16 @@ Content-Length: 143
Content-Length: 92
{"jsonrpc":"2.0","id":1,"method":"textDocument/jumpInTheAirLikeYouJustDontCare","params":{}}
-# CHECK: {"jsonrpc":"2.0","id":1,"error":{"code":-32601,"message":"method not found"}}
+# CHECK: "error": {
+# CHECK-NEXT: "code": -32601,
+# CHECK-NEXT: "message": "method not found"
+# CHECK-NEXT: },
+# CHECK-NEXT: "id": 1,
+# CHECK-NEXT: "jsonrpc": "2.0"
Content-Length: 44
{"jsonrpc":"2.0","id":2,"method":"shutdown"}
-# CHECK: {"jsonrpc":"2.0","id":2,"result":null}
Content-Length: 33
{"jsonrpc":"2.0":"method":"exit"}
diff --git a/tool-template/CMakeLists.txt b/tool-template/CMakeLists.txt
index 8223e4c8..125523ab 100644
--- a/tool-template/CMakeLists.txt
+++ b/tool-template/CMakeLists.txt
@@ -12,4 +12,5 @@ target_link_libraries(tool-template
clangBasic
clangFrontend
clangTooling
+ clangToolingRefactor
)
diff --git a/tool-template/ToolTemplate.cpp b/tool-template/ToolTemplate.cpp
index f97575e5..5345d77f 100644
--- a/tool-template/ToolTemplate.cpp
+++ b/tool-template/ToolTemplate.cpp
@@ -40,7 +40,9 @@
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/Lexer.h"
#include "clang/Tooling/CommonOptionsParser.h"
+#include "clang/Tooling/Execution.h"
#include "clang/Tooling/Refactoring.h"
+#include "clang/Tooling/Refactoring/AtomicChange.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MemoryBuffer.h"
@@ -54,19 +56,31 @@ using namespace llvm;
namespace {
class ToolTemplateCallback : public MatchFinder::MatchCallback {
public:
- ToolTemplateCallback(std::map<std::string, Replacements> *Replace)
- : Replace(Replace) {}
+ ToolTemplateCallback(ExecutionContext &Context) : Context(Context) {}
void run(const MatchFinder::MatchResult &Result) override {
// TODO: This routine will get called for each thing that the matchers
// find.
// At this point, you can examine the match, and do whatever you want,
// including replacing the matched text with other text
- (void)Replace; // This to prevent an "unused member variable" warning;
+ auto *D = Result.Nodes.getNodeAs<NamedDecl>("decl");
+ assert(D);
+ // Use AtomicChange to get a key.
+ if (D->getLocStart().isValid()) {
+ AtomicChange Change(*Result.SourceManager, D->getLocStart());
+ Context.reportResult(Change.getKey(), D->getQualifiedNameAsString());
+ }
+ }
+
+ void onStartOfTranslationUnit() override {
+ Context.reportResult("START", "Start of TU.");
+ }
+ void onEndOfTranslationUnit() override {
+ Context.reportResult("END", "End of TU.");
}
private:
- std::map<std::string, Replacements> *Replace;
+ ExecutionContext &Context;
};
} // end anonymous namespace
@@ -76,15 +90,33 @@ static cl::OptionCategory ToolTemplateCategory("tool-template options");
int main(int argc, const char **argv) {
llvm::sys::PrintStackTraceOnErrorSignal(argv[0]);
- CommonOptionsParser OptionsParser(argc, argv, ToolTemplateCategory);
- RefactoringTool Tool(OptionsParser.getCompilations(),
- OptionsParser.getSourcePathList());
+
+ auto Executor = clang::tooling::createExecutorFromCommandLineArgs(
+ argc, argv, ToolTemplateCategory);
+
+ if (!Executor) {
+ llvm::errs() << llvm::toString(Executor.takeError()) << "\n";
+ return 1;
+ }
+
ast_matchers::MatchFinder Finder;
- ToolTemplateCallback Callback(&Tool.getReplacements());
+ ToolTemplateCallback Callback(*Executor->get()->getExecutionContext());
// TODO: Put your matchers here.
// Use Finder.addMatcher(...) to define the patterns in the AST that you
// want to match against. You are not limited to just one matcher!
+ //
+ // This is a sample matcher:
+ Finder.addMatcher(
+ namedDecl(cxxRecordDecl(), isExpansionInMainFile()).bind("decl"),
+ &Callback);
- return Tool.run(newFrontendActionFactory(&Finder).get());
+ auto Err = Executor->get()->execute(newFrontendActionFactory(&Finder));
+ if (Err) {
+ llvm::errs() << llvm::toString(std::move(Err)) << "\n";
+ }
+ Executor->get()->getToolResults()->forEachResult(
+ [](llvm::StringRef key, llvm::StringRef value) {
+ llvm::errs() << "----" << key.str() << "\n" << value.str() << "\n";
+ });
}
diff --git a/unittests/clangd/CMakeLists.txt b/unittests/clangd/CMakeLists.txt
index f45bc712..5be935c9 100644
--- a/unittests/clangd/CMakeLists.txt
+++ b/unittests/clangd/CMakeLists.txt
@@ -10,6 +10,7 @@ include_directories(
add_extra_unittest(ClangdTests
ClangdTests.cpp
+ JSONExprTests.cpp
TraceTests.cpp
)
diff --git a/unittests/clangd/JSONExprTests.cpp b/unittests/clangd/JSONExprTests.cpp
new file mode 100644
index 00000000..a95b4b0c
--- /dev/null
+++ b/unittests/clangd/JSONExprTests.cpp
@@ -0,0 +1,116 @@
+//===-- JSONExprTests.cpp - JSON expression unit tests ----------*- C++ -*-===//
+//
+// The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#include "JSONExpr.h"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+namespace clang {
+namespace clangd {
+namespace json {
+namespace {
+
+std::string s(const Expr &E) { return llvm::formatv("{0}", E).str(); }
+std::string sp(const Expr &E) { return llvm::formatv("{0:2}", E).str(); }
+
+TEST(JSONExprTests, Types) {
+ EXPECT_EQ("true", s(true));
+ EXPECT_EQ("null", s(nullptr));
+ EXPECT_EQ("2.5", s(2.5));
+ EXPECT_EQ(R"("foo")", s("foo"));
+ EXPECT_EQ("[1,2,3]", s({1, 2, 3}));
+ EXPECT_EQ(R"({"x":10,"y":20})", s(obj{{"x", 10}, {"y", 20}}));
+}
+
+TEST(JSONExprTests, Constructors) {
+ // Lots of edge cases around empty and singleton init lists.
+ EXPECT_EQ("[[[3]]]", s({{{3}}}));
+ EXPECT_EQ("[[[]]]", s({{{}}}));
+ EXPECT_EQ("[[{}]]", s({{obj{}}}));
+ EXPECT_EQ(R"({"A":{"B":{}}})", s(obj{{"A", obj{{"B", obj{}}}}}));
+ EXPECT_EQ(R"({"A":{"B":{"X":"Y"}}})",
+ s(obj{{"A", obj{{"B", obj{{"X", "Y"}}}}}}));
+}
+
+TEST(JSONExprTests, StringOwnership) {
+ char X[] = "Hello";
+ Expr Alias = static_cast<const char *>(X);
+ X[1] = 'a';
+ EXPECT_EQ(R"("Hallo")", s(Alias));
+
+ std::string Y = "Hello";
+ Expr Copy = Y;
+ Y[1] = 'a';
+ EXPECT_EQ(R"("Hello")", s(Copy));
+}
+
+TEST(JSONExprTests, CanonicalOutput) {
+ // Objects are sorted (but arrays aren't)!
+ EXPECT_EQ(R"({"a":1,"b":2,"c":3})", s(obj{{"a", 1}, {"c", 3}, {"b", 2}}));
+ EXPECT_EQ(R"(["a","c","b"])", s({"a", "c", "b"}));
+ EXPECT_EQ("3", s(3.0));
+}
+
+TEST(JSONExprTests, Escaping) {
+ std::string test = {
+ 0, // Strings may contain nulls.
+ '\b', '\f', // Have mnemonics, but we escape numerically.
+ '\r', '\n', '\t', // Escaped with mnemonics.
+ 'S', '\"', '\\', // Printable ASCII characters.
+ '\x7f', // Delete is not escaped.
+ '\xce', '\x94', // Non-ASCII UTF-8 is not escaped.
+ };
+
+ std::string teststring = R"("\u0000\u0008\u000c\r\n\tS\"\\)"
+ "\x7f\xCE\x94\"";
+
+ EXPECT_EQ(teststring, s(test));
+
+ EXPECT_EQ(R"({"object keys are\nescaped":true})",
+ s(obj{{"object keys are\nescaped", true}}));
+}
+
+TEST(JSONExprTests, PrettyPrinting) {
+ const char str[] = R"({
+ "empty_array": [],
+ "empty_object": {},
+ "full_array": [
+ 1,
+ null
+ ],
+ "full_object": {
+ "nested_array": [
+ {
+ "property": "value"
+ }
+ ]
+ }
+})";
+
+ EXPECT_EQ(
+ str,
+ sp(obj{
+ {"empty_object", obj{}},
+ {"empty_array", {}},
+ {"full_array", {1, nullptr}},
+ {"full_object",
+ obj{
+ {"nested_array",
+ {obj{
+ {"property", "value"},
+ }}},
+ }},
+ }));
+}
+
+} // namespace
+} // namespace json
+} // namespace clangd
+} // namespace clang