diff options
author | David L. Jones <dlj@google.com> | 2017-11-15 01:40:05 +0000 |
---|---|---|
committer | David L. Jones <dlj@google.com> | 2017-11-15 01:40:05 +0000 |
commit | e116b2cbca67fd5860351c74e296cc5ce72c7980 (patch) | |
tree | 080e959bbba8787e252efa998568def298147e2d | |
parent | 81f5ab7c488b645ad1c1587fc4a30451d12aaac1 (diff) | |
parent | d68d476fc2b4e565efc28b4a29f2ab11a2730241 (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
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 |