Index: clangd/CMakeLists.txt =================================================================== --- clangd/CMakeLists.txt +++ clangd/CMakeLists.txt @@ -71,6 +71,8 @@ index/dex/PostingList.cpp index/dex/Trigram.cpp + refactor/Tweak.cpp + LINK_LIBS clangAST clangASTMatchers @@ -108,6 +110,7 @@ ${CLANGD_ATOMIC_LIB} ) +add_subdirectory(refactor/tweaks) if( LLVM_LIB_FUZZING_ENGINE OR LLVM_USE_SANITIZE_COVERAGE ) add_subdirectory(fuzzer) endif() Index: clangd/ClangdLSPServer.cpp =================================================================== --- clangd/ClangdLSPServer.cpp +++ clangd/ClangdLSPServer.cpp @@ -8,11 +8,14 @@ #include "ClangdLSPServer.h" #include "Diagnostics.h" +#include "Protocol.h" #include "SourceCode.h" #include "Trace.h" #include "URI.h" +#include "clang/Tooling/Core/Replacement.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/Path.h" #include "llvm/Support/ScopedPrinter.h" @@ -30,6 +33,28 @@ } }; +/// Transforms a tweak into a code action that would apply it if executed. +/// EXPECTS: T.prepare() was called and returned true. +CodeAction toCodeAction(const ClangdServer::TweakRef &T, const URIForFile &File, + Range Selection) { + CodeAction CA; + CA.title = T.Title; + CA.kind = CodeAction::REFACTOR_KIND; + // This tweak may have an expensive second stage, we only run it if the user + // actually chooses it in the UI. We reply with a command that would run the + // corresponding tweak. + // FIXME: for some tweaks, computing the edits is cheap and we could send them + // directly. + CA.command.emplace(); + CA.command->title = T.Title; + CA.command->command = Command::CLANGD_APPLY_TWEAK; + CA.command->tweakArgs.emplace(); + CA.command->tweakArgs->file = File; + CA.command->tweakArgs->tweakID = T.ID; + CA.command->tweakArgs->selection = Selection; + return CA; +}; + void adjustSymbolKinds(llvm::MutableArrayRef Syms, SymbolKindBitset Kinds) { for (auto &S : Syms) { @@ -338,7 +363,9 @@ {"referencesProvider", true}, {"executeCommandProvider", llvm::json::Object{ - {"commands", {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND}}, + {"commands", + {ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND, + ExecuteCommandParams::CLANGD_APPLY_TWEAK}}, }}, }}}}); } @@ -400,7 +427,7 @@ void ClangdLSPServer::onCommand(const ExecuteCommandParams &Params, Callback Reply) { - auto ApplyEdit = [&](WorkspaceEdit WE) { + auto ApplyEdit = [this](WorkspaceEdit WE) { ApplyWorkspaceEditParams Edit; Edit.edit = std::move(WE); // Ideally, we would wait for the response and if there is no error, we @@ -420,6 +447,31 @@ Reply("Fix applied."); ApplyEdit(*Params.workspaceEdit); + } else if (Params.command == ExecuteCommandParams::CLANGD_APPLY_TWEAK && + Params.tweakArgs) { + auto Code = DraftMgr.getDraft(Params.tweakArgs->file.file()); + if (!Code) + return Reply(llvm::createStringError( + llvm::inconvertibleErrorCode(), + "trying to apply a code action for a non-added file")); + + auto Action = [ApplyEdit](decltype(Reply) Reply, URIForFile File, + std::string Code, + llvm::Expected R) { + if (!R) + return Reply(R.takeError()); + + WorkspaceEdit WE; + WE.changes.emplace(); + (*WE.changes)[File.uri()] = replacementsToEdits(Code, *R); + + Reply("Fix applied."); + ApplyEdit(std::move(WE)); + }; + Server->applyTweak(Params.tweakArgs->file.file(), + Params.tweakArgs->selection, Params.tweakArgs->tweakID, + Bind(Action, std::move(Reply), Params.tweakArgs->file, + std::move(*Code))); } 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 @@ -601,28 +653,53 @@ void ClangdLSPServer::onCodeAction(const CodeActionParams &Params, Callback Reply) { - auto Code = DraftMgr.getDraft(Params.textDocument.uri.file()); + URIForFile File = Params.textDocument.uri; + auto Code = DraftMgr.getDraft(File.file()); if (!Code) return Reply(llvm::make_error( "onCodeAction called for non-added file", ErrorCode::InvalidParams)); // We provide a code action for Fixes on the specified diagnostics. - std::vector Actions; + std::vector FixIts; for (const Diagnostic &D : Params.context.diagnostics) { - for (auto &F : getFixes(Params.textDocument.uri.file(), D)) { - Actions.push_back(toCodeAction(F, Params.textDocument.uri)); - Actions.back().diagnostics = {D}; + for (auto &F : getFixes(File.file(), D)) { + FixIts.push_back(toCodeAction(F, Params.textDocument.uri)); + FixIts.back().diagnostics = {D}; } } - if (SupportsCodeAction) - Reply(llvm::json::Array(Actions)); - else { - std::vector Commands; - for (const auto &Action : Actions) - if (auto Command = asCommand(Action)) - Commands.push_back(std::move(*Command)); - Reply(llvm::json::Array(Commands)); - } + // Now enumerate the semantic code actions. + auto ConsumeActions = + [this](decltype(Reply) Reply, URIForFile File, std::string Code, + Range Selection, std::vector FixIts, + llvm::Expected> Tweaks) { + if (!Tweaks) { + auto Err = Tweaks.takeError(); + if (Err.isA()) + return Reply(std::move(Err)); // do no logging, this is expected. + elog("error while getting semantic code actions: {0}", + std::move(Err)); + return Reply(llvm::json::Array(FixIts)); + } + + std::vector Actions = std::move(FixIts); + Actions.reserve(Actions.size() + Tweaks->size()); + for (const auto &T : *Tweaks) + Actions.push_back(toCodeAction(T, File, Selection)); + + if (SupportsCodeAction) + return Reply(llvm::json::Array(Actions)); + std::vector Commands; + for (const auto &Action : Actions) { + if (auto Command = asCommand(Action)) + Commands.push_back(std::move(*Command)); + } + return Reply(llvm::json::Array(Commands)); + }; + + Server->enumerateTweaks(File.file(), Params.range, + Bind(ConsumeActions, std::move(Reply), + std::move(File), std::move(*Code), Params.range, + std::move(FixIts))); } void ClangdLSPServer::onCompletion(const CompletionParams &Params, Index: clangd/ClangdServer.h =================================================================== --- clangd/ClangdServer.h +++ clangd/ClangdServer.h @@ -21,8 +21,10 @@ #include "index/Background.h" #include "index/FileIndex.h" #include "index/Index.h" +#include "refactor/Tweak.h" #include "clang/Tooling/CompilationDatabase.h" #include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/FunctionExtras.h" #include "llvm/ADT/IntrusiveRefCntPtr.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/StringRef.h" @@ -211,6 +213,18 @@ void rename(PathRef File, Position Pos, llvm::StringRef NewName, Callback> CB); + struct TweakRef { + TweakID ID; /// ID to pass for applyTweak. + std::string Title; /// A single-line message to show in the UI. + }; + /// Enumerate the code tweaks available to the user at a specified point. + void enumerateTweaks(PathRef File, Range Sel, + Callback> CB); + + /// Apply the code tweak with a specified \p ID. + void applyTweak(PathRef File, Range Sel, TweakID ID, + Callback CB); + /// Only for testing purposes. /// Waits until all requests to worker thread are finished and dumps AST for /// \p File. \p File must be in the list of added documents. Index: clangd/ClangdServer.cpp =================================================================== --- clangd/ClangdServer.cpp +++ clangd/ClangdServer.cpp @@ -16,11 +16,13 @@ #include "XRefs.h" #include "index/FileIndex.h" #include "index/Merge.h" +#include "refactor/Tweak.h" #include "clang/Format/Format.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/CompilerInvocation.h" #include "clang/Lex/Preprocessor.h" #include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Core/Replacement.h" #include "clang/Tooling/Refactoring/RefactoringResultConsumer.h" #include "clang/Tooling/Refactoring/Rename/RenamingAction.h" #include "llvm/ADT/ArrayRef.h" @@ -28,10 +30,12 @@ #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Errc.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include +#include #include namespace clang { @@ -325,6 +329,56 @@ "Rename", File, Bind(Action, File.str(), NewName.str(), std::move(CB))); } +void ClangdServer::enumerateTweaks(PathRef File, Range Sel, + Callback> CB) { + auto Action = [Sel](decltype(CB) CB, std::string File, + Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + + auto &AST = InpAST->AST; + auto CursorLoc = sourceLocationInMainFile( + AST.getASTContext().getSourceManager(), Sel.start); + if (!CursorLoc) + return CB(CursorLoc.takeError()); + Tweak::Selection Inputs = {InpAST->Inputs.Contents, InpAST->AST, + *CursorLoc}; + + std::vector Res; + for (auto &T : prepareTweaks(Inputs)) + Res.push_back({T->id(), T->title()}); + CB(std::move(Res)); + }; + + WorkScheduler.runWithAST("EnumerateTweaks", File, + Bind(Action, std::move(CB), File.str())); +} + +void ClangdServer::applyTweak(PathRef File, Range Sel, TweakID ID, + Callback CB) { + auto Action = [ID, Sel](decltype(CB) CB, std::string File, + Expected InpAST) { + if (!InpAST) + return CB(InpAST.takeError()); + + auto &AST = InpAST->AST; + auto CursorLoc = sourceLocationInMainFile( + AST.getASTContext().getSourceManager(), Sel.start); + if (!CursorLoc) + return CB(CursorLoc.takeError()); + Tweak::Selection Inputs = {InpAST->Inputs.Contents, InpAST->AST, + *CursorLoc}; + + auto A = prepareTweak(ID, Inputs); + if (!A) + return CB(A.takeError()); + // FIXME: run formatter on top of resulting replacements. + return CB((*A)->apply(Inputs)); + }; + WorkScheduler.runWithAST("ApplyTweak", File, + Bind(Action, std::move(CB), File.str())); +} + void ClangdServer::dumpAST(PathRef File, llvm::unique_function Callback) { auto Action = [](decltype(Callback) Callback, Index: clangd/Protocol.h =================================================================== --- clangd/Protocol.h +++ clangd/Protocol.h @@ -631,6 +631,21 @@ bool fromJSON(const llvm::json::Value &, WorkspaceEdit &); llvm::json::Value toJSON(const WorkspaceEdit &WE); +/// Arguments for the 'applyTweak' command. The server sends these commands as a +/// response to the textDocument/codeAction request. The client can later send a +/// command back to the server if the user requests to execute a particular code +/// tweak. +struct TweakArgs { + /// A file provided by the client on a textDocument/codeAction request. + URIForFile file; + /// A selection provided by the client on a textDocument/codeAction request. + Range selection; + /// ID of the tweak that should be executed. Corresponds to Tweak::id(). + std::string tweakID; +}; +bool fromJSON(const llvm::json::Value &, TweakArgs &); +llvm::json::Value toJSON(const TweakArgs &A); + /// 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 @@ -642,12 +657,15 @@ struct ExecuteCommandParams { // Command to apply fix-its. Uses WorkspaceEdit as argument. const static llvm::StringLiteral CLANGD_APPLY_FIX_COMMAND; + // Command to apply the code action. Uses TweakArgs as argument. + const static llvm::StringLiteral CLANGD_APPLY_TWEAK; /// The command identifier, e.g. CLANGD_APPLY_FIX_COMMAND std::string command; // Arguments llvm::Optional workspaceEdit; + llvm::Optional tweakArgs; }; bool fromJSON(const llvm::json::Value &, ExecuteCommandParams &); @@ -669,6 +687,7 @@ /// Used to filter code actions. llvm::Optional kind; const static llvm::StringLiteral QUICKFIX_KIND; + const static llvm::StringLiteral REFACTOR_KIND; /// The diagnostics that this code action resolves. llvm::Optional> diagnostics; Index: clangd/Protocol.cpp =================================================================== --- clangd/Protocol.cpp +++ clangd/Protocol.cpp @@ -421,6 +421,9 @@ const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_FIX_COMMAND = "clangd.applyFix"; +const llvm::StringLiteral ExecuteCommandParams::CLANGD_APPLY_TWEAK = + "clangd.applyTweak"; + bool fromJSON(const llvm::json::Value &Params, ExecuteCommandParams &R) { llvm::json::ObjectMapper O(Params); if (!O || !O.map("command", R.command)) @@ -431,6 +434,8 @@ return Args && Args->size() == 1 && fromJSON(Args->front(), R.workspaceEdit); } + if (R.command == ExecuteCommandParams::CLANGD_APPLY_TWEAK) + return Args && Args->size() == 1 && fromJSON(Args->front(), R.tweakArgs); return false; // Unrecognized command. } @@ -497,10 +502,13 @@ auto Cmd = llvm::json::Object{{"title", C.title}, {"command", C.command}}; if (C.workspaceEdit) Cmd["arguments"] = {*C.workspaceEdit}; + if (C.tweakArgs) + Cmd["arguments"] = {*C.tweakArgs}; return std::move(Cmd); } const llvm::StringLiteral CodeAction::QUICKFIX_KIND = "quickfix"; +const llvm::StringLiteral CodeAction::REFACTOR_KIND = "refactor"; llvm::json::Value toJSON(const CodeAction &CA) { auto CodeAction = llvm::json::Object{{"title", CA.title}}; @@ -544,6 +552,17 @@ return llvm::json::Object{{"changes", std::move(FileChanges)}}; } +bool fromJSON(const llvm::json::Value &Params, TweakArgs &A) { + llvm::json::ObjectMapper O(Params); + return O && O.map("file", A.file) && O.map("selection", A.selection) && + O.map("tweakID", A.tweakID); +} + +llvm::json::Value toJSON(const TweakArgs &A) { + return llvm::json::Object{ + {"tweakID", A.tweakID}, {"selection", A.selection}, {"file", A.file}}; +} + llvm::json::Value toJSON(const ApplyWorkspaceEditParams &Params) { return llvm::json::Object{{"edit", Params.edit}}; } Index: clangd/SourceCode.h =================================================================== --- clangd/SourceCode.h +++ clangd/SourceCode.h @@ -56,6 +56,11 @@ /// FIXME: This should return an error if the location is invalid. Position sourceLocToPosition(const SourceManager &SM, SourceLocation Loc); +/// Return the file location, corresponding to \p P. Note that one should take +/// care to avoid comparing the result with expansion locations. +llvm::Expected sourceLocationInMainFile(const SourceManager &SM, + Position P); + // Converts a half-open clang source range to an LSP range. // Note that clang also uses closed source ranges, which this can't handle! Range halfOpenToRange(const SourceManager &SM, CharSourceRange R); Index: clangd/SourceCode.cpp =================================================================== --- clangd/SourceCode.cpp +++ clangd/SourceCode.cpp @@ -141,6 +141,16 @@ return P; } +llvm::Expected sourceLocationInMainFile(const SourceManager &SM, + Position P) { + llvm::StringRef Code = SM.getBuffer(SM.getMainFileID())->getBuffer(); + auto Offset = + positionToOffset(Code, P, /*AllowColumnBeyondLineLength=*/false); + if (!Offset) + return Offset.takeError(); + return SM.getLocForStartOfFile(SM.getMainFileID()).getLocWithOffset(*Offset); +} + Range halfOpenToRange(const SourceManager &SM, CharSourceRange R) { // Clang is 1-based, LSP uses 0-based indexes. Position Begin = sourceLocToPosition(SM, R.getBegin()); Index: clangd/refactor/Tweak.h =================================================================== --- clangd/refactor/Tweak.h +++ clangd/refactor/Tweak.h @@ -0,0 +1,98 @@ +//===--- Tweak.h -------------------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// Tweaks are small refactoring-like actions that run over the AST and produce +// the set of edits as a result. They are local, i.e. they should take the +// current editor context, e.g. the cursor position and selection into account. +// The actions are executed in two stages: +// - Stage 1 should check whether the action is available in a current +// context. It should be cheap and fast to compute as it is executed for all +// available actions on every client request, which happen quite frequently. +// - Stage 2 is performed after stage 1 and can be more expensive to compute. +// It is performed when the user actually chooses the action. +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_TWEAK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANGD_REFACTOR_ACTIONS_TWEAK_H + +#include "ClangdUnit.h" +#include "Protocol.h" +#include "clang/Tooling/Core/Replacement.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/StringRef.h" +namespace clang { +namespace clangd { + +using TweakID = llvm::StringRef; + +/// An interface base for small context-sensitive refactoring actions. +/// To implement a new tweak use the following pattern in a .cpp file: +/// class MyTweak : public Tweak { +/// public: +/// TweakID id() const override final; // definition provided by +/// // REGISTER_TWEAK. +/// // implement other methods here. +/// }; +/// REGISTER_TWEAK(MyTweak); +class Tweak { +public: + /// Input to prepare and apply tweaks. + struct Selection { + /// The text of the active document. + llvm::StringRef Code; + /// Parsed AST of the active file. + ParsedAST &AST; + /// A location of the cursor in the editor. + SourceLocation Cursor; + // FIXME: add selection when there are checks relying on it. + // FIXME: provide a way to get sources and ASTs for other files. + // FIXME: cache some commonly required information (e.g. AST nodes under + // cursor) to avoid redundant AST visit in every action. + }; + virtual ~Tweak() = default; + /// A unique id of the action, it is always equal to the name of the class + /// defining the Tweak. Definition is provided automatically by + /// REGISTER_TWEAK. + virtual TweakID id() const = 0; + /// Run the first stage of the action. The non-None result indicates that the + /// action is available and should be shown to the user. Returns None if the + /// action is not available. + /// This function should be fast, if the action requires non-trivial work it + /// should be moved into 'apply'. + /// Returns true iff the action is available and apply() can be called on it. + virtual bool prepare(const Selection &Sel) = 0; + /// Run the second stage of the action that would produce the actual changes. + /// EXPECTS: prepare() was called and returned true. + virtual Expected apply(const Selection &Sel) = 0; + /// A one-line title of the action that should be shown to the users in the + /// UI. + /// EXPECTS: prepare() was called and returned true. + virtual std::string title() const = 0; +}; + +// All tweaks must be registered in the .cpp file next to their definition. +#define REGISTER_TWEAK(Subclass) \ + ::llvm::Registry<::clang::clangd::Tweak>::Add \ + TweakRegistrationFor##Subclass(#Subclass, /*Description=*/""); \ + ::clang::clangd::TweakID Subclass::id() const { \ + return llvm::StringLiteral(#Subclass); \ + } + +/// Calls prepare() on all tweaks, returning those that can run on the +/// selection. +std::vector> prepareTweaks(const Tweak::Selection &S); + +// Calls prepare() on the tweak with a given ID. +// If prepare() returns false, returns an error. +// If prepare() returns true, returns the corresponding tweak. +llvm::Expected> prepareTweak(TweakID ID, + const Tweak::Selection &S); + +} // namespace clangd +} // namespace clang + +#endif Index: clangd/refactor/Tweak.cpp =================================================================== --- clangd/refactor/Tweak.cpp +++ clangd/refactor/Tweak.cpp @@ -0,0 +1,74 @@ +//===--- Tweak.cpp -----------------------------------------------*- C++-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +#include "Tweak.h" +#include "Logger.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Registry.h" +#include +#include + +LLVM_INSTANTIATE_REGISTRY(llvm::Registry); + +namespace clang { +namespace clangd { + +/// A handy typedef to save some typing. +typedef llvm::Registry TweakRegistry; + +namespace { +/// Asserts invariants on TweakRegistry. No-op with assertion disabled. +void validateRegistry() { +#ifndef NDEBUG + llvm::StringSet<> Seen; + for (const auto &E : TweakRegistry::entries()) { + // REGISTER_TWEAK ensures E.getName() is equal to the tweak class name. + // We check that id() matches it. + assert(E.instantiate()->id() == E.getName() && + "id should be equal to class name"); + assert(Seen.try_emplace(E.getName()).second && "duplicate check id"); + } +#endif +} +} // namespace + +std::vector> prepareTweaks(const Tweak::Selection &S) { + validateRegistry(); + + std::vector> Available; + for (const auto &E : TweakRegistry::entries()) { + std::unique_ptr T = E.instantiate(); + if (!T->prepare(S)) + continue; + Available.push_back(std::move(T)); + } + // Ensure deterministic order of the results. + llvm::sort(Available, + [](const std::unique_ptr &L, + const std::unique_ptr &R) { return L->id() < R->id(); }); + return Available; +} + +llvm::Expected> prepareTweak(TweakID ID, + const Tweak::Selection &S) { + auto It = llvm::find_if( + TweakRegistry::entries(), + [ID](const TweakRegistry::entry &E) { return E.getName() == ID; }); + if (It == TweakRegistry::end()) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "id of the tweak is invalid"); + std::unique_ptr T = It->instantiate(); + if (!T->prepare(S)) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "failed to prepare() a check"); + return T; +} + +} // namespace clangd +} // namespace clang Index: clangd/refactor/tweaks/CMakeLists.txt =================================================================== --- clangd/refactor/tweaks/CMakeLists.txt +++ clangd/refactor/tweaks/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../..) + +# A target containing all code tweaks (i.e. mini-refactorings) provided by +# clangd. +# Built as an object library to make sure linker does not remove global +# constructors that register individual tweaks in a global registry. +# To enable these tweaks in exectubales or shared libraries, add +# $ to a list of sources, see +# clangd/tool/CMakeLists.txt for an example. +add_clang_library(clangDaemonTweaks OBJECT + Dummy.cpp # FIXME: to avoid CMake errors due to empty inputs, remove when a + # first tweak lands. + ) Index: clangd/refactor/tweaks/Dummy.cpp =================================================================== --- clangd/refactor/tweaks/Dummy.cpp +++ clangd/refactor/tweaks/Dummy.cpp @@ -0,0 +1,9 @@ +//===--- Dummy.cpp -----------------------------------------------*- C++-*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// Does nothing, only here to avoid cmake errors for empty libraries. \ No newline at end of file Index: clangd/tool/CMakeLists.txt =================================================================== --- clangd/tool/CMakeLists.txt +++ clangd/tool/CMakeLists.txt @@ -3,6 +3,7 @@ add_clang_tool(clangd ClangdMain.cpp + $ ) set(LLVM_LINK_COMPONENTS Index: test/clangd/fixits-command.test =================================================================== --- test/clangd/fixits-command.test +++ test/clangd/fixits-command.test @@ -23,7 +23,7 @@ # CHECK-NEXT: "uri": "file://{{.*}}/foo.c" # CHECK-NEXT: } --- -{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses"}]}}} +{"jsonrpc":"2.0","id":2,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses"}]}}} # CHECK: "id": 2, # CHECK-NEXT: "jsonrpc": "2.0", # CHECK-NEXT: "result": [ @@ -92,7 +92,7 @@ # CHECK-NEXT: } # CHECK-NEXT: ] --- -{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":104,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses"}]}}} +{"jsonrpc":"2.0","id":3,"method":"textDocument/codeAction","params":{"textDocument":{"uri":"test:///foo.c"},"range":{"start":{"line":0,"character":13},"end":{"line":0,"character":35}},"context":{"diagnostics":[{"range":{"start": {"line": 0, "character": 32}, "end": {"line": 0, "character": 37}},"severity":2,"message":"Using the result of an assignment as a condition without parentheses"}]}}} # Make sure unused "code" and "source" fields ignored gracefully # CHECK: "id": 3, # CHECK-NEXT: "jsonrpc": "2.0", Index: test/clangd/initialize-params.test =================================================================== --- test/clangd/initialize-params.test +++ test/clangd/initialize-params.test @@ -25,7 +25,8 @@ # CHECK-NEXT: "documentSymbolProvider": true, # CHECK-NEXT: "executeCommandProvider": { # CHECK-NEXT: "commands": [ -# CHECK-NEXT: "clangd.applyFix" +# CHECK-NEXT: "clangd.applyFix", +# CHECK-NEXT: "clangd.applyTweak" # CHECK-NEXT: ] # CHECK-NEXT: }, # CHECK-NEXT: "hoverProvider": true,