mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
LSP rename
This commit is contained in:
parent
b6f11b3392
commit
16b64f3aee
@ -159,6 +159,8 @@ set(sources
|
|||||||
lsp/FileRepository.h
|
lsp/FileRepository.h
|
||||||
lsp/GotoDefinition.cpp
|
lsp/GotoDefinition.cpp
|
||||||
lsp/GotoDefinition.h
|
lsp/GotoDefinition.h
|
||||||
|
lsp/RenameSymbol.cpp
|
||||||
|
lsp/RenameSymbol.h
|
||||||
lsp/HandlerBase.cpp
|
lsp/HandlerBase.cpp
|
||||||
lsp/HandlerBase.h
|
lsp/HandlerBase.h
|
||||||
lsp/LanguageServer.cpp
|
lsp/LanguageServer.cpp
|
||||||
|
@ -44,7 +44,6 @@ public:
|
|||||||
/// Changes the source identified by the LSP client path _uri to _text.
|
/// Changes the source identified by the LSP client path _uri to _text.
|
||||||
void setSourceByUri(std::string const& _uri, std::string _text);
|
void setSourceByUri(std::string const& _uri, std::string _text);
|
||||||
|
|
||||||
void addOrUpdateFile(boost::filesystem::path const& _path, frontend::SourceCode _source);
|
|
||||||
void setSourceUnits(StringMap _sources);
|
void setSourceUnits(StringMap _sources);
|
||||||
frontend::ReadCallback::Result readFile(std::string const& _kind, std::string const& _sourceUnitName);
|
frontend::ReadCallback::Result readFile(std::string const& _kind, std::string const& _sourceUnitName);
|
||||||
frontend::ReadCallback::Callback reader()
|
frontend::ReadCallback::Callback reader()
|
||||||
|
@ -45,8 +45,8 @@ public:
|
|||||||
/// from the JSON-RPC parameters.
|
/// from the JSON-RPC parameters.
|
||||||
std::pair<std::string, langutil::LineColumn> extractSourceUnitNameAndLineColumn(Json::Value const& _params) const;
|
std::pair<std::string, langutil::LineColumn> extractSourceUnitNameAndLineColumn(Json::Value const& _params) const;
|
||||||
|
|
||||||
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.charStreamProvider(); }
|
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.compilerStack(); }
|
||||||
FileRepository const& fileRepository() const noexcept { return m_server.fileRepository(); }
|
FileRepository& fileRepository() const noexcept { return m_server.fileRepository(); }
|
||||||
Transport& client() const noexcept { return m_server.client(); }
|
Transport& client() const noexcept { return m_server.client(); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
// LSP feature implementations
|
// LSP feature implementations
|
||||||
#include <libsolidity/lsp/GotoDefinition.h>
|
#include <libsolidity/lsp/GotoDefinition.h>
|
||||||
|
#include <libsolidity/lsp/RenameSymbol.h>
|
||||||
#include <libsolidity/lsp/SemanticTokensBuilder.h>
|
#include <libsolidity/lsp/SemanticTokensBuilder.h>
|
||||||
|
|
||||||
#include <liblangutil/SourceReferenceExtractor.h>
|
#include <liblangutil/SourceReferenceExtractor.h>
|
||||||
@ -124,6 +125,7 @@ LanguageServer::LanguageServer(Transport& _transport):
|
|||||||
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
|
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
|
||||||
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
|
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
|
||||||
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
|
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
|
||||||
|
{"textDocument/rename", RenameSymbol(*this) },
|
||||||
{"textDocument/implementation", GotoDefinition(*this) },
|
{"textDocument/implementation", GotoDefinition(*this) },
|
||||||
{"textDocument/semanticTokens/full", bind(&LanguageServer::semanticTokensFull, this, _1, _2)},
|
{"textDocument/semanticTokens/full", bind(&LanguageServer::semanticTokensFull, this, _1, _2)},
|
||||||
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
|
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
|
||||||
@ -314,6 +316,8 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
|
|||||||
replyArgs["capabilities"]["semanticTokensProvider"]["legend"] = semanticTokensLegend();
|
replyArgs["capabilities"]["semanticTokensProvider"]["legend"] = semanticTokensLegend();
|
||||||
replyArgs["capabilities"]["semanticTokensProvider"]["range"] = false;
|
replyArgs["capabilities"]["semanticTokensProvider"]["range"] = false;
|
||||||
replyArgs["capabilities"]["semanticTokensProvider"]["full"] = true; // XOR requests.full.delta = true
|
replyArgs["capabilities"]["semanticTokensProvider"]["full"] = true; // XOR requests.full.delta = true
|
||||||
|
replyArgs["capabilities"]["renameProvider"] = true;
|
||||||
|
|
||||||
|
|
||||||
m_client.reply(_id, move(replyArgs));
|
m_client.reply(_id, move(replyArgs));
|
||||||
}
|
}
|
||||||
@ -432,6 +436,7 @@ void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args)
|
|||||||
compileAndUpdateDiagnostics();
|
compileAndUpdateDiagnostics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ASTNode const* LanguageServer::astNodeAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos)
|
ASTNode const* LanguageServer::astNodeAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos)
|
||||||
{
|
{
|
||||||
if (m_compilerStack.state() < CompilerStack::AnalysisPerformed)
|
if (m_compilerStack.state() < CompilerStack::AnalysisPerformed)
|
||||||
|
@ -33,6 +33,7 @@
|
|||||||
namespace solidity::lsp
|
namespace solidity::lsp
|
||||||
{
|
{
|
||||||
|
|
||||||
|
class RenameSymbol;
|
||||||
enum class ErrorCode;
|
enum class ErrorCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -60,7 +61,7 @@ public:
|
|||||||
FileRepository& fileRepository() noexcept { return m_fileRepository; }
|
FileRepository& fileRepository() noexcept { return m_fileRepository; }
|
||||||
Transport& client() noexcept { return m_client; }
|
Transport& client() noexcept { return m_client; }
|
||||||
frontend::ASTNode const* astNodeAtSourceLocation(std::string const& _sourceUnitName, langutil::LineColumn const& _filePos);
|
frontend::ASTNode const* astNodeAtSourceLocation(std::string const& _sourceUnitName, langutil::LineColumn const& _filePos);
|
||||||
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_compilerStack; }
|
frontend::CompilerStack const& compilerStack() const noexcept { return m_compilerStack; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Checks if the server is initialized (to be used by messages that need it to be initialized).
|
/// Checks if the server is initialized (to be used by messages that need it to be initialized).
|
||||||
@ -72,6 +73,7 @@ private:
|
|||||||
void handleTextDocumentDidOpen(Json::Value const& _args);
|
void handleTextDocumentDidOpen(Json::Value const& _args);
|
||||||
void handleTextDocumentDidChange(Json::Value const& _args);
|
void handleTextDocumentDidChange(Json::Value const& _args);
|
||||||
void handleTextDocumentDidClose(Json::Value const& _args);
|
void handleTextDocumentDidClose(Json::Value const& _args);
|
||||||
|
void handleRename(Json::Value const& _args);
|
||||||
void handleGotoDefinition(MessageID _id, Json::Value const& _args);
|
void handleGotoDefinition(MessageID _id, Json::Value const& _args);
|
||||||
void semanticTokensFull(MessageID _id, Json::Value const& _args);
|
void semanticTokensFull(MessageID _id, Json::Value const& _args);
|
||||||
|
|
||||||
|
316
libsolidity/lsp/RenameSymbol.cpp
Normal file
316
libsolidity/lsp/RenameSymbol.cpp
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
|
#include <libsolidity/lsp/RenameSymbol.h>
|
||||||
|
#include <libsolidity/lsp/Utils.h>
|
||||||
|
|
||||||
|
#include <libyul/AST.h>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
using namespace solidity::frontend;
|
||||||
|
using namespace solidity::langutil;
|
||||||
|
using namespace solidity::lsp;
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
CallableDeclaration const* extractCallableDeclaration(FunctionCall const& _functionCall)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
auto const* functionType = dynamic_cast<FunctionType const*>(_functionCall.expression().annotation().type);
|
||||||
|
functionType && functionType->hasDeclaration()
|
||||||
|
)
|
||||||
|
if (auto const* functionDefinition = dynamic_cast<FunctionDefinition const*>(&functionType->declaration()))
|
||||||
|
return functionDefinition;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::operator()(MessageID _id, Json::Value const& _args)
|
||||||
|
{
|
||||||
|
auto const&& [sourceUnitName, lineColumn] = extractSourceUnitNameAndLineColumn(_args);
|
||||||
|
string const newName = _args["newName"].asString();
|
||||||
|
string const uri = _args["textDocument"]["uri"].asString();
|
||||||
|
|
||||||
|
ASTNode const* sourceNode = m_server.astNodeAtSourceLocation(sourceUnitName, lineColumn);
|
||||||
|
|
||||||
|
m_symbolName = {};
|
||||||
|
m_declarationToRename = nullptr;
|
||||||
|
m_sourceUnits = { &m_server.compilerStack().ast(sourceUnitName) };
|
||||||
|
m_locations.clear();
|
||||||
|
|
||||||
|
optional<int> cursorBytePosition = charStreamProvider()
|
||||||
|
.charStream(sourceUnitName)
|
||||||
|
.translateLineColumnToPosition(lineColumn);
|
||||||
|
solAssert(cursorBytePosition.has_value(), "Expected source pos");
|
||||||
|
|
||||||
|
extractNameAndDeclaration(*sourceNode, *cursorBytePosition);
|
||||||
|
|
||||||
|
// Find all source units using this symbol
|
||||||
|
for (auto const& [name, content]: fileRepository().sourceUnits())
|
||||||
|
{
|
||||||
|
auto const& sourceUnit = m_server.compilerStack().ast(name);
|
||||||
|
for (auto const* referencedSourceUnit: sourceUnit.referencedSourceUnits(true, util::convertContainer<set<SourceUnit const*>>(m_sourceUnits)))
|
||||||
|
if (*referencedSourceUnit->location().sourceName == sourceUnitName)
|
||||||
|
{
|
||||||
|
m_sourceUnits.insert(&sourceUnit);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Origin source unit should always be checked
|
||||||
|
m_sourceUnits.insert(&m_declarationToRename->sourceUnit());
|
||||||
|
|
||||||
|
Visitor visitor(*this);
|
||||||
|
|
||||||
|
for (auto const* sourceUnit: m_sourceUnits)
|
||||||
|
sourceUnit->accept(visitor);
|
||||||
|
|
||||||
|
// Apply changes in reverse order (will iterate in reverse)
|
||||||
|
sort(m_locations.begin(), m_locations.end());
|
||||||
|
|
||||||
|
Json::Value reply = Json::objectValue;
|
||||||
|
reply["changes"] = Json::objectValue;
|
||||||
|
|
||||||
|
Json::Value edits = Json::arrayValue;
|
||||||
|
|
||||||
|
for (auto i = m_locations.rbegin(); i != m_locations.rend(); i++)
|
||||||
|
{
|
||||||
|
solAssert(i->isValid());
|
||||||
|
|
||||||
|
// Replace in our file repository
|
||||||
|
string const uri = fileRepository().sourceUnitNameToUri(*i->sourceName);
|
||||||
|
string buffer = fileRepository().sourceUnits().at(*i->sourceName);
|
||||||
|
buffer.replace((size_t)i->start, (size_t)(i->end - i->start), newName);
|
||||||
|
fileRepository().setSourceByUri(uri, std::move(buffer));
|
||||||
|
|
||||||
|
Json::Value edit = Json::objectValue;
|
||||||
|
edit["range"] = toRange(*i);
|
||||||
|
edit["newText"] = newName;
|
||||||
|
|
||||||
|
// Record changes for the client
|
||||||
|
edits.append(edit);
|
||||||
|
if (i + 1 == m_locations.rend() || (i + 1)->sourceName != i->sourceName)
|
||||||
|
{
|
||||||
|
reply["changes"][uri] = edits;
|
||||||
|
edits = Json::arrayValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
client().reply(_id, reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::extractNameAndDeclaration(ASTNode const& _node, int _cursorBytePosition)
|
||||||
|
{
|
||||||
|
// Identify symbol name and node
|
||||||
|
if (auto const* declaration = dynamic_cast<Declaration const*>(&_node))
|
||||||
|
{
|
||||||
|
if (declaration->nameLocation().containsOffset(_cursorBytePosition))
|
||||||
|
{
|
||||||
|
m_symbolName = declaration->name();
|
||||||
|
m_declarationToRename = declaration;
|
||||||
|
}
|
||||||
|
else if (auto const* importDirective = dynamic_cast<ImportDirective const*>(declaration))
|
||||||
|
extractNameAndDeclaration(*importDirective, _cursorBytePosition);
|
||||||
|
}
|
||||||
|
else if (auto const* identifier = dynamic_cast<Identifier const*>(&_node))
|
||||||
|
{
|
||||||
|
if (auto const* declReference = dynamic_cast<Declaration const*>(identifier->annotation().referencedDeclaration))
|
||||||
|
{
|
||||||
|
m_symbolName = identifier->name();
|
||||||
|
m_declarationToRename = declReference;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (auto const* identifierPath = dynamic_cast<IdentifierPath const*>(&_node))
|
||||||
|
extractNameAndDeclaration(*identifierPath, _cursorBytePosition);
|
||||||
|
else if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(&_node))
|
||||||
|
{
|
||||||
|
m_symbolName = memberAccess->memberName();
|
||||||
|
m_declarationToRename = memberAccess->annotation().referencedDeclaration;
|
||||||
|
}
|
||||||
|
else if (auto const* functionCall = dynamic_cast<FunctionCall const*>(&_node))
|
||||||
|
extractNameAndDeclaration(*functionCall, _cursorBytePosition);
|
||||||
|
else if (auto const* inlineAssembly = dynamic_cast<InlineAssembly const*>(&_node))
|
||||||
|
extractNameAndDeclaration(*inlineAssembly, _cursorBytePosition);
|
||||||
|
else
|
||||||
|
solAssert(false, "Unexpected ASTNODE id: " + to_string(_node.id()));
|
||||||
|
|
||||||
|
lspDebug(fmt::format("Goal: rename '{}', loc: {}-{}", m_symbolName, m_declarationToRename->nameLocation().start, m_declarationToRename->nameLocation().end));
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::extractNameAndDeclaration(ImportDirective const& _importDirective, int _cursorBytePosition)
|
||||||
|
{
|
||||||
|
for (ImportDirective::SymbolAlias const& symbolAlias: _importDirective.symbolAliases())
|
||||||
|
if (symbolAlias.location.containsOffset(_cursorBytePosition))
|
||||||
|
{
|
||||||
|
solAssert(symbolAlias.alias);
|
||||||
|
m_symbolName = *symbolAlias.alias;
|
||||||
|
m_declarationToRename = symbolAlias.symbol->annotation().referencedDeclaration;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::Visitor::endVisit(ImportDirective const& _node)
|
||||||
|
{
|
||||||
|
// Handles SourceUnit aliases
|
||||||
|
if (handleGenericDeclaration(_node))
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (ImportDirective::SymbolAlias const& symbolAlias: _node.symbolAliases())
|
||||||
|
if (
|
||||||
|
symbolAlias.alias != nullptr &&
|
||||||
|
*symbolAlias.alias == m_outer.m_symbolName &&
|
||||||
|
symbolAlias.symbol->annotation().referencedDeclaration == m_outer.m_declarationToRename
|
||||||
|
)
|
||||||
|
m_outer.m_locations.emplace_back(symbolAlias.location);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::extractNameAndDeclaration(FunctionCall const& _functionCall, int _cursorBytePosition)
|
||||||
|
{
|
||||||
|
if (auto const* functionDefinition = extractCallableDeclaration(_functionCall))
|
||||||
|
for (size_t i = 0; i < _functionCall.names().size(); i++)
|
||||||
|
if (_functionCall.nameLocations()[i].containsOffset(_cursorBytePosition))
|
||||||
|
{
|
||||||
|
m_symbolName = *_functionCall.names()[i];
|
||||||
|
for (size_t j = 0; j < functionDefinition->parameters().size(); j++)
|
||||||
|
if (
|
||||||
|
functionDefinition->parameters()[j] &&
|
||||||
|
functionDefinition->parameters()[j]->name() == m_symbolName
|
||||||
|
)
|
||||||
|
m_declarationToRename = functionDefinition->parameters()[j].get();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::Visitor::endVisit(FunctionCall const& _node)
|
||||||
|
{
|
||||||
|
SourceLocation nameLocationInFunctionCall;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _node.names().size(); i++)
|
||||||
|
if (_node.names()[i] && *_node.names()[i] == m_outer.m_symbolName)
|
||||||
|
nameLocationInFunctionCall = _node.nameLocations()[i];
|
||||||
|
|
||||||
|
if (!nameLocationInFunctionCall.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (auto const* functionDefinition = extractCallableDeclaration(_node))
|
||||||
|
for (size_t j = 0; j < functionDefinition->parameters().size(); j++)
|
||||||
|
if (
|
||||||
|
functionDefinition->parameters()[j] &&
|
||||||
|
*functionDefinition->parameters()[j] == *m_outer.m_declarationToRename
|
||||||
|
)
|
||||||
|
m_outer.m_locations.emplace_back(nameLocationInFunctionCall);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::Visitor::endVisit(MemberAccess const& _node)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
m_outer.m_symbolName == _node.memberName() &&
|
||||||
|
*m_outer.m_declarationToRename == *_node.annotation().referencedDeclaration
|
||||||
|
)
|
||||||
|
m_outer.m_locations.emplace_back(_node.memberLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::Visitor::endVisit(Identifier const& _node)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
m_outer.m_symbolName == _node.name() &&
|
||||||
|
*m_outer.m_declarationToRename == *_node.annotation().referencedDeclaration
|
||||||
|
)
|
||||||
|
m_outer.m_locations.emplace_back(_node.location());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::extractNameAndDeclaration(IdentifierPath const& _identifierPath, int _cursorBytePosition)
|
||||||
|
{
|
||||||
|
// iterate through the elements of the path to find the one the cursor is on
|
||||||
|
size_t numIdentifiers = _identifierPath.pathLocations().size();
|
||||||
|
for (size_t i = 0; i < numIdentifiers; i++)
|
||||||
|
{
|
||||||
|
auto& location = _identifierPath.pathLocations()[i];
|
||||||
|
|
||||||
|
if (location.containsOffset(_cursorBytePosition))
|
||||||
|
{
|
||||||
|
solAssert(_identifierPath.annotation().pathDeclarations.size() == numIdentifiers);
|
||||||
|
solAssert(_identifierPath.path().size() == numIdentifiers);
|
||||||
|
|
||||||
|
m_declarationToRename = _identifierPath.annotation().pathDeclarations[i];
|
||||||
|
m_symbolName = _identifierPath.path()[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::Visitor::endVisit(IdentifierPath const& _node)
|
||||||
|
{
|
||||||
|
std::vector<Declaration const*>& declarations = _node.annotation().pathDeclarations;
|
||||||
|
solAssert(declarations.size() == _node.path().size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < _node.path().size(); i++)
|
||||||
|
if (
|
||||||
|
_node.path()[i] == m_outer.m_symbolName &&
|
||||||
|
declarations[i] == m_outer.m_declarationToRename
|
||||||
|
)
|
||||||
|
m_outer.m_locations.emplace_back(_node.pathLocations()[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::extractNameAndDeclaration(InlineAssembly const& _inlineAssembly, int _cursorBytePosition)
|
||||||
|
{
|
||||||
|
for (auto&& [identifier, externalReference]: _inlineAssembly.annotation().externalReferences)
|
||||||
|
{
|
||||||
|
SourceLocation location = yul::nativeLocationOf(*identifier);
|
||||||
|
location.end -= static_cast<int>(externalReference.suffix.size() + 1);
|
||||||
|
|
||||||
|
if (location.containsOffset(_cursorBytePosition))
|
||||||
|
{
|
||||||
|
m_declarationToRename = externalReference.declaration;
|
||||||
|
m_symbolName = identifier->name.str();
|
||||||
|
|
||||||
|
if (!externalReference.suffix.empty())
|
||||||
|
m_symbolName = m_symbolName.substr(0, m_symbolName.length() - externalReference.suffix.size() - 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenameSymbol::Visitor::endVisit(InlineAssembly const& _node)
|
||||||
|
{
|
||||||
|
for (auto&& [identifier, externalReference]: _node.annotation().externalReferences)
|
||||||
|
{
|
||||||
|
string identifierName = identifier->name.str();
|
||||||
|
if (!externalReference.suffix.empty())
|
||||||
|
identifierName = identifierName.substr(0, identifierName.length() - externalReference.suffix.size() - 1);
|
||||||
|
|
||||||
|
if (
|
||||||
|
externalReference.declaration == m_outer.m_declarationToRename &&
|
||||||
|
identifierName == m_outer.m_symbolName
|
||||||
|
)
|
||||||
|
{
|
||||||
|
SourceLocation location = yul::nativeLocationOf(*identifier);
|
||||||
|
location.end -= static_cast<int>(externalReference.suffix.size() + 1);
|
||||||
|
|
||||||
|
m_outer.m_locations.emplace_back(location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
119
libsolidity/lsp/RenameSymbol.h
Normal file
119
libsolidity/lsp/RenameSymbol.h
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
This file is part of solidity.
|
||||||
|
|
||||||
|
solidity is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
solidity is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
|
#include <libsolidity/lsp/HandlerBase.h>
|
||||||
|
#include <libsolidity/ast/AST.h>
|
||||||
|
#include <libsolidity/ast/ASTVisitor.h>
|
||||||
|
|
||||||
|
namespace solidity::lsp
|
||||||
|
{
|
||||||
|
|
||||||
|
class RenameSymbol: public HandlerBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit RenameSymbol(LanguageServer& _server): HandlerBase(_server) {}
|
||||||
|
|
||||||
|
void operator()(MessageID, Json::Value const&);
|
||||||
|
protected:
|
||||||
|
// Nested class because otherwise `RenameSymbol` couldn't be easily used
|
||||||
|
// with LanguageServer::m_handlers as `ASTConstVisitor` deletes required
|
||||||
|
// c'tors
|
||||||
|
struct Visitor: public frontend::ASTConstVisitor
|
||||||
|
{
|
||||||
|
explicit Visitor(RenameSymbol& _outer): m_outer(_outer) {}
|
||||||
|
void endVisit(frontend::ImportDirective const& _node) override;
|
||||||
|
void endVisit(frontend::MemberAccess const& _node) override;
|
||||||
|
void endVisit(frontend::Identifier const& _node) override;
|
||||||
|
void endVisit(frontend::IdentifierPath const& _node) override;
|
||||||
|
void endVisit(frontend::FunctionCall const& _node) override;
|
||||||
|
void endVisit(frontend::InlineAssembly const& _node) override;
|
||||||
|
|
||||||
|
void endVisit(frontend::ContractDefinition const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::StructDefinition const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::EnumDefinition const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::EnumValue const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::UserDefinedValueTypeDefinition const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::VariableDeclaration const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::FunctionDefinition const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::ModifierDefinition const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::EventDefinition const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
void endVisit(frontend::ErrorDefinition const& _node) override
|
||||||
|
{
|
||||||
|
handleGenericDeclaration(_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handleGenericDeclaration(frontend::Declaration const& _declaration)
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
m_outer.m_symbolName == _declaration.name() &&
|
||||||
|
*m_outer.m_declarationToRename == _declaration
|
||||||
|
)
|
||||||
|
{
|
||||||
|
m_outer.m_locations.emplace_back(_declaration.nameLocation());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenameSymbol& m_outer;
|
||||||
|
};
|
||||||
|
|
||||||
|
void extractNameAndDeclaration(frontend::ASTNode const& _node, int _cursorBytePosition);
|
||||||
|
void extractNameAndDeclaration(frontend::IdentifierPath const& _identifierPath, int _cursorBytePosition);
|
||||||
|
void extractNameAndDeclaration(frontend::ImportDirective const& _importDirective, int _cursorBytePosition);
|
||||||
|
void extractNameAndDeclaration(frontend::FunctionCall const& _functionCall, int _cursorBytePosition);
|
||||||
|
void extractNameAndDeclaration(frontend::InlineAssembly const& _inlineAssembly, int _cursorBytePosition);
|
||||||
|
|
||||||
|
// Node to rename
|
||||||
|
frontend::Declaration const* m_declarationToRename = nullptr;
|
||||||
|
// Original name
|
||||||
|
frontend::ASTString m_symbolName = {};
|
||||||
|
// SourceUnits to search & replace symbol in
|
||||||
|
std::set<frontend::SourceUnit const*, frontend::ASTNode::CompareByID> m_sourceUnits = {};
|
||||||
|
// Source locations that need to be replaced
|
||||||
|
std::vector<langutil::SourceLocation> m_locations = {};
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -195,7 +195,7 @@ void SemanticTokensBuilder::endVisit(frontend::StructuredDocumentation const& _d
|
|||||||
|
|
||||||
void SemanticTokensBuilder::endVisit(frontend::Identifier const& _identifier)
|
void SemanticTokensBuilder::endVisit(frontend::Identifier const& _identifier)
|
||||||
{
|
{
|
||||||
lspDebug(fmt::format("Identifier: {}, {}..{} cat={}", _identifier.name(), _identifier.location().start, _identifier.location().end, _identifier.annotation().type->category()));
|
//lspDebug(fmt::format("Identifier: {}, {}..{} cat={}", _identifier.name(), _identifier.location().start, _identifier.location().end, _identifier.annotation().type->category()));
|
||||||
|
|
||||||
SemanticTokenModifiers modifiers = SemanticTokenModifiers::None;
|
SemanticTokenModifiers modifiers = SemanticTokenModifiers::None;
|
||||||
if (_identifier.annotation().isConstant.set() && *_identifier.annotation().isConstant)
|
if (_identifier.annotation().isConstant.set() && *_identifier.annotation().isConstant)
|
||||||
|
307
test/libsolidity/lsp/rename/contract.sol
Normal file
307
test/libsolidity/lsp/rename/contract.sol
Normal file
@ -0,0 +1,307 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity >=0.8.0;
|
||||||
|
|
||||||
|
contract ToRename
|
||||||
|
// ^ @CursorOnContractDefinition
|
||||||
|
// ^^^^^^^^ @ContractInDefinition
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
contract User
|
||||||
|
// ^^^^ @UserContractInContractTest
|
||||||
|
{
|
||||||
|
ToRename public publicVariable;
|
||||||
|
// ^^^^^^^^ @ContractInPublicVariable
|
||||||
|
// ^ @CursorOnPublicVariableType
|
||||||
|
|
||||||
|
ToRename[10] previousContracts;
|
||||||
|
// ^^^^^^^^ @ContractInArrayType
|
||||||
|
// ^ @CursorOnArrayType
|
||||||
|
|
||||||
|
mapping(int => ToRename) contractMapping;
|
||||||
|
// ^^^^^^^^ @ContractInMapping
|
||||||
|
// ^ @CursorOnMapping
|
||||||
|
|
||||||
|
function getContract() public returns (ToRename)
|
||||||
|
// ^^^^^^^^ @ContractInReturnParameter
|
||||||
|
// ^ @CursorOnReturnParameter
|
||||||
|
{
|
||||||
|
return new ToRename();
|
||||||
|
// ^^^^^^^^ @ContractInReturnExpression
|
||||||
|
// ^ @CursorOnReturnExpression
|
||||||
|
}
|
||||||
|
|
||||||
|
function setContract(ToRename _contract) public
|
||||||
|
// ^^^^^^^^ @ContractInParameter
|
||||||
|
// ^ @CursorOnParameter
|
||||||
|
{
|
||||||
|
publicVariable = _contract;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnContractDefinition
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnReturnParameter
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnReturnExpression
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnPublicVariableType
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnArrayType
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnMapping
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnParameter
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
194
test/libsolidity/lsp/rename/function.sol
Normal file
194
test/libsolidity/lsp/rename/function.sol
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity >=0.8.0;
|
||||||
|
|
||||||
|
contract C
|
||||||
|
{
|
||||||
|
function renameMe() public pure returns (int)
|
||||||
|
// ^^^^^^^^ @FunctionInDefinition
|
||||||
|
// ^ @CursorInDefinition
|
||||||
|
{
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function other() public view
|
||||||
|
{
|
||||||
|
renameMe();
|
||||||
|
// ^^^^^^^^ @FunctionInFunctionSameContract
|
||||||
|
// ^ @CursorInFunctionSameContract
|
||||||
|
this.renameMe();
|
||||||
|
// ^^^^^^^^ @FunctionInFunctionSameContractExternal
|
||||||
|
// ^ @CursorInFunctionSameContractExternal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract Other
|
||||||
|
{
|
||||||
|
C m_contract;
|
||||||
|
|
||||||
|
function other() public view
|
||||||
|
{
|
||||||
|
m_contract.renameMe();
|
||||||
|
// ^^^^^^^^ @FunctionInFunctionOtherContract
|
||||||
|
// ^ @CursorInFunctionOtherContract
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function free() pure
|
||||||
|
{
|
||||||
|
C local_contract;
|
||||||
|
local_contract.renameMe();
|
||||||
|
// ^^^^^^^^ @FunctionInFreeFunction
|
||||||
|
// ^ @CursorInFreeFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorInDefinition
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/function.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionOtherContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContractExternal
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorInFunctionOtherContract
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/function.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionOtherContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContractExternal
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorInFunctionSameContractExternal
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/function.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionOtherContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContractExternal
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorInFunctionSameContract
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/function.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionOtherContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContractExternal
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorInFreeFunction
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/function.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionOtherContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContractExternal
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInFunctionSameContract
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FunctionInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
202
test/libsolidity/lsp/rename/functionCall.sol
Normal file
202
test/libsolidity/lsp/rename/functionCall.sol
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity >=0.8.0;
|
||||||
|
|
||||||
|
contract C
|
||||||
|
{
|
||||||
|
function foo(int a, int b, int c) pure public returns(int)
|
||||||
|
// ^ @ParameterB
|
||||||
|
// ^ @ParameterA
|
||||||
|
// ^ @ParameterC
|
||||||
|
{
|
||||||
|
return a + b + c;
|
||||||
|
// ^ @ParameterBInFoo
|
||||||
|
// ^ @ParameterAInFoo
|
||||||
|
// ^ @ParameterCInFoo
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function bar() public view
|
||||||
|
{
|
||||||
|
this.foo({c:1, b:2, a:3});
|
||||||
|
// ^ @ParameterBInCall
|
||||||
|
// ^ @ParameterCInCall
|
||||||
|
// ^ @ParameterAInCall
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @ParameterA
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/functionCall.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterAInCall
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterAInFoo
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterA
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @ParameterAInCall
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/functionCall.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterAInCall
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterAInFoo
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterA
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @ParameterAInFoo
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/functionCall.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterAInCall
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterAInFoo
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterA
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @ParameterC
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/functionCall.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterCInCall
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterCInFoo
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterC
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @ParameterCInCall
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/functionCall.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterCInCall
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterCInFoo
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterC
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @ParameterCInFoo
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/functionCall.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterCInCall
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterCInFoo
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterC
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @ParameterBInCall
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/functionCall.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterBInCall
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterBInFoo
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterB
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @ParameterBInFoo
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/functionCall.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterBInCall
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterBInFoo
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ParameterB
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
247
test/libsolidity/lsp/rename/import_directive.sol
Normal file
247
test/libsolidity/lsp/rename/import_directive.sol
Normal file
@ -0,0 +1,247 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity >=0.8.0;
|
||||||
|
|
||||||
|
import "./contract.sol" as externalFile;
|
||||||
|
// ^^^^^^^^^^^^ @FileAliasInImportDirective
|
||||||
|
// ^ @CursorOnFileAliasInImportDirective
|
||||||
|
import {ToRename as ExternalContract, User} from "./contract.sol";
|
||||||
|
// ^^^^^^^^^^^^^^^^ @RenamedContractInImportDirective
|
||||||
|
// ^ @CursorOnRenamedContractInImportDirective
|
||||||
|
// ^^^^^^^^ @OriginalNameInImportDirective
|
||||||
|
// ^ @CursorOnOriginalNameInImportDirective
|
||||||
|
// ^^^^ @UserInImportDirective
|
||||||
|
// ^ @CursorOnUserInImportDirective
|
||||||
|
|
||||||
|
contract C
|
||||||
|
{
|
||||||
|
ExternalContract public externalContract;
|
||||||
|
// ^^^^^^^^^^^^^^^^ @RenamedContractInPublicVariable
|
||||||
|
// ^ @CursorOnRenamedContractInPublicVariable
|
||||||
|
externalFile.ToRename public externalFileContract;
|
||||||
|
// ^^^^^^^^^^^^ @FileAliasInPublicVariable
|
||||||
|
// ^ @CursorOnFileAliasInPublicVariable
|
||||||
|
// ^^^^^^^^ @OriginalNameInPublicVariable
|
||||||
|
// ^ @CursorOnOriginalNameInPublicVariable
|
||||||
|
User public externalUserContract;
|
||||||
|
// ^^^^ @UserInPublicVariable
|
||||||
|
// ^ @CursorOnUserInPublicVariable
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
// contract:
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnFileAliasInImportDirective
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/import_directive.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FileAliasInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FileAliasInImportDirective
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnRenamedContractInImportDirective
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/import_directive.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @RenamedContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @RenamedContractInImportDirective
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnOriginalNameInImportDirective
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "rename/import_directive.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @OriginalNameInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @OriginalNameInImportDirective
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnRenamedContractInPublicVariable
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/import_directive.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @RenamedContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @RenamedContractInImportDirective
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnFileAliasInPublicVariable
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/import_directive.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FileAliasInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @FileAliasInImportDirective
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnOriginalNameInPublicVariable
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnExpression
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInReturnParameter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInMapping
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInArrayType
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @ContractInDefinition
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "rename/import_directive.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @OriginalNameInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @OriginalNameInImportDirective
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnUserInPublicVariable
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @UserContractInContractTest
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "rename/import_directive.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @UserInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @UserInImportDirective
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnUserInImportDirective
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/contract.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @UserContractInContractTest
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "rename/import_directive.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @UserInPublicVariable
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @UserInImportDirective
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
132
test/libsolidity/lsp/rename/variable.sol
Normal file
132
test/libsolidity/lsp/rename/variable.sol
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity >=0.8.0;
|
||||||
|
|
||||||
|
contract C
|
||||||
|
{
|
||||||
|
int public renameMe;
|
||||||
|
// ^^^^^^^^ @VariableInDefinition
|
||||||
|
// ^ @CursorOnVariableDefinition
|
||||||
|
|
||||||
|
function foo() public returns(int)
|
||||||
|
{
|
||||||
|
renameMe = 1;
|
||||||
|
// ^^^^^^^^ @VariableInFunction
|
||||||
|
// ^ @CursorOnVariableInFunction
|
||||||
|
return this.renameMe();
|
||||||
|
// ^^^^^^^^ @VariableInGetter
|
||||||
|
// ^ @CursorOnVariableInGetter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function freeFunction(C _contract) view returns(int)
|
||||||
|
{
|
||||||
|
return _contract.renameMe();
|
||||||
|
// ^^^^^^^^ @VariableInFreeFunction
|
||||||
|
// ^ @CursorOnVariableInFreeFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnVariableInFunction
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/variable.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInGetter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnVariableDefinition
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/variable.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInGetter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnVariableInGetter
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/variable.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInGetter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// -> textDocument/rename {
|
||||||
|
// "newName": "Renamed",
|
||||||
|
// "position": @CursorOnVariableInFreeFunction
|
||||||
|
// }
|
||||||
|
// <- {
|
||||||
|
// "changes": {
|
||||||
|
// "rename/variable.sol": [
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInFreeFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInGetter
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInFunction
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// "newText": "Renamed",
|
||||||
|
// "range": @VariableInDefinition
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
// }
|
160
test/lsp.py
160
test/lsp.py
@ -15,7 +15,7 @@ from copy import deepcopy
|
|||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from itertools import islice
|
from itertools import islice
|
||||||
from pathlib import PurePath
|
from pathlib import PurePath
|
||||||
from typing import Any, List, Optional, Tuple, Union
|
from typing import Any, List, Optional, Tuple, Union, NewType
|
||||||
|
|
||||||
import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows.
|
import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows.
|
||||||
from deepdiff import DeepDiff
|
from deepdiff import DeepDiff
|
||||||
@ -30,6 +30,13 @@ else:
|
|||||||
tty.setcbreak(sys.stdin.fileno())
|
tty.setcbreak(sys.stdin.fileno())
|
||||||
|
|
||||||
|
|
||||||
|
# Type for the pure test name without .sol suffix or sub directory
|
||||||
|
TestName = NewType("TestName", str)
|
||||||
|
|
||||||
|
# Type for the test path, e.g. subdir/mytest.sol
|
||||||
|
RelativeTestPath = NewType("RelativeTestPath", str)
|
||||||
|
|
||||||
|
|
||||||
def escape_string(text: str) -> str:
|
def escape_string(text: str) -> str:
|
||||||
"""
|
"""
|
||||||
Trivially escapes given input string's \r \n and \\.
|
Trivially escapes given input string's \r \n and \\.
|
||||||
@ -148,11 +155,13 @@ class JsonRpcProcess:
|
|||||||
exe_args: List[str]
|
exe_args: List[str]
|
||||||
process: subprocess.Popen
|
process: subprocess.Popen
|
||||||
trace_io: bool
|
trace_io: bool
|
||||||
|
print_pid: bool
|
||||||
|
|
||||||
def __init__(self, exe_path: str, exe_args: List[str], trace_io: bool = True):
|
def __init__(self, exe_path: str, exe_args: List[str], trace_io: bool = True, print_pid = False):
|
||||||
self.exe_path = exe_path
|
self.exe_path = exe_path
|
||||||
self.exe_args = exe_args
|
self.exe_args = exe_args
|
||||||
self.trace_io = trace_io
|
self.trace_io = trace_io
|
||||||
|
self.print_pid = print_pid
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
self.process = subprocess.Popen(
|
self.process = subprocess.Popen(
|
||||||
@ -161,6 +170,10 @@ class JsonRpcProcess:
|
|||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE
|
stderr=subprocess.PIPE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if self.print_pid:
|
||||||
|
print(f"solc pid: {self.process.pid}. Attach with sudo gdb -p {self.process.pid}")
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, exception_type, exception_value, traceback) -> None:
|
def __exit__(self, exception_type, exception_value, traceback) -> None:
|
||||||
@ -285,6 +298,13 @@ def create_cli_parser() -> argparse.ArgumentParser:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Prevent interactive queries and just fail instead."
|
help="Prevent interactive queries and just fail instead."
|
||||||
)
|
)
|
||||||
|
parser.set_defaults(print_solc_pid=False)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p", "--print-solc-pid",
|
||||||
|
dest="print_solc_pid",
|
||||||
|
action="store_true",
|
||||||
|
help="Print pid of each started solc for debugging purposes."
|
||||||
|
)
|
||||||
parser.set_defaults(trace_io=False)
|
parser.set_defaults(trace_io=False)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-T", "--trace-io",
|
"-T", "--trace-io",
|
||||||
@ -347,10 +367,13 @@ class TestParser:
|
|||||||
|
|
||||||
parsed_testcases = TestParser(content).parse()
|
parsed_testcases = TestParser(content).parse()
|
||||||
|
|
||||||
# First diagnostics are yielded
|
# First diagnostics are yielded.
|
||||||
|
# Type is "TestParser.Diagnostics"
|
||||||
expected_diagnostics = next(parsed_testcases)
|
expected_diagnostics = next(parsed_testcases)
|
||||||
|
|
||||||
...
|
...
|
||||||
# Now each request/response pair in the test definition
|
# Now each request/response pair in the test definition
|
||||||
|
# Type is "TestParser.RequestAndResponse"
|
||||||
for testcase in self.parsed_testcases:
|
for testcase in self.parsed_testcases:
|
||||||
...
|
...
|
||||||
"""
|
"""
|
||||||
@ -393,11 +416,11 @@ class TestParser:
|
|||||||
yield self.parseDiagnostics()
|
yield self.parseDiagnostics()
|
||||||
|
|
||||||
while not self.at_end():
|
while not self.at_end():
|
||||||
yield self.RequestAndResponse(**self.parseRequestAndResponse())
|
yield self.parseRequestAndResponse()
|
||||||
self.next_line()
|
self.next_line()
|
||||||
|
|
||||||
|
|
||||||
def parseDiagnostics(self):
|
def parseDiagnostics(self) -> Diagnostics:
|
||||||
"""
|
"""
|
||||||
Parse diagnostic expectations specified in the file.
|
Parse diagnostic expectations specified in the file.
|
||||||
Returns a named tuple instance of "Diagnostics"
|
Returns a named tuple instance of "Diagnostics"
|
||||||
@ -429,7 +452,7 @@ class TestParser:
|
|||||||
return self.Diagnostics(**diagnostics)
|
return self.Diagnostics(**diagnostics)
|
||||||
|
|
||||||
|
|
||||||
def parseRequestAndResponse(self):
|
def parseRequestAndResponse(self) -> RequestAndResponse:
|
||||||
RESPONSE_START = "// <- "
|
RESPONSE_START = "// <- "
|
||||||
REQUEST_END = "// }"
|
REQUEST_END = "// }"
|
||||||
COMMENT_PREFIX = "// "
|
COMMENT_PREFIX = "// "
|
||||||
@ -490,7 +513,7 @@ class TestParser:
|
|||||||
if self.at_end():
|
if self.at_end():
|
||||||
raise TestParserException(ret, "Response footer not found")
|
raise TestParserException(ret, "Response footer not found")
|
||||||
|
|
||||||
return ret
|
return self.RequestAndResponse(**ret)
|
||||||
|
|
||||||
def next_line(self):
|
def next_line(self):
|
||||||
self.current_line_tuple = next(self.lines, None)
|
self.current_line_tuple = next(self.lines, None)
|
||||||
@ -532,7 +555,7 @@ class FileTestRunner:
|
|||||||
self.solc = solc
|
self.solc = solc
|
||||||
self.open_tests = []
|
self.open_tests = []
|
||||||
self.content = self.suite.get_test_file_contents(self.test_name, self.sub_dir)
|
self.content = self.suite.get_test_file_contents(self.test_name, self.sub_dir)
|
||||||
self.markers = self.suite.get_file_tags(self.test_name, self.sub_dir)
|
self.markers = self.suite.get_test_tags(self.test_name, self.sub_dir)
|
||||||
self.parsed_testcases = None
|
self.parsed_testcases = None
|
||||||
self.expected_diagnostics = None
|
self.expected_diagnostics = None
|
||||||
|
|
||||||
@ -580,7 +603,7 @@ class FileTestRunner:
|
|||||||
len(expected_diagnostics),
|
len(expected_diagnostics),
|
||||||
description="Unexpected amount of diagnostics"
|
description="Unexpected amount of diagnostics"
|
||||||
)
|
)
|
||||||
markers = self.suite.get_file_tags(testname, sub_dir)
|
markers = self.suite.get_test_tags(testname, sub_dir)
|
||||||
for actual_diagnostic in diagnostics_per_file["diagnostics"]:
|
for actual_diagnostic in diagnostics_per_file["diagnostics"]:
|
||||||
expected_diagnostic = next((diagnostic for diagnostic in
|
expected_diagnostic = next((diagnostic for diagnostic in
|
||||||
expected_diagnostics if actual_diagnostic['range'] ==
|
expected_diagnostics if actual_diagnostic['range'] ==
|
||||||
@ -643,7 +666,13 @@ class FileTestRunner:
|
|||||||
finally:
|
finally:
|
||||||
self.close_all_open_files()
|
self.close_all_open_files()
|
||||||
|
|
||||||
def user_interaction_failed_method_test(self, testcase, actual, expected) -> TestResult:
|
def user_interaction_failed_method_test(
|
||||||
|
self,
|
||||||
|
testcase: TestParser.RequestAndResponse,
|
||||||
|
actual,
|
||||||
|
expected
|
||||||
|
) -> TestResult:
|
||||||
|
|
||||||
actual_pretty = self.suite.replace_ranges_with_tags(actual, self.sub_dir)
|
actual_pretty = self.suite.replace_ranges_with_tags(actual, self.sub_dir)
|
||||||
|
|
||||||
if expected is None:
|
if expected is None:
|
||||||
@ -688,16 +717,29 @@ class FileTestRunner:
|
|||||||
"""
|
"""
|
||||||
Runs the given testcase.
|
Runs the given testcase.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
requestBodyJson = self.parse_json_with_tags(testcase.request, self.markers)
|
requestBodyJson = self.parse_json_with_tags(testcase.request, self.markers)
|
||||||
# add textDocument/uri if missing
|
# add textDocument/uri if missing
|
||||||
if 'textDocument' not in requestBodyJson:
|
if 'textDocument' not in requestBodyJson:
|
||||||
requestBodyJson['textDocument'] = { 'uri': self.suite.get_test_file_uri(self.test_name, self.sub_dir) }
|
requestBodyJson['textDocument'] = { 'uri': self.suite.get_test_file_uri(self.test_name, self.sub_dir) }
|
||||||
|
|
||||||
actualResponseJson = self.solc.call_method(testcase.method, requestBodyJson)
|
actualResponseJson = self.solc.call_method(testcase.method, requestBodyJson)
|
||||||
|
|
||||||
# simplify response
|
# simplify response
|
||||||
for result in actualResponseJson["result"]:
|
if "result" in actualResponseJson:
|
||||||
if "uri" in result:
|
if isinstance(actualResponseJson["result"], list):
|
||||||
result["uri"] = result["uri"].replace(self.suite.project_root_uri + "/" + self.sub_dir + "/", "")
|
for result in actualResponseJson["result"]:
|
||||||
|
if "uri" in result:
|
||||||
|
result["uri"] = result["uri"].replace(self.suite.project_root_uri + "/" + self.sub_dir + "/", "")
|
||||||
|
|
||||||
|
elif isinstance(actualResponseJson["result"], dict):
|
||||||
|
if "changes" in actualResponseJson["result"]:
|
||||||
|
changes = actualResponseJson["result"]["changes"]
|
||||||
|
for key in list(changes.keys()):
|
||||||
|
new_key = key.replace(self.suite.project_root_uri + "/", "")
|
||||||
|
changes[new_key] = changes[key]
|
||||||
|
del changes[key]
|
||||||
|
|
||||||
if "jsonrpc" in actualResponseJson:
|
if "jsonrpc" in actualResponseJson:
|
||||||
actualResponseJson.pop("jsonrpc")
|
actualResponseJson.pop("jsonrpc")
|
||||||
|
|
||||||
@ -737,21 +779,39 @@ class FileTestRunner:
|
|||||||
if not isinstance(data, dict):
|
if not isinstance(data, dict):
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def findMarker(desired_tag):
|
||||||
|
if not isinstance(desired_tag, str):
|
||||||
|
return desired_tag
|
||||||
|
|
||||||
|
for tag, tagRange in markers.items():
|
||||||
|
if tag == desired_tag:
|
||||||
|
return tagRange
|
||||||
|
elif tag.lower() == desired_tag.lower():
|
||||||
|
raise Exception(f"Detected lower/upper case mismatch: Requested {desired_tag} but only found {tag}")
|
||||||
|
|
||||||
|
raise Exception(f"Marker {desired_tag} not found in file")
|
||||||
|
|
||||||
|
|
||||||
# Check if we need markers from a specific file
|
# Check if we need markers from a specific file
|
||||||
# Needs to be done before the loop or it might be called only after
|
# Needs to be done before the loop or it might be called only after
|
||||||
# we found "range" or "position"
|
# we found "range" or "position"
|
||||||
if "uri" in data:
|
if "uri" in data:
|
||||||
markers = self.suite.get_file_tags(data["uri"][:-len(".sol")], self.sub_dir)
|
markers = self.suite.get_test_tags(data["uri"][:-len(".sol")], self.sub_dir)
|
||||||
|
|
||||||
for key, val in data.items():
|
for key, val in data.items():
|
||||||
if key == "range":
|
if key == "range":
|
||||||
for tag, tagRange in markers.items():
|
data[key] = findMarker(val)
|
||||||
if tag == val:
|
|
||||||
data[key] = tagRange
|
|
||||||
elif key == "position":
|
elif key == "position":
|
||||||
for tag, tagRange in markers.items():
|
tag_range = findMarker(val)
|
||||||
if tag == val:
|
if "start" in tag_range:
|
||||||
data[key] = tagRange["start"]
|
data[key] = tag_range["start"]
|
||||||
|
elif key == "changes":
|
||||||
|
for path, list_of_changes in val.items():
|
||||||
|
test_name, file_sub_dir = split_path(path)
|
||||||
|
markers = self.suite.get_test_tags(test_name[:-len(".sol")], file_sub_dir)
|
||||||
|
for change in list_of_changes:
|
||||||
|
if "range" in change:
|
||||||
|
change["range"] = findMarker(change["range"])
|
||||||
elif isinstance(val, dict):
|
elif isinstance(val, dict):
|
||||||
replace_tag(val, markers)
|
replace_tag(val, markers)
|
||||||
elif isinstance(val, list):
|
elif isinstance(val, list):
|
||||||
@ -781,6 +841,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
self.test_pattern = args.test_pattern
|
self.test_pattern = args.test_pattern
|
||||||
self.fail_fast = args.fail_fast
|
self.fail_fast = args.fail_fast
|
||||||
self.non_interactive = args.non_interactive
|
self.non_interactive = args.non_interactive
|
||||||
|
self.print_solc_pid = args.print_solc_pid
|
||||||
|
|
||||||
print(f"{SGR_NOTICE}test pattern: {self.test_pattern}{SGR_RESET}")
|
print(f"{SGR_NOTICE}test pattern: {self.test_pattern}{SGR_RESET}")
|
||||||
|
|
||||||
@ -803,7 +864,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
title: str = test_fn.__name__[5:]
|
title: str = test_fn.__name__[5:]
|
||||||
print(f"{SGR_TEST_BEGIN}Testing {title} ...{SGR_RESET}")
|
print(f"{SGR_TEST_BEGIN}Testing {title} ...{SGR_RESET}")
|
||||||
try:
|
try:
|
||||||
with JsonRpcProcess(self.solc_path, ["--lsp"], trace_io=self.trace_io) as solc:
|
with JsonRpcProcess(self.solc_path, ["--lsp"], trace_io=self.trace_io, print_pid=self.print_solc_pid) as solc:
|
||||||
test_fn(solc)
|
test_fn(solc)
|
||||||
self.test_counter.passed += 1
|
self.test_counter.passed += 1
|
||||||
except ExpectationFailed:
|
except ExpectationFailed:
|
||||||
@ -1102,7 +1163,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
Find and return the tag that represents the requested range otherwise
|
Find and return the tag that represents the requested range otherwise
|
||||||
return None.
|
return None.
|
||||||
"""
|
"""
|
||||||
markers = self.get_file_tags(test, sub_dir)
|
markers = self.get_test_tags(test, sub_dir)
|
||||||
|
|
||||||
for tag, tag_range in markers.items():
|
for tag, tag_range in markers.items():
|
||||||
if tag_range == target_range:
|
if tag_range == target_range:
|
||||||
@ -1113,8 +1174,18 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
def replace_ranges_with_tags(self, content, sub_dir):
|
def replace_ranges_with_tags(self, content, sub_dir):
|
||||||
"""
|
"""
|
||||||
Replace matching ranges with "@<tagname>".
|
Replace matching ranges with "@<tagname>".
|
||||||
|
|
||||||
|
Recognized patterns:
|
||||||
|
{ "changes": { "<uri>": { "range": "<range>" } } }
|
||||||
|
{ "uri": "<uri>", "range": "<range> }
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def replace_range(item: dict, markers):
|
||||||
|
for tag, tagRange in markers.items():
|
||||||
|
if "range" in item and tagRange == item["range"]:
|
||||||
|
item["range"] = str(tag)
|
||||||
|
|
||||||
def recursive_iter(obj):
|
def recursive_iter(obj):
|
||||||
if isinstance(obj, dict):
|
if isinstance(obj, dict):
|
||||||
yield obj
|
yield obj
|
||||||
@ -1126,10 +1197,27 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
|
|
||||||
for item in recursive_iter(content):
|
for item in recursive_iter(content):
|
||||||
if "uri" in item and "range" in item:
|
if "uri" in item and "range" in item:
|
||||||
markers = self.get_file_tags(item["uri"][:-len(".sol")], sub_dir)
|
try:
|
||||||
for tag, tagRange in markers.items():
|
markers = self.get_test_tags(item["uri"][:-len(".sol")], sub_dir)
|
||||||
if tagRange == item["range"]:
|
replace_range(item, markers)
|
||||||
item["range"] = str(tag)
|
except FileNotFoundError:
|
||||||
|
# Skip over errors as this is user provided input that can
|
||||||
|
# point to non-existing files
|
||||||
|
pass
|
||||||
|
elif "changes" in item:
|
||||||
|
for file, changes_for_file in item["changes"].items():
|
||||||
|
test_name, file_sub_dir = split_path(file)
|
||||||
|
try:
|
||||||
|
markers = self.get_test_tags(test_name[:-len(".sol")], file_sub_dir)
|
||||||
|
for change in changes_for_file:
|
||||||
|
replace_range(change, markers)
|
||||||
|
|
||||||
|
except FileNotFoundError:
|
||||||
|
# Skip over errors as this is user provided input that can
|
||||||
|
# point to non-existing files
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Convert JSON to string and split it at the quoted tags
|
# Convert JSON to string and split it at the quoted tags
|
||||||
split_by_tag = TEST_REGEXES.findQuotedTag.split(json.dumps(content, indent=4, sort_keys=True))
|
split_by_tag = TEST_REGEXES.findQuotedTag.split(json.dumps(content, indent=4, sort_keys=True))
|
||||||
@ -1178,7 +1266,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
if user_response == "r":
|
if user_response == "r":
|
||||||
print("retrying...")
|
print("retrying...")
|
||||||
# pragma pylint: disable=no-member
|
# pragma pylint: disable=no-member
|
||||||
self.get_file_tags.cache_clear()
|
self.get_test_tags.cache_clear()
|
||||||
return False
|
return False
|
||||||
if user_response == "e":
|
if user_response == "e":
|
||||||
editor = os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi'))
|
editor = os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi'))
|
||||||
@ -1188,7 +1276,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
check=True
|
check=True
|
||||||
)
|
)
|
||||||
# pragma pylint: disable=no-member
|
# pragma pylint: disable=no-member
|
||||||
self.get_file_tags.cache_clear()
|
self.get_test_tags.cache_clear()
|
||||||
elif user_response == "s":
|
elif user_response == "s":
|
||||||
print("skipping...")
|
print("skipping...")
|
||||||
|
|
||||||
@ -1236,11 +1324,11 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
report = published_diagnostics[1]
|
report = published_diagnostics[1]
|
||||||
self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI")
|
self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI")
|
||||||
self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
|
self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
|
||||||
marker = self.get_file_tags("lib", "goto")["@diagnostics"]
|
marker = self.get_test_tags("lib", "goto")["@diagnostics"]
|
||||||
self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker)
|
self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker)
|
||||||
|
|
||||||
@functools.lru_cache() # pragma pylint: disable=lru-cache-decorating-method
|
@functools.lru_cache() # pragma pylint: disable=lru-cache-decorating-method
|
||||||
def get_file_tags(self, test_name: str, sub_dir=None, verbose=False):
|
def get_test_tags(self, test_name: TestName, sub_dir=None, verbose=False):
|
||||||
"""
|
"""
|
||||||
Finds all tags (e.g. @tagname) in the given test and returns them as a
|
Finds all tags (e.g. @tagname) in the given test and returns them as a
|
||||||
dictionary having the following structure: {
|
dictionary having the following structure: {
|
||||||
@ -1285,7 +1373,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
def test_didChange_in_A_causing_error_in_B(self, solc: JsonRpcProcess) -> None:
|
def test_didChange_in_A_causing_error_in_B(self, solc: JsonRpcProcess) -> None:
|
||||||
# Reusing another test but now change some file that generates an error in the other.
|
# Reusing another test but now change some file that generates an error in the other.
|
||||||
self.test_textDocument_didOpen_with_relative_import(solc)
|
self.test_textDocument_didOpen_with_relative_import(solc)
|
||||||
marker = self.get_file_tags("lib", "goto")["@addFunction"]
|
marker = self.get_test_tags("lib", "goto")["@addFunction"]
|
||||||
self.open_file_and_wait_for_diagnostics(solc, 'lib', "goto")
|
self.open_file_and_wait_for_diagnostics(solc, 'lib', "goto")
|
||||||
solc.send_message(
|
solc.send_message(
|
||||||
'textDocument/didChange',
|
'textDocument/didChange',
|
||||||
@ -1310,7 +1398,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
report = published_diagnostics[0]
|
report = published_diagnostics[0]
|
||||||
self.expect_equal(report['uri'], self.get_test_file_uri('didOpen_with_import'))
|
self.expect_equal(report['uri'], self.get_test_file_uri('didOpen_with_import'))
|
||||||
diagnostics = report['diagnostics']
|
diagnostics = report['diagnostics']
|
||||||
marker = self.get_file_tags("didOpen_with_import")["@diagnostics"]
|
marker = self.get_test_tags("didOpen_with_import")["@diagnostics"]
|
||||||
self.expect_equal(len(diagnostics), 1, "now, no diagnostics")
|
self.expect_equal(len(diagnostics), 1, "now, no diagnostics")
|
||||||
self.expect_diagnostic(diagnostics[0], code=9582, marker=marker)
|
self.expect_diagnostic(diagnostics[0], code=9582, marker=marker)
|
||||||
|
|
||||||
@ -1343,7 +1431,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI")
|
self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI")
|
||||||
self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
|
self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
|
||||||
|
|
||||||
markers = self.get_file_tags('lib', 'goto')
|
markers = self.get_test_tags('lib', 'goto')
|
||||||
marker = markers["@diagnostics"]
|
marker = markers["@diagnostics"]
|
||||||
self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker)
|
self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker)
|
||||||
|
|
||||||
@ -1400,7 +1488,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME, "goto"), "Correct file URI")
|
self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME, "goto"), "Correct file URI")
|
||||||
diagnostics = report['diagnostics']
|
diagnostics = report['diagnostics']
|
||||||
self.expect_equal(len(diagnostics), 3, "3 diagnostic messages")
|
self.expect_equal(len(diagnostics), 3, "3 diagnostic messages")
|
||||||
markers = self.get_file_tags(TEST_NAME, "goto")
|
markers = self.get_test_tags(TEST_NAME, "goto")
|
||||||
self.expect_diagnostic(diagnostics[0], code=6321, marker=markers["@unusedReturnVariable"])
|
self.expect_diagnostic(diagnostics[0], code=6321, marker=markers["@unusedReturnVariable"])
|
||||||
self.expect_diagnostic(diagnostics[1], code=2072, marker=markers["@unusedVariable"])
|
self.expect_diagnostic(diagnostics[1], code=2072, marker=markers["@unusedVariable"])
|
||||||
self.expect_diagnostic(diagnostics[2], code=2072, marker=markers["@unusedContractVariable"])
|
self.expect_diagnostic(diagnostics[2], code=2072, marker=markers["@unusedContractVariable"])
|
||||||
@ -1433,7 +1521,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
self.test_textDocument_didOpen_with_relative_import(solc)
|
self.test_textDocument_didOpen_with_relative_import(solc)
|
||||||
self.open_file_and_wait_for_diagnostics(solc, 'lib', 'goto')
|
self.open_file_and_wait_for_diagnostics(solc, 'lib', 'goto')
|
||||||
|
|
||||||
marker = self.get_file_tags('lib', 'goto')["@diagnostics"]
|
marker = self.get_test_tags('lib', 'goto')["@diagnostics"]
|
||||||
|
|
||||||
# lib.sol: Fix the unused variable message by removing it.
|
# lib.sol: Fix the unused variable message by removing it.
|
||||||
solc.send_message(
|
solc.send_message(
|
||||||
@ -1563,7 +1651,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
self.expect_equal(len(reports), 2, '')
|
self.expect_equal(len(reports), 2, '')
|
||||||
self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
|
self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
|
||||||
|
|
||||||
marker = self.get_file_tags("lib", 'goto')["@diagnostics"]
|
marker = self.get_test_tags("lib", 'goto')["@diagnostics"]
|
||||||
|
|
||||||
# unused variable in lib.sol
|
# unused variable in lib.sol
|
||||||
self.expect_diagnostic(reports[1]['diagnostics'][0], code=2072, marker=marker)
|
self.expect_diagnostic(reports[1]['diagnostics'][0], code=2072, marker=marker)
|
||||||
|
Loading…
Reference in New Issue
Block a user