From ab061df0db0bd0718717a5350f7ab4d1838fb504 Mon Sep 17 00:00:00 2001 From: Ilya Date: Wed, 30 Mar 2022 19:30:12 +0100 Subject: [PATCH] Detach the "Move method" logic from the `LSPTask` (#5537) * Move `queryBy*` functions from the `LSPTask` to a separate file * Move the `getRenameEdits` function from the `LSPTask` to the `AbstractRenamer` * Move the refactoring logic from the code action into separate file * Group query functions into a LSPQuery class with static methods * main/lsp/requests/move_method -> main/lsp/MoveMethod * Formatting --- main/lsp/AbstractRenamer.cc | 32 ++++ main/lsp/AbstractRenamer.h | 3 + main/lsp/BUILD | 2 + main/lsp/LSPQuery.cc | 124 +++++++++++++ main/lsp/LSPQuery.h | 25 +++ main/lsp/LSPTask.cc | 151 +--------------- main/lsp/LSPTask.h | 12 -- main/lsp/MoveMethod.cc | 224 ++++++++++++++++++++++++ main/lsp/MoveMethod.h | 17 ++ main/lsp/requests/code_action.cc | 220 +---------------------- main/lsp/requests/code_action.h | 6 - main/lsp/requests/completion.cc | 3 +- main/lsp/requests/definition.cc | 5 +- main/lsp/requests/document_highlight.cc | 5 +- main/lsp/requests/hover.cc | 4 +- main/lsp/requests/implementation.cc | 5 +- main/lsp/requests/prepare_rename.cc | 5 +- main/lsp/requests/references.cc | 5 +- main/lsp/requests/rename.cc | 10 +- main/lsp/requests/signature_help.cc | 5 +- main/lsp/requests/sorbet_show_symbol.cc | 5 +- main/lsp/requests/type_definition.cc | 5 +- 22 files changed, 470 insertions(+), 403 deletions(-) create mode 100644 main/lsp/LSPQuery.cc create mode 100644 main/lsp/LSPQuery.h create mode 100644 main/lsp/MoveMethod.cc create mode 100644 main/lsp/MoveMethod.h diff --git a/main/lsp/AbstractRenamer.cc b/main/lsp/AbstractRenamer.cc index 3333a08bf51..9c010048edf 100644 --- a/main/lsp/AbstractRenamer.cc +++ b/main/lsp/AbstractRenamer.cc @@ -1,5 +1,7 @@ #include "AbstractRenamer.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/lsp.h" + using namespace std; namespace sorbet::realmain::lsp { @@ -132,4 +134,34 @@ void AbstractRenamer::addDispatchRelatedMethods(const core::GlobalState &gs, con } } +void AbstractRenamer::getRenameEdits(LSPTypecheckerInterface &typechecker, core::SymbolRef symbol, string newName) { + const core::GlobalState &gs = typechecker.state(); + auto originalName = symbol.name(gs).show(gs); + + addSymbol(symbol); + + auto symbolQueue = getQueue(); + for (auto sym = symbolQueue->pop(); sym.exists(); sym = symbolQueue->pop()) { + auto queryResult = LSPQuery::bySymbol(config, typechecker, sym); + if (queryResult.error) { + return; + } + + // Filter for untyped files, and deduplicate responses by location. We don't use extractLocations here because + // in some cases like sends, we need the SendResponse to be able to accurately find the method name in the + // expression. + for (auto &response : LSPQuery::filterAndDedup(gs, queryResult.responses)) { + auto loc = response->getLoc(); + if (loc.file().data(gs).isPayload()) { + // We don't support renaming things in payload files. + return; + } + + // We may process the same send multiple times in case of union types, but this is ok because the renamer + // de-duplicates edits at the same location + rename(response, sym); + } + } +} + } // namespace sorbet::realmain::lsp diff --git a/main/lsp/AbstractRenamer.h b/main/lsp/AbstractRenamer.h index cf56a31a7f5..422466a175f 100644 --- a/main/lsp/AbstractRenamer.h +++ b/main/lsp/AbstractRenamer.h @@ -3,6 +3,7 @@ #include "core/lsp/QueryResponse.h" #include "main/lsp/LSPConfiguration.h" +#include "main/lsp/LSPTypechecker.h" #include "main/lsp/json_types.h" namespace sorbet::realmain::lsp { @@ -29,6 +30,8 @@ class AbstractRenamer { std::variant> buildWorkspaceEdit(); virtual void addSymbol(const core::SymbolRef) = 0; + void getRenameEdits(LSPTypecheckerInterface &typechecker, core::SymbolRef symbol, std::string newName); + bool getInvalid(); std::string getError(); std::shared_ptr getQueue(); diff --git a/main/lsp/BUILD b/main/lsp/BUILD index bd0e4b02369..aae609ddc11 100644 --- a/main/lsp/BUILD +++ b/main/lsp/BUILD @@ -38,6 +38,8 @@ cc_library( "LSPMessage.h", "LSPOutput.h", "LSPPreprocessor.h", + "LSPQuery.h", + "MoveMethod.h", "json_types.h", "lsp.h", "wrapper.h", diff --git a/main/lsp/LSPQuery.cc b/main/lsp/LSPQuery.cc new file mode 100644 index 00000000000..68811971c97 --- /dev/null +++ b/main/lsp/LSPQuery.cc @@ -0,0 +1,124 @@ +#include "main/lsp/LSPQuery.h" +#include "common/sort.h" +#include "core/NameHash.h" +#include "main/lsp/json_types.h" + +using namespace std; + +namespace sorbet::realmain::lsp { + +// Filter for untyped locations, and dedup responses that are at the same location +vector> +LSPQuery::filterAndDedup(const core::GlobalState &gs, + const vector> &queryResponses) { + vector> responses; + // Filter for responses with a loc that exists and points to a typed file, unless it's a const, field or + // definition in which case we're ok with untyped files (because we know where those things are even in untyped + // files.) + for (auto &q : queryResponses) { + core::Loc loc = q->getLoc(); + if (loc.exists() && loc.file().exists()) { + auto fileIsTyped = loc.file().data(gs).strictLevel >= core::StrictLevel::True; + // If file is untyped, only support responses involving constants and definitions. + if (fileIsTyped || q->isConstant() || q->isField() || q->isMethodDef()) { + responses.push_back(make_unique(*q)); + } + } + } + + // sort by location and deduplicate + fast_sort(responses, + [](const unique_ptr &a, const unique_ptr &b) -> bool { + auto aLoc = a->getLoc(); + auto bLoc = b->getLoc(); + int cmp = aLoc.file().id() - bLoc.file().id(); + if (cmp == 0) { + cmp = aLoc.beginPos() - bLoc.beginPos(); + } + if (cmp == 0) { + cmp = aLoc.endPos() - bLoc.endPos(); + } + // TODO: precedence based on response type, in case of same location? + return cmp < 0; + }); + responses.resize( + std::distance(responses.begin(), std::unique(responses.begin(), responses.end(), + [](const unique_ptr &a, + const unique_ptr &b) -> bool { + auto aLoc = a->getLoc(); + auto bLoc = b->getLoc(); + return aLoc == bLoc; + }))); + return responses; +} + +LSPQueryResult LSPQuery::byLoc(const LSPConfiguration &config, LSPTypecheckerInterface &typechecker, string_view uri, + const Position &pos, LSPMethod forMethod, bool errorIfFileIsUntyped) { + Timer timeit(config.logger, "setupLSPQueryByLoc"); + const core::GlobalState &gs = typechecker.state(); + auto fref = config.uri2FileRef(gs, uri); + + if (!fref.exists() && config.isFileIgnored(config.remoteName2Local(uri))) { + auto error = make_unique( + (int)LSPErrorCodes::InvalidParams, + fmt::format("Ignored file at uri {} in {}", uri, convertLSPMethodToString(forMethod))); + return LSPQueryResult{{}, move(error)}; + } + + if (!fref.exists()) { + auto error = make_unique( + (int)LSPErrorCodes::InvalidParams, + fmt::format("Did not find file at uri {} in {}", uri, convertLSPMethodToString(forMethod))); + return LSPQueryResult{{}, move(error)}; + } + + if (errorIfFileIsUntyped && fref.data(gs).strictLevel < core::StrictLevel::True) { + config.logger->info("Ignoring request on untyped file `{}`", uri); + // Act as if the query returned no results. + return LSPQueryResult{{}, nullptr}; + } + + auto loc = config.lspPos2Loc(fref, pos, gs); + return typechecker.query(core::lsp::Query::createLocQuery(loc), {fref}); +} + +LSPQueryResult LSPQuery::LSPQuery::bySymbolInFiles(const LSPConfiguration &config, LSPTypecheckerInterface &typechecker, + core::SymbolRef symbol, vector frefs) { + Timer timeit(config.logger, "setupLSPQueryBySymbolInFiles"); + ENFORCE(symbol.exists()); + return typechecker.query(core::lsp::Query::createSymbolQuery(symbol), frefs); +} + +LSPQueryResult LSPQuery::bySymbol(const LSPConfiguration &config, LSPTypecheckerInterface &typechecker, + core::SymbolRef symbol) { + Timer timeit(config.logger, "setupLSPQueryBySymbol"); + ENFORCE(symbol.exists()); + vector frefs; + const core::GlobalState &gs = typechecker.state(); + const core::NameHash symNameHash(gs, symbol.name(gs)); + // Locate files that contain the same Name as the symbol. Is an overapproximation, but a good first filter. + int i = -1; + for (auto &file : typechecker.state().getFiles()) { + i++; + if (file == nullptr) { + continue; + } + + ENFORCE(file->getFileHash() != nullptr); + const auto &hash = *file->getFileHash(); + const auto &usedSends = hash.usages.sends; + const auto &usedSymbolNames = hash.usages.symbols; + auto ref = core::FileRef(i); + + const bool fileIsValid = ref.exists() && ref.data(gs).sourceType == core::File::Type::Normal; + if (fileIsValid && + (std::find(usedSends.begin(), usedSends.end(), symNameHash) != usedSends.end() || + std::find(usedSymbolNames.begin(), usedSymbolNames.end(), symNameHash) != usedSymbolNames.end())) { + frefs.emplace_back(ref); + } + } + + return typechecker.query(core::lsp::Query::createSymbolQuery(symbol), frefs); +} + +} // namespace sorbet::realmain::lsp diff --git a/main/lsp/LSPQuery.h b/main/lsp/LSPQuery.h new file mode 100644 index 00000000000..7246c451914 --- /dev/null +++ b/main/lsp/LSPQuery.h @@ -0,0 +1,25 @@ +#ifndef SORBET_LSP_QUERY_H +#define SORBET_LSP_QUERY_H + +#include "main/lsp/LSPTypechecker.h" + +namespace sorbet::realmain::lsp { + +class LSPQuery { +public: + static std::vector> + filterAndDedup(const core::GlobalState &gs, + const std::vector> &queryResponses); + + static LSPQueryResult byLoc(const LSPConfiguration &config, LSPTypecheckerInterface &typechecker, + std::string_view uri, const Position &pos, LSPMethod forMethod, + bool errorIfFileIsUntyped = true); + static LSPQueryResult bySymbolInFiles(const LSPConfiguration &config, LSPTypecheckerInterface &typechecker, + core::SymbolRef symbol, std::vector frefs); + static LSPQueryResult bySymbol(const LSPConfiguration &config, LSPTypecheckerInterface &typechecker, + core::SymbolRef symbol); +}; + +} // namespace sorbet::realmain::lsp + +#endif diff --git a/main/lsp/LSPTask.cc b/main/lsp/LSPTask.cc index 921b928cf3d..68aa9534e86 100644 --- a/main/lsp/LSPTask.cc +++ b/main/lsp/LSPTask.cc @@ -5,6 +5,7 @@ #include "core/NameHash.h" #include "core/lsp/QueryResponse.h" #include "main/lsp/LSPOutput.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" #include "main/lsp/lsp.h" @@ -246,105 +247,6 @@ bool LSPRequestTask::cancel(const MessageId &id) { return false; } -LSPQueryResult LSPTask::queryByLoc(LSPTypecheckerInterface &typechecker, string_view uri, const Position &pos, - const LSPMethod forMethod, bool errorIfFileIsUntyped) const { - Timer timeit(config.logger, "setupLSPQueryByLoc"); - const core::GlobalState &gs = typechecker.state(); - auto fref = config.uri2FileRef(gs, uri); - - if (!fref.exists() && config.isFileIgnored(config.remoteName2Local(uri))) { - auto error = make_unique( - (int)LSPErrorCodes::InvalidParams, - fmt::format("Ignored file at uri {} in {}", uri, convertLSPMethodToString(forMethod))); - return LSPQueryResult{{}, move(error)}; - } - - if (!fref.exists()) { - auto error = make_unique( - (int)LSPErrorCodes::InvalidParams, - fmt::format("Did not find file at uri {} in {}", uri, convertLSPMethodToString(forMethod))); - return LSPQueryResult{{}, move(error)}; - } - - if (errorIfFileIsUntyped && fref.data(gs).strictLevel < core::StrictLevel::True) { - config.logger->info("Ignoring request on untyped file `{}`", uri); - // Act as if the query returned no results. - return LSPQueryResult{{}, nullptr}; - } - - auto loc = config.lspPos2Loc(fref, pos, gs); - return typechecker.query(core::lsp::Query::createLocQuery(loc), {fref}); -} - -LSPQueryResult LSPTask::queryBySymbolInFiles(LSPTypecheckerInterface &typechecker, core::SymbolRef sym, - vector frefs) const { - Timer timeit(config.logger, "setupLSPQueryBySymbolInFiles"); - ENFORCE(sym.exists()); - return typechecker.query(core::lsp::Query::createSymbolQuery(sym), frefs); -} - -LSPQueryResult LSPTask::queryBySymbol(LSPTypecheckerInterface &typechecker, core::SymbolRef sym) const { - Timer timeit(config.logger, "setupLSPQueryBySymbol"); - ENFORCE(sym.exists()); - vector frefs; - const core::GlobalState &gs = typechecker.state(); - const core::NameHash symNameHash(gs, sym.name(gs)); - // Locate files that contain the same Name as the symbol. Is an overapproximation, but a good first filter. - int i = -1; - for (auto &file : typechecker.state().getFiles()) { - i++; - if (file == nullptr) { - continue; - } - - ENFORCE(file->getFileHash() != nullptr); - const auto &hash = *file->getFileHash(); - const auto &usedSends = hash.usages.sends; - const auto &usedSymbolNames = hash.usages.symbols; - auto ref = core::FileRef(i); - - const bool fileIsValid = ref.exists() && ref.data(gs).sourceType == core::File::Type::Normal; - if (fileIsValid && - (std::find(usedSends.begin(), usedSends.end(), symNameHash) != usedSends.end() || - std::find(usedSymbolNames.begin(), usedSymbolNames.end(), symNameHash) != usedSymbolNames.end())) { - frefs.emplace_back(ref); - } - } - - return typechecker.query(core::lsp::Query::createSymbolQuery(sym), frefs); -} - -void LSPTask::getRenameEdits(LSPTypecheckerInterface &typechecker, shared_ptr renamer, - core::SymbolRef symbol, string newName) { - const core::GlobalState &gs = typechecker.state(); - auto originalName = symbol.name(gs).show(gs); - - renamer->addSymbol(symbol); - - auto symbolQueue = renamer->getQueue(); - for (auto sym = symbolQueue->pop(); sym.exists(); sym = symbolQueue->pop()) { - auto queryResult = queryBySymbol(typechecker, sym); - if (queryResult.error) { - return; - } - - // Filter for untyped files, and deduplicate responses by location. We don't use extractLocations here because - // in some cases like sends, we need the SendResponse to be able to accurately find the method name in the - // expression. - for (auto &response : filterAndDedup(gs, queryResult.responses)) { - auto loc = response->getLoc(); - if (loc.file().data(gs).isPayload()) { - // We don't support renaming things in payload files. - return; - } - - // We may process the same send multiple times in case of union types, but this is ok because the renamer - // de-duplicates edits at the same location - renamer->rename(response, sym); - } - } -} - bool LSPTask::needsMultithreading(const LSPIndexer &indexer) const { return false; } @@ -366,56 +268,11 @@ bool LSPTask::canUseStaleData() const { return false; } -// Filter for untyped locations, and dedup responses that are at the same location -vector> -LSPTask::filterAndDedup(const core::GlobalState &gs, - const vector> &queryResponses) const { - vector> responses; - // Filter for responses with a loc that exists and points to a typed file, unless it's a const, field or - // definition in which case we're ok with untyped files (because we know where those things are even in untyped - // files.) - for (auto &q : queryResponses) { - core::Loc loc = q->getLoc(); - if (loc.exists() && loc.file().exists()) { - auto fileIsTyped = loc.file().data(gs).strictLevel >= core::StrictLevel::True; - // If file is untyped, only support responses involving constants and definitions. - if (fileIsTyped || q->isConstant() || q->isField() || q->isMethodDef()) { - responses.push_back(make_unique(*q)); - } - } - } - - // sort by location and deduplicate - fast_sort(responses, - [](const unique_ptr &a, const unique_ptr &b) -> bool { - auto aLoc = a->getLoc(); - auto bLoc = b->getLoc(); - int cmp = aLoc.file().id() - bLoc.file().id(); - if (cmp == 0) { - cmp = aLoc.beginPos() - bLoc.beginPos(); - } - if (cmp == 0) { - cmp = aLoc.endPos() - bLoc.endPos(); - } - // TODO: precedence based on response type, in case of same location? - return cmp < 0; - }); - responses.resize( - std::distance(responses.begin(), std::unique(responses.begin(), responses.end(), - [](const unique_ptr &a, - const unique_ptr &b) -> bool { - auto aLoc = a->getLoc(); - auto bLoc = b->getLoc(); - return aLoc == bLoc; - }))); - return responses; -} - vector> LSPTask::extractLocations(const core::GlobalState &gs, const vector> &queryResponses, vector> locations) const { - auto queryResponsesFiltered = filterAndDedup(gs, queryResponses); + auto queryResponsesFiltered = LSPQuery::filterAndDedup(gs, queryResponses); for (auto &q : queryResponsesFiltered) { if (auto *send = q->isSend()) { addLocIfExists(gs, locations, send->funLoc); @@ -430,7 +287,7 @@ vector> LSPTask::getReferencesToSymbol(LSPTypecheckerInterface &typechecker, core::SymbolRef symbol, vector> &&priorRefs) const { if (symbol.exists()) { - auto run2 = queryBySymbol(typechecker, symbol); + auto run2 = LSPQuery::bySymbol(config, typechecker, symbol); absl::c_move(run2.responses, back_inserter(priorRefs)); } return move(priorRefs); @@ -440,7 +297,7 @@ vector> LSPTask::getReferencesToSymbolInFile(LSPTypecheckerInterface &typechecker, core::FileRef fref, core::SymbolRef symbol, vector> &&priorRefs) const { if (symbol.exists() && fref.exists()) { - auto run2 = queryBySymbolInFiles(typechecker, symbol, {fref}); + auto run2 = LSPQuery::bySymbolInFiles(config, typechecker, symbol, {fref}); for (auto &resp : run2.responses) { // Ignore results in other files (which may have been picked up for typechecking purposes) if (resp->getLoc().file() == fref) { diff --git a/main/lsp/LSPTask.h b/main/lsp/LSPTask.h index 0c336977d89..9141701cedd 100644 --- a/main/lsp/LSPTask.h +++ b/main/lsp/LSPTask.h @@ -51,15 +51,6 @@ class LSPTask { extractLocations(const core::GlobalState &gs, const std::vector> &queryResponses, std::vector> locations = {}) const; - std::vector> - filterAndDedup(const core::GlobalState &gs, - const std::vector> &queryResponses) const; - - LSPQueryResult queryByLoc(LSPTypecheckerInterface &typechecker, std::string_view uri, const Position &pos, - LSPMethod forMethod, bool errorIfFileIsUntyped = true) const; - LSPQueryResult queryBySymbolInFiles(LSPTypecheckerInterface &typechecker, core::SymbolRef symbol, - std::vector frefs) const; - LSPQueryResult queryBySymbol(LSPTypecheckerInterface &typechecker, core::SymbolRef symbol) const; // Given a method or field symbol, checks if the symbol belongs to a `prop`, `const`, `attr_reader`, `attr_writer`, // etc, and populates an AccessorInfo object. @@ -77,9 +68,6 @@ class LSPTask { core::SymbolRef fallback, std::vector> &&priorRefs = {}) const; - void getRenameEdits(LSPTypecheckerInterface &typechecker, std::shared_ptr renamer, - core::SymbolRef symbol, std::string newName); - LSPTask(const LSPConfiguration &config, LSPMethod method); public: diff --git a/main/lsp/MoveMethod.cc b/main/lsp/MoveMethod.cc new file mode 100644 index 00000000000..3c81c99a425 --- /dev/null +++ b/main/lsp/MoveMethod.cc @@ -0,0 +1,224 @@ +#include "main/lsp/MoveMethod.h" +#include "main/lsp/AbstractRenamer.h" +#include "main/sig_finder/sig_finder.h" +using namespace std; + +namespace sorbet::realmain::lsp { + +namespace { + +bool isTSigRequired(const core::GlobalState &gs) { + return !core::Symbols::Module().data(gs)->derivesFrom(gs, core::Symbols::T_Sig()); +} + +optional findMethodTree(const ast::ExpressionPtr &tree, const core::SymbolRef method) { + if (auto seq = ast::cast_tree(tree)) { + for (auto &subtree : seq->stats) { + auto maybeMethod = findMethodTree(subtree, method); + if (maybeMethod.has_value()) { + return maybeMethod.value(); + } + } + } else if (auto klass = ast::cast_tree(tree)) { + for (auto &subtree : klass->rhs) { + auto maybeMethod = findMethodTree(subtree, method); + if (maybeMethod.has_value()) { + return maybeMethod.value(); + } + } + } else if (auto methodDef = ast::cast_tree(tree)) { + if (methodDef->symbol == method.asMethodRef()) { + return methodDef; + } + } + return nullopt; +} + +optional, core::LocOffsets>> methodLocs(const core::GlobalState &gs, + const ast::ExpressionPtr &rootTree, + const core::SymbolRef method, + const core::FileRef fref) { + auto maybeTree = findMethodTree(rootTree, method.asMethodRef()); + if (!maybeTree.has_value()) { + return nullopt; + } + auto methodLoc = maybeTree.value()->loc; + + auto maybeSig = sorbet::sig_finder::findSignature(gs, method); + if (!maybeSig.has_value()) { + return make_pair(nullopt, methodLoc); + } + core::LocOffsets sigLoc = {maybeSig->sig.beginPos(), maybeSig->body.endPos()}; + + return make_pair(sigLoc, methodLoc); +} + +// Turns ruby_function_name__ to RubyFunctionName, +// so we can use it in a new module name +string snakeToCamelCase(string_view name) { + string res; + const auto originalSize = name.size(); + res.reserve(originalSize); + bool shouldCapitalize = true; + for (int i = 0; i < originalSize; i++) { + if (name.at(i) == '_') { + shouldCapitalize = true; + continue; + } + res += shouldCapitalize ? toupper(name[i]) : name[i]; + shouldCapitalize = false; + } + return res; +} + +optional getNewModuleName(const core::GlobalState &gs, const core::NameRef name) { + const auto moduleName = absl::StrCat(snakeToCamelCase(name.show(gs)), "Module"); + const auto nameNotExists = [&](auto moduleName) { + if (auto nameRef = gs.lookupNameUTF8(moduleName); !nameRef.exists()) { + // We're using root here, because newely defined module would be at the top level + return !core::Symbols::root().data(gs)->findMember(gs, nameRef).exists(); + } + return false; + }; + + // if there are no names like that, return the new module name + if (nameNotExists(moduleName)) { + return moduleName; + } + + // otherwise, try to augment it + for (int i = 1; i < 42; i++) { + const auto newName = absl::StrCat(moduleName, i); + if (nameNotExists(newName)) { + return newName; + } + } + + // bail, if we haven't found untaken name in 42 tries + return nullopt; +} + +class MethodCallSiteRenamer : public AbstractRenamer { +public: + MethodCallSiteRenamer(const core::GlobalState &gs, const LSPConfiguration &config, const string oldName, + const string newName) + : AbstractRenamer(gs, config, oldName, newName) { + const vector invalidNames = {"initialize", "call"}; + for (auto name : invalidNames) { + if (oldName == name) { + invalid = true; + error = fmt::format("The `{}` method cannot be moved to a module.", oldName); + return; + } + } + } + + ~MethodCallSiteRenamer() {} + void rename(unique_ptr &response, const core::SymbolRef originalSymbol) override { + if (invalid) { + return; + } + auto loc = response->getLoc(); + + // If we're renaming the exact same place twice, silently ignore it. We reach this condition when we find the + // same method send through multiple definitions (e.g. in the case of union types) + auto it = edits.find(loc); + if (it != edits.end()) { + return; + } + + auto source = loc.source(gs); + if (!source.has_value()) { + return; + } + string newsrc; + if (auto sendResp = response->isSend()) { + // if the call site is not trivial, don't attempt to rename + // the typecheck error will guide user how to fix it + for (auto dr = sendResp->dispatchResult.get(); dr != nullptr; dr = dr->secondary.get()) { + if (dr->main.method != originalSymbol.asMethodRef()) { + return; + } + } + + edits[sendResp->receiverLoc] = newName; + } + } + void addSymbol(const core::SymbolRef symbol) override { + if (!symbol.isMethod()) { + return; + } + addSubclassRelatedMethods(gs, symbol.asMethodRef(), getQueue()); + } +}; // MethodCallSiteRenamer + +vector> moveMethod(const LSPConfiguration &config, const core::GlobalState &gs, + const core::lsp::MethodDefResponse &definition, + LSPTypecheckerInterface &typechecker, string_view newModuleName) { + auto moduleStart = fmt::format("module {}{}\n ", newModuleName, isTSigRequired(gs) ? "\n extend T::Sig" : ""); + auto moduleEnd = "\nend"; + + auto fref = definition.termLoc.file(); + + auto trees = typechecker.getResolved({fref}); + ENFORCE(!trees.empty()); + auto &rootTree = trees[0].tree; + + auto sigAndMethodLocs = methodLocs(gs, rootTree, definition.symbol, fref); + if (!sigAndMethodLocs.has_value()) { + return {}; + } + auto [maybeSigLoc, methodLoc] = sigAndMethodLocs.value(); + auto methodSourceLocOffset = maybeSigLoc.has_value() ? maybeSigLoc.value().join(methodLoc) : methodLoc; + auto methodSourceLoc = core::Loc(fref, methodSourceLocOffset); + auto methodSource = methodSourceLoc.source(gs); + if (!methodSource.has_value()) { + return {}; + } + + auto insertPosition = Range::fromLoc(gs, core::Loc(fref, rootTree.loc().copyWithZeroLength())); + auto newModuleSource = fmt::format("{}{}{}\n\n", moduleStart, methodSource.value(), moduleEnd); + + // This manipulations with the positions are required to remove leading tabs and whitespaces at the original method + // position + auto [oldMethodStart, oldMethodEnd] = methodSourceLoc.position(gs); + auto oldMethodLoc = core::Loc::fromDetails(gs, fref, {oldMethodStart.line, 0}, oldMethodEnd); + ENFORCE(oldMethodLoc.has_value()); + + vector> res; + res.emplace_back(make_unique(move(insertPosition), newModuleSource)); + res.emplace_back(make_unique(Range::fromLoc(gs, oldMethodLoc.value()), "")); + return res; +} + +} // namespace + +vector> getMoveMethodEdits(const LSPConfiguration &config, const core::GlobalState &gs, + const core::lsp::MethodDefResponse &definition, + LSPTypecheckerInterface &typechecker) { + vector> res; + auto newModuleName = getNewModuleName(gs, definition.name); + if (!newModuleName.has_value()) { + return res; + } + + vector> edits = moveMethod(config, gs, definition, typechecker, newModuleName.value()); + + auto renamer = make_shared(gs, config, definition.name.show(gs), newModuleName.value()); + renamer->getRenameEdits(typechecker, definition.symbol, newModuleName.value()); + auto callSiteEdits = renamer->buildTextDocumentEdits(); + + if (callSiteEdits.has_value()) { + for (auto &edit : callSiteEdits.value()) { + res.emplace_back(move(edit)); + } + } + auto docEdit = + make_unique(make_unique( + config.fileRef2Uri(gs, definition.termLoc.file()), JSONNullObject()), + move(edits)); + + res.emplace_back(move(docEdit)); + return res; +} +} // namespace sorbet::realmain::lsp diff --git a/main/lsp/MoveMethod.h b/main/lsp/MoveMethod.h new file mode 100644 index 00000000000..b4a3b1e4deb --- /dev/null +++ b/main/lsp/MoveMethod.h @@ -0,0 +1,17 @@ +#ifndef SORBET_MOVE_METHOD_H +#define SORBET_MOVE_METHOD_H + +#include "main/lsp/LSPConfiguration.h" +#include "main/lsp/LSPTypechecker.h" +#include "main/lsp/json_types.h" + +namespace sorbet::realmain::lsp { + +std::vector> getMoveMethodEdits(const LSPConfiguration &config, + const core::GlobalState &gs, + const core::lsp::MethodDefResponse &definition, + LSPTypecheckerInterface &typechecker); + +} // namespace sorbet::realmain::lsp + +#endif diff --git a/main/lsp/requests/code_action.cc b/main/lsp/requests/code_action.cc index 097b4550c5d..58be8b51a65 100644 --- a/main/lsp/requests/code_action.cc +++ b/main/lsp/requests/code_action.cc @@ -1,6 +1,8 @@ #include "main/lsp/requests/code_action.h" #include "common/sort.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" +#include "main/lsp/MoveMethod.h" #include "main/lsp/json_types.h" #include "main/lsp/lsp.h" #include "main/sig_finder/sig_finder.h" @@ -19,10 +21,6 @@ bool isOperator(string_view name) { return std::find(operators.begin(), operators.end(), name) != operators.end(); } -bool isTSigRequired(const core::GlobalState &gs) { - return !core::Symbols::Module().data(gs)->derivesFrom(gs, core::Symbols::T_Sig()); -} - vector> getQuickfixEdits(const LSPConfiguration &config, const core::GlobalState &gs, const vector &edits) { UnorderedMap>> editsByFile; @@ -42,221 +40,11 @@ vector> getQuickfixEdits(const LSPConfiguration &co } return documentEdits; } - -optional findMethodTree(const ast::ExpressionPtr &tree, const core::SymbolRef method) { - if (auto seq = ast::cast_tree(tree)) { - for (auto &subtree : seq->stats) { - auto maybeMethod = findMethodTree(subtree, method); - if (maybeMethod.has_value()) { - return maybeMethod.value(); - } - } - } else if (auto klass = ast::cast_tree(tree)) { - for (auto &subtree : klass->rhs) { - auto maybeMethod = findMethodTree(subtree, method); - if (maybeMethod.has_value()) { - return maybeMethod.value(); - } - } - } else if (auto methodDef = ast::cast_tree(tree)) { - if (methodDef->symbol == method.asMethodRef()) { - return methodDef; - } - } - return nullopt; -} - -optional, core::LocOffsets>> methodLocs(const core::GlobalState &gs, - const ast::ExpressionPtr &rootTree, - const core::SymbolRef method, - const core::FileRef fref) { - auto maybeTree = findMethodTree(rootTree, method.asMethodRef()); - if (!maybeTree.has_value()) { - return nullopt; - } - auto methodLoc = maybeTree.value()->loc; - - auto maybeSig = sorbet::sig_finder::findSignature(gs, method); - if (!maybeSig.has_value()) { - return make_pair(nullopt, methodLoc); - } - core::LocOffsets sigLoc = {maybeSig->sig.beginPos(), maybeSig->body.endPos()}; - - return make_pair(sigLoc, methodLoc); -} - -// Turns ruby_function_name__ to RubyFunctionName, -// so we can use it in a new module name -string snakeToCamelCase(string_view name) { - string res; - const auto originalSize = name.size(); - res.reserve(originalSize); - bool shouldCapitalize = true; - for (int i = 0; i < originalSize; i++) { - if (name.at(i) == '_') { - shouldCapitalize = true; - continue; - } - res += shouldCapitalize ? toupper(name[i]) : name[i]; - shouldCapitalize = false; - } - return res; -} - -optional getNewModuleName(const core::GlobalState &gs, const core::NameRef name) { - const auto moduleName = absl::StrCat(snakeToCamelCase(name.show(gs)), "Module"); - const auto nameNotExists = [&](auto moduleName) { - if (auto nameRef = gs.lookupNameUTF8(moduleName); !nameRef.exists()) { - // We're using root here, because newely defined module would be at the top level - return !core::Symbols::root().data(gs)->findMember(gs, nameRef).exists(); - } - return false; - }; - - // if there are no names like that, return the new module name - if (nameNotExists(moduleName)) { - return moduleName; - } - - // otherwise, try to augment it - for (int i = 1; i < 42; i++) { - const auto newName = absl::StrCat(moduleName, i); - if (nameNotExists(newName)) { - return newName; - } - } - - // bail, if we haven't found untaken name in 42 tries - return nullopt; -} - -class MethodCallSiteRenamer : public AbstractRenamer { -public: - MethodCallSiteRenamer(const core::GlobalState &gs, const LSPConfiguration &config, const string oldName, - const string newName) - : AbstractRenamer(gs, config, oldName, newName) { - const vector invalidNames = {"initialize", "call"}; - for (auto name : invalidNames) { - if (oldName == name) { - invalid = true; - error = fmt::format("The `{}` method cannot be moved to a module.", oldName); - return; - } - } - } - - ~MethodCallSiteRenamer() {} - void rename(unique_ptr &response, const core::SymbolRef originalSymbol) override { - if (invalid) { - return; - } - auto loc = response->getLoc(); - - // If we're renaming the exact same place twice, silently ignore it. We reach this condition when we find the - // same method send through multiple definitions (e.g. in the case of union types) - auto it = edits.find(loc); - if (it != edits.end()) { - return; - } - - auto source = loc.source(gs); - if (!source.has_value()) { - return; - } - string newsrc; - if (auto sendResp = response->isSend()) { - // if the call site is not trivial, don't attempt to rename - // the typecheck error will guide user how to fix it - for (auto dr = sendResp->dispatchResult.get(); dr != nullptr; dr = dr->secondary.get()) { - if (dr->main.method != originalSymbol.asMethodRef()) { - return; - } - } - - edits[sendResp->receiverLoc] = newName; - } - } - void addSymbol(const core::SymbolRef symbol) override { - if (!symbol.isMethod()) { - return; - } - addSubclassRelatedMethods(gs, symbol.asMethodRef(), getQueue()); - } -}; // MethodCallSiteRenamer - -vector> moveMethod(const LSPConfiguration &config, const core::GlobalState &gs, - const core::lsp::MethodDefResponse &definition, - LSPTypecheckerInterface &typechecker, string_view newModuleName) { - auto moduleStart = fmt::format("module {}{}\n ", newModuleName, isTSigRequired(gs) ? "\n extend T::Sig" : ""); - auto moduleEnd = "\nend"; - - auto fref = definition.termLoc.file(); - - auto trees = typechecker.getResolved({fref}); - ENFORCE(!trees.empty()); - auto &rootTree = trees[0].tree; - - auto sigAndMethodLocs = methodLocs(gs, rootTree, definition.symbol, fref); - if (!sigAndMethodLocs.has_value()) { - return {}; - } - auto [maybeSigLoc, methodLoc] = sigAndMethodLocs.value(); - auto methodSourceLocOffset = maybeSigLoc.has_value() ? maybeSigLoc.value().join(methodLoc) : methodLoc; - auto methodSourceLoc = core::Loc(fref, methodSourceLocOffset); - auto methodSource = methodSourceLoc.source(gs); - if (!methodSource.has_value()) { - return {}; - } - - auto insertPosition = Range::fromLoc(gs, core::Loc(fref, rootTree.loc().copyWithZeroLength())); - auto newModuleSource = fmt::format("{}{}{}\n\n", moduleStart, methodSource.value(), moduleEnd); - - // This manipulations with the positions are required to remove leading tabs and whitespaces at the original method - // position - auto [oldMethodStart, oldMethodEnd] = methodSourceLoc.position(gs); - auto oldMethodLoc = core::Loc::fromDetails(gs, fref, {oldMethodStart.line, 0}, oldMethodEnd); - ENFORCE(oldMethodLoc.has_value()); - - vector> res; - res.emplace_back(make_unique(move(insertPosition), newModuleSource)); - res.emplace_back(make_unique(Range::fromLoc(gs, oldMethodLoc.value()), "")); - return res; -} } // namespace CodeActionTask::CodeActionTask(const LSPConfiguration &config, MessageId id, unique_ptr params) : LSPRequestTask(config, move(id), LSPMethod::TextDocumentCodeAction), params(move(params)) {} -vector> CodeActionTask::getMoveMethodEdits(const LSPConfiguration &config, - const core::GlobalState &gs, - const core::lsp::MethodDefResponse &definition, - LSPTypecheckerInterface &typechecker) { - vector> res; - auto newModuleName = getNewModuleName(gs, definition.name); - if (!newModuleName.has_value()) { - return res; - } - - vector> edits = moveMethod(config, gs, definition, typechecker, newModuleName.value()); - - auto renamer = make_shared(gs, config, definition.name.show(gs), newModuleName.value()); - getRenameEdits(typechecker, renamer, definition.symbol, newModuleName.value()); - auto callSiteEdits = renamer->buildTextDocumentEdits(); - - if (callSiteEdits.has_value()) { - for (auto &edit : callSiteEdits.value()) { - res.emplace_back(move(edit)); - } - } - auto docEdit = - make_unique(make_unique( - config.fileRef2Uri(gs, definition.termLoc.file()), JSONNullObject()), - move(edits)); - - res.emplace_back(move(docEdit)); - return res; -} - unique_ptr CodeActionTask::runRequest(LSPTypecheckerInterface &typechecker) { auto response = make_unique("2.0", id, LSPMethod::TextDocumentCodeAction); @@ -338,8 +126,8 @@ unique_ptr CodeActionTask::runRequest(LSPTypecheckerInterface & } if (config.opts.lspMoveMethodEnabled) { - auto queryResult = queryByLoc(typechecker, params->textDocument->uri, *params->range->start, - LSPMethod::TextDocumentCodeAction, false); + auto queryResult = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->range->start, + LSPMethod::TextDocumentCodeAction, false); // Generate "Move method" code actions only for class method definitions if (queryResult.error == nullptr) { diff --git a/main/lsp/requests/code_action.h b/main/lsp/requests/code_action.h index 2231c136f3c..08f6d9be9cd 100644 --- a/main/lsp/requests/code_action.h +++ b/main/lsp/requests/code_action.h @@ -15,12 +15,6 @@ class CodeActionTask final : public LSPRequestTask { std::unique_ptr runRequest(LSPTypecheckerInterface &typechecker) override; bool canUseStaleData() const override; - -private: - std::vector> getMoveMethodEdits(const LSPConfiguration &config, - const core::GlobalState &gs, - const core::lsp::MethodDefResponse &definition, - LSPTypecheckerInterface &typechecker); }; } // namespace sorbet::realmain::lsp diff --git a/main/lsp/requests/completion.cc b/main/lsp/requests/completion.cc index 7c43ca74393..4aacf19750f 100644 --- a/main/lsp/requests/completion.cc +++ b/main/lsp/requests/completion.cc @@ -9,6 +9,7 @@ #include "common/typecase.h" #include "core/lsp/QueryResponse.h" #include "main/lsp/FieldFinder.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/LocalVarFinder.h" #include "main/lsp/NextMethodFinder.h" #include "main/lsp/json_types.h" @@ -1159,7 +1160,7 @@ unique_ptr CompletionTask::runRequest(LSPTypecheckerInterface & response->result = std::move(emptyResult); return response; } - auto result = queryByLoc(typechecker, uri, pos, LSPMethod::TextDocumentCompletion); + auto result = LSPQuery::byLoc(config, typechecker, uri, pos, LSPMethod::TextDocumentCompletion); if (result.error) { // An error happened while setting up the query. diff --git a/main/lsp/requests/definition.cc b/main/lsp/requests/definition.cc index dd9f89a5d40..c2dbcefb80f 100644 --- a/main/lsp/requests/definition.cc +++ b/main/lsp/requests/definition.cc @@ -1,5 +1,6 @@ #include "main/lsp/requests/definition.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" using namespace std; @@ -12,8 +13,8 @@ DefinitionTask::DefinitionTask(const LSPConfiguration &config, MessageId id, unique_ptr DefinitionTask::runRequest(LSPTypecheckerInterface &typechecker) { auto response = make_unique("2.0", id, LSPMethod::TextDocumentDefinition); const core::GlobalState &gs = typechecker.state(); - auto result = - queryByLoc(typechecker, params->textDocument->uri, *params->position, LSPMethod::TextDocumentDefinition, false); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentDefinition, false); if (result.error) { // An error happened while setting up the query. response->error = move(result.error); diff --git a/main/lsp/requests/document_highlight.cc b/main/lsp/requests/document_highlight.cc index 745db6f8b61..b09dabefb3a 100644 --- a/main/lsp/requests/document_highlight.cc +++ b/main/lsp/requests/document_highlight.cc @@ -1,6 +1,7 @@ #include "main/lsp/requests/document_highlight.h" #include "absl/strings/match.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" using namespace std; @@ -36,8 +37,8 @@ unique_ptr DocumentHighlightTask::runRequest(LSPTypecheckerInte const core::GlobalState &gs = typechecker.state(); auto uri = params->textDocument->uri; - auto result = queryByLoc(typechecker, params->textDocument->uri, *params->position, - LSPMethod::TextDocumentDocumentHighlight, false); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentDocumentHighlight, false); if (result.error) { // An error happened while setting up the query. response->error = move(result.error); diff --git a/main/lsp/requests/hover.cc b/main/lsp/requests/hover.cc index 3502b73718b..0cf2a8c58bb 100644 --- a/main/lsp/requests/hover.cc +++ b/main/lsp/requests/hover.cc @@ -3,6 +3,7 @@ #include "absl/strings/str_join.h" #include "common/sort.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" #include "main/lsp/lsp.h" @@ -49,7 +50,8 @@ unique_ptr HoverTask::runRequest(LSPTypecheckerInterface &typec } const core::GlobalState &gs = typechecker.state(); - auto result = queryByLoc(typechecker, params->textDocument->uri, *params->position, LSPMethod::TextDocumentHover); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentHover); if (result.error) { // An error happened while setting up the query. response->error = move(result.error); diff --git a/main/lsp/requests/implementation.cc b/main/lsp/requests/implementation.cc index f932fec0d0a..27bf4951d91 100644 --- a/main/lsp/requests/implementation.cc +++ b/main/lsp/requests/implementation.cc @@ -1,5 +1,6 @@ #include "main/lsp/requests/implementation.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" #include "main/lsp/lsp.h" @@ -64,8 +65,8 @@ unique_ptr ImplementationTask::runRequest(LSPTypecheckerInterfa auto response = make_unique("2.0", id, LSPMethod::TextDocumentImplementation); const core::GlobalState &gs = typechecker.state(); - auto queryResult = - queryByLoc(typechecker, params->textDocument->uri, *params->position, LSPMethod::TextDocumentImplementation); + auto queryResult = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentImplementation); if (queryResult.error) { // An error happened while setting up the query. diff --git a/main/lsp/requests/prepare_rename.cc b/main/lsp/requests/prepare_rename.cc index d2903134a3e..9aed0c4cf69 100644 --- a/main/lsp/requests/prepare_rename.cc +++ b/main/lsp/requests/prepare_rename.cc @@ -1,6 +1,7 @@ #include "main/lsp/requests/prepare_rename.h" #include "absl/strings/match.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" #include "main/lsp/lsp.h" @@ -54,8 +55,8 @@ unique_ptr PrepareRenameTask::runRequest(LSPTypecheckerInterfac auto response = make_unique("2.0", id, LSPMethod::TextDocumentPrepareRename); prodCategoryCounterInc("lsp.messages.processed", "textDocument.prepareRename"); - auto result = queryByLoc(typechecker, params->textDocument->uri, *params->position, - LSPMethod::TextDocumentPrepareRename, false); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentPrepareRename, false); if (result.error) { // An error happened while setting up the query. response->error = move(result.error); diff --git a/main/lsp/requests/references.cc b/main/lsp/requests/references.cc index 73df0fc9da4..b472abf80ff 100644 --- a/main/lsp/requests/references.cc +++ b/main/lsp/requests/references.cc @@ -1,5 +1,6 @@ #include "main/lsp/requests/references.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/ShowOperation.h" #include "main/lsp/json_types.h" @@ -19,8 +20,8 @@ unique_ptr ReferencesTask::runRequest(LSPTypecheckerInterface & ShowOperation op(config, ShowOperation::Kind::References); const core::GlobalState &gs = typechecker.state(); - auto result = - queryByLoc(typechecker, params->textDocument->uri, *params->position, LSPMethod::TextDocumentReferences, false); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentReferences, false); if (result.error) { // An error happened while setting up the query. response->error = move(result.error); diff --git a/main/lsp/requests/rename.cc b/main/lsp/requests/rename.cc index 4e89ffa85e6..b9a5f75ebf5 100644 --- a/main/lsp/requests/rename.cc +++ b/main/lsp/requests/rename.cc @@ -5,6 +5,7 @@ #include "absl/strings/str_split.h" #include "core/lsp/QueryResponse.h" #include "main/lsp/AbstractRenamer.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/ShowOperation.h" #include "main/lsp/json_types.h" #include "main/lsp/lsp.h" @@ -217,7 +218,8 @@ unique_ptr RenameTask::runRequest(LSPTypecheckerInterface &type ShowOperation op(config, ShowOperation::Kind::Rename); - auto result = queryByLoc(typechecker, params->textDocument->uri, *params->position, LSPMethod::TextDocumentRename); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentRename); if (result.error) { // An error happened while setting up the query. response->error = move(result.error); @@ -244,20 +246,20 @@ unique_ptr RenameTask::runRequest(LSPTypecheckerInterface &type if (isValidRenameLocation(constResp->symbol, gs, response)) { auto originalName = constResp->symbol.name(gs).show(gs); renamer = makeRenamer(gs, config, constResp->symbol, params->newName); - getRenameEdits(typechecker, renamer, constResp->symbol, params->newName); + renamer->getRenameEdits(typechecker, constResp->symbol, params->newName); enrichResponse(response, renamer); } } else if (auto defResp = resp->isMethodDef()) { if (isValidRenameLocation(defResp->symbol, gs, response)) { renamer = makeRenamer(gs, config, defResp->symbol, params->newName); - getRenameEdits(typechecker, renamer, defResp->symbol, params->newName); + renamer->getRenameEdits(typechecker, defResp->symbol, params->newName); enrichResponse(response, renamer); } } else if (auto sendResp = resp->isSend()) { // We don't need to handle dispatchResult->secondary here, because it will be checked in getRenameEdits. auto method = sendResp->dispatchResult->main.method; renamer = makeRenamer(gs, config, method, params->newName); - getRenameEdits(typechecker, renamer, method, params->newName); + renamer->getRenameEdits(typechecker, method, params->newName); enrichResponse(response, renamer); } diff --git a/main/lsp/requests/signature_help.cc b/main/lsp/requests/signature_help.cc index b3664fc362d..313eef562fa 100644 --- a/main/lsp/requests/signature_help.cc +++ b/main/lsp/requests/signature_help.cc @@ -1,5 +1,6 @@ #include "main/lsp/requests/signature_help.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" #include "main/lsp/lsp.h" @@ -64,8 +65,8 @@ unique_ptr SignatureHelpTask::runRequest(LSPTypecheckerInterfac } const core::GlobalState &gs = typechecker.state(); - auto result = - queryByLoc(typechecker, params->textDocument->uri, *params->position, LSPMethod::TextDocumentSignatureHelp); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentSignatureHelp); if (result.error) { // An error happened while setting up the query. response->error = move(result.error); diff --git a/main/lsp/requests/sorbet_show_symbol.cc b/main/lsp/requests/sorbet_show_symbol.cc index bcecd503136..947351f2e4d 100644 --- a/main/lsp/requests/sorbet_show_symbol.cc +++ b/main/lsp/requests/sorbet_show_symbol.cc @@ -1,4 +1,5 @@ #include "main/lsp/requests/sorbet_show_symbol.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" #include "main/lsp/lsp.h" @@ -17,8 +18,8 @@ unique_ptr SorbetShowSymbolTask::runRequest(LSPTypecheckerInter // To match the behavior of Go To Definition, we don't error in an untyped file, but instead // be okay with returning an empty result for certain queries. auto errorIfFileIsUntyped = false; - auto result = queryByLoc(typechecker, params->textDocument->uri, *params->position, LSPMethod::SorbetShowSymbol, - errorIfFileIsUntyped); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::SorbetShowSymbol, errorIfFileIsUntyped); if (result.error) { // An error happened while setting up the query. response->error = move(result.error); diff --git a/main/lsp/requests/type_definition.cc b/main/lsp/requests/type_definition.cc index 3a7dcadef25..4f902ea7fc4 100644 --- a/main/lsp/requests/type_definition.cc +++ b/main/lsp/requests/type_definition.cc @@ -1,6 +1,7 @@ #include "main/lsp/requests/type_definition.h" #include "common/typecase.h" #include "core/lsp/QueryResponse.h" +#include "main/lsp/LSPQuery.h" #include "main/lsp/json_types.h" using namespace std; @@ -89,8 +90,8 @@ TypeDefinitionTask::TypeDefinitionTask(const LSPConfiguration &config, MessageId unique_ptr TypeDefinitionTask::runRequest(LSPTypecheckerInterface &typechecker) { auto response = make_unique("2.0", id, LSPMethod::TextDocumentTypeDefinition); const core::GlobalState &gs = typechecker.state(); - auto result = queryByLoc(typechecker, params->textDocument->uri, *params->position, - LSPMethod::TextDocumentTypeDefinition, false); + auto result = LSPQuery::byLoc(config, typechecker, params->textDocument->uri, *params->position, + LSPMethod::TextDocumentTypeDefinition, false); if (result.error) { // An error happened while setting up the query. response->error = move(result.error);