mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
LSP: Implements goto-definition.
This commit is contained in:
parent
1035eacb53
commit
2b2f8acc12
@ -8,6 +8,7 @@ Compiler Features:
|
||||
* JSON-AST: Added selector field for errors and events.
|
||||
* Peephole Optimizer: Optimize comparisons in front of conditional jumps and conditional jumps across a single unconditional jump.
|
||||
* Yul Optimizer: Remove ``sstore`` and ``mstore`` operations that are never read from.
|
||||
* LSP: Implements goto-definition.
|
||||
|
||||
Bugfixes:
|
||||
* Yul IR Code Generation: Optimize embedded creation code with correct settings. This fixes potential mismatches between the constructor code of a contract compiled in isolation and the bytecode in ``type(C).creationCode``, resp. the bytecode used for ``new C(...)``.
|
||||
|
@ -155,10 +155,10 @@ set(sources
|
||||
interface/StorageLayout.h
|
||||
interface/Version.cpp
|
||||
interface/Version.h
|
||||
lsp/LanguageServer.cpp
|
||||
lsp/LanguageServer.h
|
||||
lsp/FileRepository.cpp
|
||||
lsp/FileRepository.h
|
||||
lsp/GotoDefinition.cpp
|
||||
lsp/GotoDefinition.h
|
||||
lsp/HandlerBase.cpp
|
||||
lsp/HandlerBase.h
|
||||
lsp/LanguageServer.cpp
|
||||
|
@ -18,12 +18,35 @@
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ASTUtils.h>
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
#include <libsolutil/Algorithms.h>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
ASTNode const* locateInnermostASTNode(int _offsetInFile, SourceUnit const& _sourceUnit)
|
||||
{
|
||||
ASTNode const* innermostMatch = nullptr;
|
||||
auto locator = SimpleASTVisitor(
|
||||
[&](ASTNode const& _node) -> bool
|
||||
{
|
||||
// In the AST parent location always covers the whole child location.
|
||||
// The parent is visited first so to get the innermost node we simply
|
||||
// take the last one that still contains the offset.
|
||||
|
||||
if (!_node.location().containsOffset(_offsetInFile))
|
||||
return false;
|
||||
|
||||
innermostMatch = &_node;
|
||||
return true;
|
||||
},
|
||||
[](ASTNode const&) {}
|
||||
);
|
||||
_sourceUnit.accept(locator);
|
||||
return innermostMatch;
|
||||
}
|
||||
|
||||
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
solAssert(_varDecl.isConstant(), "Constant variable expected");
|
||||
|
@ -21,9 +21,11 @@
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
class VariableDeclaration;
|
||||
class ASTNode;
|
||||
class Declaration;
|
||||
class Expression;
|
||||
class SourceUnit;
|
||||
class VariableDeclaration;
|
||||
|
||||
/// Find the topmost referenced constant variable declaration when the given variable
|
||||
/// declaration value is an identifier. Works only for constant variable declarations.
|
||||
@ -33,4 +35,7 @@ VariableDeclaration const* rootConstVariableDeclaration(VariableDeclaration cons
|
||||
/// Returns true if the constant variable declaration is recursive.
|
||||
bool isConstantVariableRecursive(VariableDeclaration const& _varDecl);
|
||||
|
||||
/// Returns the innermost AST node that covers the given location or nullptr if not found.
|
||||
ASTNode const* locateInnermostASTNode(int _offsetInFile, SourceUnit const& _sourceUnit);
|
||||
|
||||
}
|
||||
|
66
libsolidity/lsp/GotoDefinition.cpp
Normal file
66
libsolidity/lsp/GotoDefinition.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
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/GotoDefinition.h>
|
||||
#include <libsolidity/lsp/Transport.h> // for RequestError
|
||||
#include <libsolidity/lsp/Utils.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/ast/ASTUtils.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;
|
||||
|
||||
void GotoDefinition::operator()(MessageID _id, Json::Value const& _args)
|
||||
{
|
||||
auto const [sourceUnitName, lineColumn] = extractSourceUnitNameAndLineColumn(_args);
|
||||
|
||||
ASTNode const* sourceNode = m_server.astNodeAtSourceLocation(sourceUnitName, lineColumn);
|
||||
|
||||
vector<SourceLocation> locations;
|
||||
if (auto const* expression = dynamic_cast<Expression const*>(sourceNode))
|
||||
{
|
||||
// Handles all expressions that can have one or more declaration annotation.
|
||||
if (auto const* declaration = referencedDeclaration(expression))
|
||||
if (auto location = declarationLocation(declaration))
|
||||
locations.emplace_back(move(location.value()));
|
||||
}
|
||||
else if (auto const* identifierPath = dynamic_cast<IdentifierPath const*>(sourceNode))
|
||||
{
|
||||
if (auto const* declaration = identifierPath->annotation().referencedDeclaration)
|
||||
if (auto location = declarationLocation(declaration))
|
||||
locations.emplace_back(move(location.value()));
|
||||
}
|
||||
else if (auto const* importDirective = dynamic_cast<ImportDirective const*>(sourceNode))
|
||||
{
|
||||
auto const& path = *importDirective->annotation().absolutePath;
|
||||
if (fileRepository().sourceUnits().count(path))
|
||||
locations.emplace_back(SourceLocation{0, 0, make_shared<string const>(path)});
|
||||
}
|
||||
|
||||
Json::Value reply = Json::arrayValue;
|
||||
for (SourceLocation const& location: locations)
|
||||
reply.append(toJson(location));
|
||||
client().reply(_id, reply);
|
||||
}
|
31
libsolidity/lsp/GotoDefinition.h
Normal file
31
libsolidity/lsp/GotoDefinition.h
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
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>
|
||||
|
||||
namespace solidity::lsp
|
||||
{
|
||||
|
||||
class GotoDefinition: public HandlerBase
|
||||
{
|
||||
public:
|
||||
explicit GotoDefinition(LanguageServer& _server): HandlerBase(_server) {}
|
||||
|
||||
void operator()(MessageID, Json::Value const&);
|
||||
};
|
||||
|
||||
}
|
@ -1,3 +1,22 @@
|
||||
/*
|
||||
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 <libsolutil/Exceptions.h>
|
||||
|
||||
#include <libsolidity/lsp/HandlerBase.h>
|
||||
#include <libsolidity/lsp/LanguageServer.h>
|
||||
#include <libsolidity/lsp/Utils.h>
|
||||
@ -5,13 +24,13 @@
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::lsp;
|
||||
using namespace solidity::util;
|
||||
using namespace std;
|
||||
|
||||
namespace solidity::lsp
|
||||
{
|
||||
|
||||
using namespace langutil;
|
||||
|
||||
Json::Value HandlerBase::toRange(SourceLocation const& _location) const
|
||||
{
|
||||
if (!_location.hasText())
|
||||
@ -33,31 +52,27 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const
|
||||
return item;
|
||||
}
|
||||
|
||||
optional<SourceLocation> HandlerBase::parsePosition(string const& _sourceUnitName, Json::Value const& _position) const
|
||||
pair<string, LineColumn> HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const
|
||||
{
|
||||
if (!fileRepository().sourceUnits().count(_sourceUnitName))
|
||||
return nullopt;
|
||||
string const uri = _args["textDocument"]["uri"].asString();
|
||||
string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri);
|
||||
if (!fileRepository().sourceUnits().count(sourceUnitName))
|
||||
BOOST_THROW_EXCEPTION(
|
||||
RequestError(ErrorCode::RequestFailed) <<
|
||||
errinfo_comment("Unknown file: " + uri)
|
||||
);
|
||||
|
||||
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
|
||||
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
|
||||
fileRepository().sourceUnits().at(_sourceUnitName),
|
||||
*lineColumn
|
||||
auto const lineColumn = parseLineColumn(_args["position"]);
|
||||
if (!lineColumn)
|
||||
BOOST_THROW_EXCEPTION(
|
||||
RequestError(ErrorCode::RequestFailed) <<
|
||||
errinfo_comment(fmt::format(
|
||||
"Unknown position {line}:{column} in file: {file}",
|
||||
fmt::arg("line", lineColumn.value().line),
|
||||
fmt::arg("column", lineColumn.value().column),
|
||||
fmt::arg("file", sourceUnitName)
|
||||
))
|
||||
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<SourceLocation> HandlerBase::parseRange(string const& _sourceUnitName, Json::Value const& _range) const
|
||||
{
|
||||
if (!_range.isObject())
|
||||
return nullopt;
|
||||
optional<SourceLocation> start = parsePosition(_sourceUnitName, _range["start"]);
|
||||
optional<SourceLocation> end = parsePosition(_sourceUnitName, _range["end"]);
|
||||
if (!start || !end)
|
||||
return nullopt;
|
||||
solAssert(*start->sourceName == *end->sourceName);
|
||||
start->end = end->end;
|
||||
return start;
|
||||
}
|
||||
);
|
||||
|
||||
return {sourceUnitName, *lineColumn};
|
||||
}
|
||||
|
@ -1,3 +1,20 @@
|
||||
/*
|
||||
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
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/lsp/FileRepository.h>
|
||||
@ -24,22 +41,15 @@ public:
|
||||
Json::Value toRange(langutil::SourceLocation const& _location) const;
|
||||
Json::Value toJson(langutil::SourceLocation const& _location) const;
|
||||
|
||||
std::optional<langutil::SourceLocation> parsePosition(
|
||||
std::string const& _sourceUnitName,
|
||||
Json::Value const& _position
|
||||
) const;
|
||||
/// @returns source unit name and the line column position as extracted
|
||||
/// from the JSON-RPC parameters.
|
||||
std::pair<std::string, langutil::LineColumn> extractSourceUnitNameAndLineColumn(Json::Value const& _params) const;
|
||||
|
||||
/// @returns the source location given a source unit name and an LSP Range object,
|
||||
/// or nullopt on failure.
|
||||
std::optional<langutil::SourceLocation> parseRange(
|
||||
std::string const& _sourceUnitName,
|
||||
Json::Value const& _range
|
||||
) const;
|
||||
|
||||
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.charStreamProvider(); };
|
||||
FileRepository const& fileRepository() const noexcept { return m_server.fileRepository(); };
|
||||
Transport& client() const noexcept { return m_server.client(); };
|
||||
langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_server.charStreamProvider(); }
|
||||
FileRepository const& fileRepository() const noexcept { return m_server.fileRepository(); }
|
||||
Transport& client() const noexcept { return m_server.client(); }
|
||||
|
||||
protected:
|
||||
LanguageServer& m_server;
|
||||
};
|
||||
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include <libsolidity/lsp/HandlerBase.h>
|
||||
#include <libsolidity/lsp/Utils.h>
|
||||
|
||||
// LSP feature implementations
|
||||
#include <libsolidity/lsp/GotoDefinition.h>
|
||||
|
||||
#include <liblangutil/SourceReferenceExtractor.h>
|
||||
#include <liblangutil/CharStream.h>
|
||||
@ -75,9 +77,11 @@ LanguageServer::LanguageServer(Transport& _transport):
|
||||
{"initialize", bind(&LanguageServer::handleInitialize, this, _1, _2)},
|
||||
{"initialized", [](auto, auto) {}},
|
||||
{"shutdown", [this](auto, auto) { m_state = State::ShutdownRequested; }},
|
||||
{"textDocument/definition", GotoDefinition(*this) },
|
||||
{"textDocument/didOpen", bind(&LanguageServer::handleTextDocumentDidOpen, this, _2)},
|
||||
{"textDocument/didChange", bind(&LanguageServer::handleTextDocumentDidChange, this, _2)},
|
||||
{"textDocument/didClose", bind(&LanguageServer::handleTextDocumentDidClose, this, _2)},
|
||||
{"textDocument/implementation", GotoDefinition(*this) },
|
||||
{"workspace/didChangeConfiguration", bind(&LanguageServer::handleWorkspaceDidChangeConfiguration, this, _2)},
|
||||
},
|
||||
m_fileRepository("/" /* basePath */),
|
||||
@ -85,11 +89,6 @@ LanguageServer::LanguageServer(Transport& _transport):
|
||||
{
|
||||
}
|
||||
|
||||
optional<SourceLocation> LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range)
|
||||
{
|
||||
return HandlerBase{*this}.parseRange(_sourceUnitName, _range);
|
||||
}
|
||||
|
||||
Json::Value LanguageServer::toRange(SourceLocation const& _location)
|
||||
{
|
||||
return HandlerBase(*this).toRange(_location);
|
||||
@ -258,8 +257,10 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
|
||||
Json::Value replyArgs;
|
||||
replyArgs["serverInfo"]["name"] = "solc";
|
||||
replyArgs["serverInfo"]["version"] = string(VersionNumber);
|
||||
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true;
|
||||
replyArgs["capabilities"]["definitionProvider"] = true;
|
||||
replyArgs["capabilities"]["implementationProvider"] = true;
|
||||
replyArgs["capabilities"]["textDocumentSync"]["change"] = 2; // 0=none, 1=full, 2=incremental
|
||||
replyArgs["capabilities"]["textDocumentSync"]["openClose"] = true;
|
||||
|
||||
m_client.reply(_id, move(replyArgs));
|
||||
}
|
||||
@ -313,7 +314,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
|
||||
string text = jsonContentChange["text"].asString();
|
||||
if (jsonContentChange["range"].isObject()) // otherwise full content update
|
||||
{
|
||||
optional<SourceLocation> change = parseRange(sourceUnitName, jsonContentChange["range"]);
|
||||
optional<SourceLocation> change = parseRange(m_fileRepository, sourceUnitName, jsonContentChange["range"]);
|
||||
lspAssert(
|
||||
change && change->hasText(),
|
||||
ErrorCode::RequestFailed,
|
||||
@ -346,7 +347,7 @@ void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args)
|
||||
compileAndUpdateDiagnostics();
|
||||
}
|
||||
|
||||
ASTNode const* LanguageServer::requestASTNode(std::string const& _sourceUnitName, LineColumn const& _filePos)
|
||||
ASTNode const* LanguageServer::astNodeAtSourceLocation(std::string const& _sourceUnitName, LineColumn const& _filePos)
|
||||
{
|
||||
if (m_compilerStack.state() < CompilerStack::AnalysisPerformed)
|
||||
return nullptr;
|
||||
@ -354,11 +355,10 @@ ASTNode const* LanguageServer::requestASTNode(std::string const& _sourceUnitName
|
||||
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
|
||||
return nullptr;
|
||||
|
||||
optional<int> sourcePos = m_compilerStack.charStream(_sourceUnitName)
|
||||
.translateLineColumnToPosition(_filePos);
|
||||
if (!sourcePos.has_value())
|
||||
return nullptr;
|
||||
|
||||
if (optional<int> sourcePos =
|
||||
m_compilerStack.charStream(_sourceUnitName).translateLineColumnToPosition(_filePos))
|
||||
return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName));
|
||||
else
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ public:
|
||||
|
||||
FileRepository& fileRepository() noexcept { return m_fileRepository; }
|
||||
Transport& client() noexcept { return m_client; }
|
||||
frontend::ASTNode const* requestASTNode(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; }
|
||||
|
||||
private:
|
||||
@ -71,6 +71,7 @@ private:
|
||||
void handleTextDocumentDidOpen(Json::Value const& _args);
|
||||
void handleTextDocumentDidChange(Json::Value const& _args);
|
||||
void handleTextDocumentDidClose(Json::Value const& _args);
|
||||
void handleGotoDefinition(MessageID _id, Json::Value const& _args);
|
||||
|
||||
/// Invoked when the server user-supplied configuration changes (initiated by the client).
|
||||
void changeConfiguration(Json::Value const&);
|
||||
@ -79,12 +80,6 @@ private:
|
||||
void compile();
|
||||
using MessageHandler = std::function<void(MessageID, Json::Value const&)>;
|
||||
|
||||
/// @returns the source location given a source unit name and an LSP Range object,
|
||||
/// or nullopt on failure.
|
||||
std::optional<langutil::SourceLocation> parseRange(
|
||||
std::string const& _sourceUnitName,
|
||||
Json::Value const& _range
|
||||
);
|
||||
Json::Value toRange(langutil::SourceLocation const& _location);
|
||||
Json::Value toJson(langutil::SourceLocation const& _location);
|
||||
|
||||
|
@ -1,9 +1,30 @@
|
||||
/*
|
||||
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 <liblangutil/CharStreamProvider.h>
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/lsp/FileRepository.h>
|
||||
#include <libsolidity/lsp/Utils.h>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
|
||||
namespace solidity::lsp
|
||||
{
|
||||
|
||||
@ -19,7 +40,7 @@ optional<LineColumn> parseLineColumn(Json::Value const& _lineColumn)
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
Json::Value toJson(LineColumn _pos)
|
||||
Json::Value toJson(LineColumn const& _pos)
|
||||
{
|
||||
Json::Value json = Json::objectValue;
|
||||
json["line"] = max(_pos.line, 0);
|
||||
@ -36,24 +57,20 @@ Json::Value toJsonRange(LineColumn const& _start, LineColumn const& _end)
|
||||
return json;
|
||||
}
|
||||
|
||||
vector<Declaration const*> allAnnotatedDeclarations(Expression const* _expression)
|
||||
Declaration const* referencedDeclaration(Expression const* _expression)
|
||||
{
|
||||
vector<Declaration const*> output;
|
||||
|
||||
if (auto const* identifier = dynamic_cast<Identifier const*>(_expression))
|
||||
{
|
||||
output.push_back(identifier->annotation().referencedDeclaration);
|
||||
output += identifier->annotation().candidateDeclarations;
|
||||
}
|
||||
else if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(_expression))
|
||||
{
|
||||
output.push_back(memberAccess->annotation().referencedDeclaration);
|
||||
if (Declaration const* referencedDeclaration = identifier->annotation().referencedDeclaration)
|
||||
return referencedDeclaration;
|
||||
|
||||
if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(_expression))
|
||||
if (memberAccess->annotation().referencedDeclaration)
|
||||
return memberAccess->annotation().referencedDeclaration;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
optional<SourceLocation> declarationPosition(Declaration const* _declaration)
|
||||
optional<SourceLocation> declarationLocation(Declaration const* _declaration)
|
||||
{
|
||||
if (!_declaration)
|
||||
return nullopt;
|
||||
@ -67,4 +84,35 @@ optional<SourceLocation> declarationPosition(Declaration const* _declaration)
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<SourceLocation> parsePosition(
|
||||
FileRepository const& _fileRepository,
|
||||
string const& _sourceUnitName,
|
||||
Json::Value const& _position
|
||||
)
|
||||
{
|
||||
if (!_fileRepository.sourceUnits().count(_sourceUnitName))
|
||||
return nullopt;
|
||||
|
||||
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
|
||||
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
|
||||
_fileRepository.sourceUnits().at(_sourceUnitName),
|
||||
*lineColumn
|
||||
))
|
||||
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
optional<SourceLocation> parseRange(FileRepository const& _fileRepository, string const& _sourceUnitName, Json::Value const& _range)
|
||||
{
|
||||
if (!_range.isObject())
|
||||
return nullopt;
|
||||
optional<SourceLocation> start = parsePosition(_fileRepository, _sourceUnitName, _range["start"]);
|
||||
optional<SourceLocation> end = parsePosition(_fileRepository, _sourceUnitName, _range["end"]);
|
||||
if (!start || !end)
|
||||
return nullopt;
|
||||
solAssert(*start->sourceName == *end->sourceName);
|
||||
start->end = end->end;
|
||||
return start;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <liblangutil/SourceLocation.h>
|
||||
@ -9,6 +27,13 @@
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#if !defined(NDEBUG)
|
||||
#include <fstream>
|
||||
#define lspDebug(message) (std::ofstream("/tmp/solc.log", std::ios::app) << (message) << std::endl)
|
||||
#else
|
||||
#define lspDebug(message) do {} while (0)
|
||||
#endif
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
class CharStreamProvider;
|
||||
@ -20,10 +45,35 @@ namespace solidity::lsp
|
||||
class FileRepository;
|
||||
|
||||
std::optional<langutil::LineColumn> parseLineColumn(Json::Value const& _lineColumn);
|
||||
Json::Value toJson(langutil::LineColumn _pos);
|
||||
Json::Value toJson(langutil::LineColumn const& _pos);
|
||||
Json::Value toJsonRange(langutil::LineColumn const& _start, langutil::LineColumn const& _end);
|
||||
|
||||
std::vector<frontend::Declaration const*> allAnnotatedDeclarations(frontend::Expression const* _expression);
|
||||
std::optional<langutil::SourceLocation> declarationPosition(frontend::Declaration const* _declaration);
|
||||
/// @returns the source location given a source unit name and an LSP Range object,
|
||||
/// or nullopt on failure.
|
||||
std::optional<langutil::SourceLocation> parsePosition(
|
||||
FileRepository const& _fileRepository,
|
||||
std::string const& _sourceUnitName,
|
||||
Json::Value const& _position
|
||||
);
|
||||
|
||||
/// @returns the source location given a source unit name and an LSP Range object,
|
||||
/// or nullopt on failure.
|
||||
std::optional<langutil::SourceLocation> parseRange(
|
||||
FileRepository const& _fileRepository,
|
||||
std::string const& _sourceUnitName,
|
||||
Json::Value const& _range
|
||||
);
|
||||
|
||||
/// Extracts the resolved declaration of the given expression AST node.
|
||||
///
|
||||
/// This may for example be the type declaration of an identifier,
|
||||
/// or the type declaration of a structured member identifier.
|
||||
///
|
||||
/// @returns the resolved type declaration if found, or nullptr otherwise.
|
||||
frontend::Declaration const* referencedDeclaration(frontend::Expression const* _expression);
|
||||
|
||||
/// @returns the location of the declaration's name, if present, or the location of the complete
|
||||
/// declaration otherwise. If the input declaration is nullptr, std::nullopt is returned instead.
|
||||
std::optional<langutil::SourceLocation> declarationLocation(frontend::Declaration const* _declaration);
|
||||
|
||||
}
|
||||
|
68
test/libsolidity/lsp/goto_definition.sol
Normal file
68
test/libsolidity/lsp/goto_definition.sol
Normal file
@ -0,0 +1,68 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
import "./lib.sol";
|
||||
|
||||
interface I
|
||||
{
|
||||
function f(uint x) external returns (uint);
|
||||
}
|
||||
|
||||
contract IA is I
|
||||
{
|
||||
function f(uint x) public pure override returns (uint) { return x + 1; }
|
||||
}
|
||||
|
||||
contract IB is I
|
||||
{
|
||||
function f(uint x) public pure override returns (uint) { return x + 2; }
|
||||
}
|
||||
|
||||
library IntLib
|
||||
{
|
||||
function add(int self, int b) public pure returns (int) { return self + b; }
|
||||
}
|
||||
|
||||
contract C
|
||||
{
|
||||
I obj;
|
||||
function virtual_inheritance() public payable
|
||||
{
|
||||
obj = new IA();
|
||||
obj.f(1); // goto-definition should jump to definition of interface.
|
||||
}
|
||||
|
||||
using IntLib for *;
|
||||
function using_for(int i) pure public
|
||||
{
|
||||
i.add(5);
|
||||
14.add(4);
|
||||
}
|
||||
|
||||
function useLib(uint n) public payable returns (uint)
|
||||
{
|
||||
return Lib.add(n, 1);
|
||||
}
|
||||
|
||||
function enums(Color c) public pure returns (Color d)
|
||||
{
|
||||
Color e = Color.Red;
|
||||
if (c == e)
|
||||
d = Color.Green;
|
||||
else
|
||||
d = c;
|
||||
}
|
||||
|
||||
type Price is uint128;
|
||||
function udlTest() public pure returns (uint128)
|
||||
{
|
||||
Price p = Price.wrap(128);
|
||||
return Price.unwrap(p);
|
||||
}
|
||||
|
||||
function structCtorTest(uint8 v) public pure returns (uint8 result)
|
||||
{
|
||||
RGBColor memory c = RGBColor(v, 2 * v, 3 * v);
|
||||
result = c.red;
|
||||
}
|
||||
}
|
19
test/libsolidity/lsp/goto_definition_imports.sol
Normal file
19
test/libsolidity/lsp/goto_definition_imports.sol
Normal file
@ -0,0 +1,19 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
import {Weather as Wetter} from "./lib.sol";
|
||||
import "./lib.sol" as That;
|
||||
|
||||
contract C
|
||||
{
|
||||
function test_symbol_alias() public pure returns (Wetter result)
|
||||
{
|
||||
result = Wetter.Sunny;
|
||||
}
|
||||
|
||||
function test_library_alias() public pure returns (That.Color result)
|
||||
{
|
||||
That.Color color = That.Color.Red;
|
||||
result = color;
|
||||
}
|
||||
}
|
@ -1,6 +1,25 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity >=0.8.0;
|
||||
|
||||
/// Some Error type E.
|
||||
error E(uint, uint);
|
||||
|
||||
enum Weather {
|
||||
Sunny,
|
||||
Cloudy,
|
||||
Rainy
|
||||
}
|
||||
|
||||
/// Some custom Color enum type holding 3 colors.
|
||||
enum Color {
|
||||
/// Red color.
|
||||
Red,
|
||||
/// Green color.
|
||||
Green,
|
||||
/// Blue color.
|
||||
Blue
|
||||
}
|
||||
|
||||
library Lib
|
||||
{
|
||||
function add(uint a, uint b) public pure returns (uint result)
|
||||
@ -13,3 +32,10 @@ library Lib
|
||||
uint unused;
|
||||
}
|
||||
}
|
||||
|
||||
struct RGBColor
|
||||
{
|
||||
uint8 red;
|
||||
uint8 green;
|
||||
uint8 blue;
|
||||
}
|
||||
|
287
test/lsp.py
287
test/lsp.py
@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# pragma pylint: disable=too-many-lines
|
||||
import argparse
|
||||
import fnmatch
|
||||
import json
|
||||
@ -361,6 +361,52 @@ class SolidityLSPTestSuite: # {{{
|
||||
},
|
||||
"diagnostic: check range"
|
||||
)
|
||||
|
||||
def expect_location(
|
||||
self,
|
||||
obj: dict,
|
||||
uri: str,
|
||||
lineNo: int,
|
||||
startEndColumns: Tuple[int, int]
|
||||
):
|
||||
"""
|
||||
obj is an JSON object containing two keys:
|
||||
- 'uri': a string of the document URI
|
||||
- 'range': the location range, two child objects 'start' and 'end',
|
||||
each having a 'line' and 'character' integer value.
|
||||
"""
|
||||
[startColumn, endColumn] = startEndColumns
|
||||
self.expect_equal(obj['uri'], uri)
|
||||
self.expect_equal(obj['range']['start']['line'], lineNo)
|
||||
self.expect_equal(obj['range']['start']['character'], startColumn)
|
||||
self.expect_equal(obj['range']['end']['line'], lineNo)
|
||||
self.expect_equal(obj['range']['end']['character'], endColumn)
|
||||
|
||||
def expect_goto_definition_location(
|
||||
self,
|
||||
solc: JsonRpcProcess,
|
||||
document_uri: str,
|
||||
document_position: Tuple[int, int],
|
||||
expected_uri: str,
|
||||
expected_lineNo: int,
|
||||
expected_startEndColumns: Tuple[int, int],
|
||||
description: str
|
||||
):
|
||||
response = solc.call_method(
|
||||
'textDocument/definition',
|
||||
{
|
||||
'textDocument': {
|
||||
'uri': document_uri,
|
||||
},
|
||||
'position': {
|
||||
'line': document_position[0],
|
||||
'character': document_position[1]
|
||||
}
|
||||
}
|
||||
)
|
||||
message = "Goto definition (" + description + ")"
|
||||
self.expect_equal(len(response['result']), 1, message)
|
||||
self.expect_location(response['result'][0], expected_uri, expected_lineNo, expected_startEndColumns)
|
||||
# }}}
|
||||
|
||||
# {{{ actual tests
|
||||
@ -434,7 +480,7 @@ class SolidityLSPTestSuite: # {{{
|
||||
report = published_diagnostics[1]
|
||||
self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI")
|
||||
self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
|
||||
self.expect_diagnostic(report['diagnostics'][0], code=2072, lineNo=12, startEndColumns=(8, 19))
|
||||
self.expect_diagnostic(report['diagnostics'][0], code=2072, lineNo=31, startEndColumns=(8, 19))
|
||||
|
||||
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.
|
||||
@ -451,8 +497,8 @@ class SolidityLSPTestSuite: # {{{
|
||||
[
|
||||
{
|
||||
'range': {
|
||||
'start': { 'line': 5, 'character': 0 },
|
||||
'end': { 'line': 10, 'character': 0 }
|
||||
'start': { 'line': 24, 'character': 0 },
|
||||
'end': { 'line': 29, 'character': 0 }
|
||||
},
|
||||
'text': "" # deleting function `add`
|
||||
}
|
||||
@ -497,7 +543,7 @@ class SolidityLSPTestSuite: # {{{
|
||||
report = published_diagnostics[1]
|
||||
self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI")
|
||||
self.expect_equal(len(report['diagnostics']), 1, "one diagnostic")
|
||||
self.expect_diagnostic(report['diagnostics'][0], code=2072, lineNo=12, startEndColumns=(8, 19))
|
||||
self.expect_diagnostic(report['diagnostics'][0], code=2072, lineNo=31, startEndColumns=(8, 19))
|
||||
|
||||
def test_textDocument_didChange_updates_diagnostics(self, solc: JsonRpcProcess) -> None:
|
||||
self.setup_lsp(solc)
|
||||
@ -555,8 +601,8 @@ class SolidityLSPTestSuite: # {{{
|
||||
{
|
||||
'range':
|
||||
{
|
||||
'start': { 'line': 12, 'character': 1 },
|
||||
'end': { 'line': 13, 'character': 1 }
|
||||
'start': { 'line': 31, 'character': 1 },
|
||||
'end': { 'line': 32, 'character': 1 }
|
||||
},
|
||||
'text': ""
|
||||
}
|
||||
@ -673,7 +719,7 @@ class SolidityLSPTestSuite: # {{{
|
||||
reports = self.wait_for_diagnostics(solc, 2)
|
||||
self.expect_equal(len(reports), 2, '')
|
||||
self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics")
|
||||
self.expect_diagnostic(reports[1]['diagnostics'][0], 2072, 12, (8, 19)) # unused variable in lib.sol
|
||||
self.expect_diagnostic(reports[1]['diagnostics'][0], 2072, 31, (8, 19)) # unused variable in lib.sol
|
||||
|
||||
# Now close the file and expect the warning for lib.sol to be removed
|
||||
solc.send_message(
|
||||
@ -744,6 +790,231 @@ class SolidityLSPTestSuite: # {{{
|
||||
self.expect_equal(len(report3['diagnostics']), 1, "one diagnostic")
|
||||
self.expect_diagnostic(report3['diagnostics'][0], 4126, 6, (1, 23))
|
||||
|
||||
def test_textDocument_definition(self, solc: JsonRpcProcess) -> None:
|
||||
self.setup_lsp(solc)
|
||||
FILE_NAME = 'goto_definition'
|
||||
FILE_URI = self.get_test_file_uri(FILE_NAME)
|
||||
LIB_URI = self.get_test_file_uri('lib')
|
||||
solc.send_message('textDocument/didOpen', {
|
||||
'textDocument': {
|
||||
'uri': FILE_URI,
|
||||
'languageId': 'Solidity',
|
||||
'version': 1,
|
||||
'text': self.get_test_file_contents(FILE_NAME)
|
||||
}
|
||||
})
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 2)
|
||||
self.expect_equal(len(published_diagnostics), 2, "publish diagnostics for 2 files")
|
||||
self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0)
|
||||
self.expect_equal(len(published_diagnostics[1]['diagnostics']), 1)
|
||||
self.expect_diagnostic(published_diagnostics[1]['diagnostics'][0], 2072, 31, (8, 19)) # unused variable in lib.sol
|
||||
|
||||
# import directive
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(3, 9), # symbol `"./lib.sol"` in `import "./lib.sol"`
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=0,
|
||||
expected_startEndColumns=(0, 0),
|
||||
description="import directive"
|
||||
)
|
||||
|
||||
# type symbol to jump to type defs (error, contract, enum, ...)
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(30, 19), # symbol `IA` in `new IA()`
|
||||
expected_uri=FILE_URI,
|
||||
expected_lineNo=10,
|
||||
expected_startEndColumns=(9, 11),
|
||||
description="type symbol to jump to definition"
|
||||
)
|
||||
|
||||
# virtual function lookup?
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(31, 12), # symbol `f`, jumps to interface definition
|
||||
expected_uri=FILE_URI,
|
||||
expected_lineNo=7,
|
||||
expected_startEndColumns=(13, 14),
|
||||
description="virtual function lookup"
|
||||
)
|
||||
|
||||
# using for
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(37, 10), # symbol `add` in `i.add(5)`
|
||||
expected_uri=FILE_URI,
|
||||
expected_lineNo=22,
|
||||
expected_startEndColumns=(13, 16),
|
||||
description="using for"
|
||||
)
|
||||
|
||||
# library
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(43, 15), # symbol `Lib` in `Lib.add(n, 1)`
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=22,
|
||||
expected_startEndColumns=(8, 11),
|
||||
description="Library symbol from different file"
|
||||
)
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(43, 19), # symbol `add` in `Lib.add(n, 1)`
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=24,
|
||||
expected_startEndColumns=(13, 16),
|
||||
description="Library member symbol from different file"
|
||||
)
|
||||
|
||||
# enum type symbol and enum values
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(46, 19), # symbol `Color` in function signature's parameter
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=13,
|
||||
expected_startEndColumns=(5, 10),
|
||||
description="Enum type"
|
||||
)
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(48, 24), # symbol `Red` in `Color.Red`
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=15,
|
||||
expected_startEndColumns=(4, 7),
|
||||
description="Enum value"
|
||||
)
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(48, 24), # symbol `Red` in `Color.Red`
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=15,
|
||||
expected_startEndColumns=(4, 7),
|
||||
description="Enum value"
|
||||
)
|
||||
|
||||
# local variable declarations
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(49, 17), # symbol `e` in `(c == e)`
|
||||
expected_uri=FILE_URI,
|
||||
expected_lineNo=48,
|
||||
expected_startEndColumns=(14, 15),
|
||||
description="local variable declaration"
|
||||
)
|
||||
|
||||
# User defined type
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(58, 8), # symbol `Price` in `Price p ...`
|
||||
expected_uri=FILE_URI,
|
||||
expected_lineNo=55,
|
||||
expected_startEndColumns=(9, 14),
|
||||
description="User defined type on left hand side"
|
||||
)
|
||||
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(58, 18), # symbol `Price` in `Price.wrap()` expected_uri=FILE_URI,
|
||||
expected_uri=FILE_URI,
|
||||
expected_lineNo=55,
|
||||
expected_startEndColumns=(9, 14),
|
||||
description="User defined type on right hand side."
|
||||
)
|
||||
|
||||
# struct constructor also properly jumps to the struct's declaration.
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(64, 33), # symbol `RGBColor` right hand side expression.
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=35,
|
||||
expected_startEndColumns=(7, 15),
|
||||
description="Struct constructor."
|
||||
)
|
||||
|
||||
def test_textDocument_definition_imports(self, solc: JsonRpcProcess) -> None:
|
||||
self.setup_lsp(solc)
|
||||
FILE_NAME = 'goto_definition_imports'
|
||||
FILE_URI = self.get_test_file_uri(FILE_NAME)
|
||||
LIB_URI = self.get_test_file_uri('lib')
|
||||
solc.send_message('textDocument/didOpen', {
|
||||
'textDocument': {
|
||||
'uri': FILE_URI,
|
||||
'languageId': 'Solidity',
|
||||
'version': 1,
|
||||
'text': self.get_test_file_contents(FILE_NAME)
|
||||
}
|
||||
})
|
||||
published_diagnostics = self.wait_for_diagnostics(solc, 2)
|
||||
self.expect_equal(len(published_diagnostics), 2, "publish diagnostics for 2 files")
|
||||
self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0)
|
||||
self.expect_equal(len(published_diagnostics[1]['diagnostics']), 1)
|
||||
self.expect_diagnostic(published_diagnostics[1]['diagnostics'][0], 2072, 31, (8, 19)) # unused variable in lib.sol
|
||||
|
||||
# import directive: test symbol alias
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(3, 9), # in `Weather` of `import {Weather as Wetter} from "./lib.sol"`
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=6,
|
||||
expected_startEndColumns=(5, 12),
|
||||
description="goto definition of symbol in symbol alias import directive"
|
||||
)
|
||||
|
||||
# import directive: test symbol alias
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(8, 55), # `Wetter` in return type declaration
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=6,
|
||||
expected_startEndColumns=(5, 12),
|
||||
description="goto definition of symbol in symbol alias import directive"
|
||||
)
|
||||
|
||||
# That.Color tests with `That` being the aliased library to be imported.
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(13, 55), # `That` in return type declaration
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=13,
|
||||
expected_startEndColumns=(5, 10),
|
||||
description="goto definition of symbol in symbol alias import directive"
|
||||
)
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(15, 8),
|
||||
expected_uri=LIB_URI,
|
||||
expected_lineNo=13,
|
||||
expected_startEndColumns=(5, 10),
|
||||
description="`That` in LHS variable assignment"
|
||||
)
|
||||
self.expect_goto_definition_location(
|
||||
solc=solc,
|
||||
document_uri=FILE_URI,
|
||||
document_position=(15, 27),
|
||||
expected_uri=FILE_URI,
|
||||
expected_lineNo=4,
|
||||
expected_startEndColumns=(22, 26),
|
||||
description="`That` in expression"
|
||||
)
|
||||
|
||||
def test_textDocument_didChange_empty_file(self, solc: JsonRpcProcess) -> None:
|
||||
"""
|
||||
Starts with an empty file and changes it to look like
|
||||
|
Loading…
Reference in New Issue
Block a user