From 1035eacb531adcfc82f339cffd8d066047b0333d Mon Sep 17 00:00:00 2001 From: Christian Parpart Date: Wed, 5 Jan 2022 12:15:10 +0100 Subject: [PATCH] LSP: Introduce HandlerBase for future LSP-feature implementations. --- libsolidity/CMakeLists.txt | 6 ++ libsolidity/lsp/HandlerBase.cpp | 63 ++++++++++++++++++++ libsolidity/lsp/HandlerBase.h | 46 +++++++++++++++ libsolidity/lsp/LanguageServer.cpp | 93 +++++++++--------------------- libsolidity/lsp/LanguageServer.h | 17 +++--- libsolidity/lsp/Utils.cpp | 70 ++++++++++++++++++++++ libsolidity/lsp/Utils.h | 29 ++++++++++ 7 files changed, 249 insertions(+), 75 deletions(-) create mode 100644 libsolidity/lsp/HandlerBase.cpp create mode 100644 libsolidity/lsp/HandlerBase.h create mode 100644 libsolidity/lsp/Utils.cpp create mode 100644 libsolidity/lsp/Utils.h diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index ff82cd9fb..04a4300a3 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -159,8 +159,14 @@ set(sources lsp/LanguageServer.h lsp/FileRepository.cpp lsp/FileRepository.h + lsp/HandlerBase.cpp + lsp/HandlerBase.h + lsp/LanguageServer.cpp + lsp/LanguageServer.h lsp/Transport.cpp lsp/Transport.h + lsp/Utils.cpp + lsp/Utils.h parsing/DocStringParser.cpp parsing/DocStringParser.h parsing/Parser.cpp diff --git a/libsolidity/lsp/HandlerBase.cpp b/libsolidity/lsp/HandlerBase.cpp new file mode 100644 index 000000000..3ef227461 --- /dev/null +++ b/libsolidity/lsp/HandlerBase.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include + +#include + +using namespace std; + +namespace solidity::lsp +{ + +using namespace langutil; + +Json::Value HandlerBase::toRange(SourceLocation const& _location) const +{ + if (!_location.hasText()) + return toJsonRange({}, {}); + + solAssert(_location.sourceName, ""); + langutil::CharStream const& stream = charStreamProvider().charStream(*_location.sourceName); + LineColumn start = stream.translatePositionToLineColumn(_location.start); + LineColumn end = stream.translatePositionToLineColumn(_location.end); + return toJsonRange(start, end); +} + +Json::Value HandlerBase::toJson(SourceLocation const& _location) const +{ + solAssert(_location.sourceName); + Json::Value item = Json::objectValue; + item["uri"] = fileRepository().sourceUnitNameToClientPath(*_location.sourceName); + item["range"] = toRange(_location); + return item; +} + +optional HandlerBase::parsePosition(string const& _sourceUnitName, Json::Value const& _position) const +{ + if (!fileRepository().sourceUnits().count(_sourceUnitName)) + return nullopt; + + if (optional lineColumn = parseLineColumn(_position)) + if (optional const offset = CharStream::translateLineColumnToPosition( + fileRepository().sourceUnits().at(_sourceUnitName), + *lineColumn + )) + return SourceLocation{*offset, *offset, make_shared(_sourceUnitName)}; + return nullopt; +} + +optional HandlerBase::parseRange(string const& _sourceUnitName, Json::Value const& _range) const +{ + if (!_range.isObject()) + return nullopt; + optional start = parsePosition(_sourceUnitName, _range["start"]); + optional end = parsePosition(_sourceUnitName, _range["end"]); + if (!start || !end) + return nullopt; + solAssert(*start->sourceName == *end->sourceName); + start->end = end->end; + return start; +} + +} diff --git a/libsolidity/lsp/HandlerBase.h b/libsolidity/lsp/HandlerBase.h new file mode 100644 index 000000000..067d485b5 --- /dev/null +++ b/libsolidity/lsp/HandlerBase.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include +#include + +#include + +namespace solidity::lsp +{ + +class Transport; + +/** + * Helper base class for implementing handlers. + */ +class HandlerBase +{ +public: + explicit HandlerBase(LanguageServer& _server): m_server{_server} {} + + Json::Value toRange(langutil::SourceLocation const& _location) const; + Json::Value toJson(langutil::SourceLocation const& _location) const; + + std::optional parsePosition( + std::string const& _sourceUnitName, + Json::Value const& _position + ) const; + + /// @returns the source location given a source unit name and an LSP Range object, + /// or nullopt on failure. + std::optional 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(); }; + + LanguageServer& m_server; +}; + +} diff --git a/libsolidity/lsp/LanguageServer.cpp b/libsolidity/lsp/LanguageServer.cpp index bda55b26d..1808fc993 100644 --- a/libsolidity/lsp/LanguageServer.cpp +++ b/libsolidity/lsp/LanguageServer.cpp @@ -21,6 +21,9 @@ #include #include #include +#include +#include + #include #include @@ -48,31 +51,6 @@ using namespace solidity::frontend; namespace { -Json::Value toJson(LineColumn _pos) -{ - Json::Value json = Json::objectValue; - json["line"] = max(_pos.line, 0); - json["character"] = max(_pos.column, 0); - - return json; -} - -Json::Value toJsonRange(LineColumn const& _start, LineColumn const& _end) -{ - Json::Value json; - json["start"] = toJson(_start); - json["end"] = toJson(_end); - return json; -} - -optional parseLineColumn(Json::Value const& _lineColumn) -{ - if (_lineColumn.isObject() && _lineColumn["line"].isInt() && _lineColumn["character"].isInt()) - return LineColumn{_lineColumn["line"].asInt(), _lineColumn["character"].asInt()}; - else - return nullopt; -} - int toDiagnosticSeverity(Error::Type _errorType) { // 1=Error, 2=Warning, 3=Info, 4=Hint @@ -107,55 +85,19 @@ LanguageServer::LanguageServer(Transport& _transport): { } -optional LanguageServer::parsePosition( - string const& _sourceUnitName, - Json::Value const& _position -) const +optional LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range) { - if (!m_fileRepository.sourceUnits().count(_sourceUnitName)) - return nullopt; - - if (optional lineColumn = parseLineColumn(_position)) - if (optional const offset = CharStream::translateLineColumnToPosition( - m_fileRepository.sourceUnits().at(_sourceUnitName), - *lineColumn - )) - return SourceLocation{*offset, *offset, make_shared(_sourceUnitName)}; - return nullopt; + return HandlerBase{*this}.parseRange(_sourceUnitName, _range); } -optional LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range) const +Json::Value LanguageServer::toRange(SourceLocation const& _location) { - if (!_range.isObject()) - return nullopt; - optional start = parsePosition(_sourceUnitName, _range["start"]); - optional end = parsePosition(_sourceUnitName, _range["end"]); - if (!start || !end) - return nullopt; - solAssert(*start->sourceName == *end->sourceName); - start->end = end->end; - return start; + return HandlerBase(*this).toRange(_location); } -Json::Value LanguageServer::toRange(SourceLocation const& _location) const +Json::Value LanguageServer::toJson(SourceLocation const& _location) { - if (!_location.hasText()) - return toJsonRange({}, {}); - - solAssert(_location.sourceName, ""); - CharStream const& stream = m_compilerStack.charStream(*_location.sourceName); - LineColumn start = stream.translatePositionToLineColumn(_location.start); - LineColumn end = stream.translatePositionToLineColumn(_location.end); - return toJsonRange(start, end); -} - -Json::Value LanguageServer::toJson(SourceLocation const& _location) const -{ - solAssert(_location.sourceName); - Json::Value item = Json::objectValue; - item["uri"] = m_fileRepository.sourceUnitNameToClientPath(*_location.sourceName); - item["range"] = toRange(_location); - return item; + return HandlerBase(*this).toJson(_location); } void LanguageServer::changeConfiguration(Json::Value const& _settings) @@ -403,3 +345,20 @@ void LanguageServer::handleTextDocumentDidClose(Json::Value const& _args) compileAndUpdateDiagnostics(); } + +ASTNode const* LanguageServer::requestASTNode(std::string const& _sourceUnitName, LineColumn const& _filePos) +{ + if (m_compilerStack.state() < CompilerStack::AnalysisPerformed) + return nullptr; + + if (!m_fileRepository.sourceUnits().count(_sourceUnitName)) + return nullptr; + + optional sourcePos = m_compilerStack.charStream(_sourceUnitName) + .translateLineColumnToPosition(_filePos); + if (!sourcePos.has_value()) + return nullptr; + + return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName)); +} + diff --git a/libsolidity/lsp/LanguageServer.h b/libsolidity/lsp/LanguageServer.h index a3ab37198..8bc5e21a2 100644 --- a/libsolidity/lsp/LanguageServer.h +++ b/libsolidity/lsp/LanguageServer.h @@ -57,6 +57,11 @@ public: /// @return boolean indicating normal or abnormal termination. bool run(); + FileRepository& fileRepository() noexcept { return m_fileRepository; } + Transport& client() noexcept { return m_client; } + frontend::ASTNode const* requestASTNode(std::string const& _sourceUnitName, langutil::LineColumn const& _filePos); + langutil::CharStreamProvider const& charStreamProvider() const noexcept { return m_compilerStack; } + private: /// Checks if the server is initialized (to be used by messages that need it to be initialized). /// Reports an error and returns false if not. @@ -72,22 +77,18 @@ private: /// Compile everything until after analysis phase. void compile(); + using MessageHandler = std::function; - std::optional parsePosition( - std::string const& _sourceUnitName, - Json::Value const& _position - ) const; /// @returns the source location given a source unit name and an LSP Range object, /// or nullopt on failure. std::optional parseRange( std::string const& _sourceUnitName, Json::Value const& _range - ) const; - Json::Value toRange(langutil::SourceLocation const& _location) const; - Json::Value toJson(langutil::SourceLocation const& _location) const; + ); + Json::Value toRange(langutil::SourceLocation const& _location); + Json::Value toJson(langutil::SourceLocation const& _location); // LSP related member fields - using MessageHandler = std::function; enum class State { Started, Initialized, ShutdownRequested, ExitRequested, ExitWithoutShutdown }; State m_state = State::Started; diff --git a/libsolidity/lsp/Utils.cpp b/libsolidity/lsp/Utils.cpp new file mode 100644 index 000000000..a25e06d5f --- /dev/null +++ b/libsolidity/lsp/Utils.cpp @@ -0,0 +1,70 @@ +#include +#include +#include +#include +#include + +namespace solidity::lsp +{ + +using namespace frontend; +using namespace langutil; +using namespace std; + +optional parseLineColumn(Json::Value const& _lineColumn) +{ + if (_lineColumn.isObject() && _lineColumn["line"].isInt() && _lineColumn["character"].isInt()) + return LineColumn{_lineColumn["line"].asInt(), _lineColumn["character"].asInt()}; + else + return nullopt; +} + +Json::Value toJson(LineColumn _pos) +{ + Json::Value json = Json::objectValue; + json["line"] = max(_pos.line, 0); + json["character"] = max(_pos.column, 0); + + return json; +} + +Json::Value toJsonRange(LineColumn const& _start, LineColumn const& _end) +{ + Json::Value json; + json["start"] = toJson(_start); + json["end"] = toJson(_end); + return json; +} + +vector allAnnotatedDeclarations(Expression const* _expression) +{ + vector output; + + if (auto const* identifier = dynamic_cast(_expression)) + { + output.push_back(identifier->annotation().referencedDeclaration); + output += identifier->annotation().candidateDeclarations; + } + else if (auto const* memberAccess = dynamic_cast(_expression)) + { + output.push_back(memberAccess->annotation().referencedDeclaration); + } + + return output; +} + +optional declarationPosition(Declaration const* _declaration) +{ + if (!_declaration) + return nullopt; + + if (_declaration->nameLocation().isValid()) + return _declaration->nameLocation(); + + if (_declaration->location().isValid()) + return _declaration->location(); + + return nullopt; +} + +} diff --git a/libsolidity/lsp/Utils.h b/libsolidity/lsp/Utils.h new file mode 100644 index 000000000..8b598823f --- /dev/null +++ b/libsolidity/lsp/Utils.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +#include + +#include + +#include +#include + +namespace solidity::langutil +{ +class CharStreamProvider; +} + +namespace solidity::lsp +{ + +class FileRepository; + +std::optional parseLineColumn(Json::Value const& _lineColumn); +Json::Value toJson(langutil::LineColumn _pos); +Json::Value toJsonRange(langutil::LineColumn const& _start, langutil::LineColumn const& _end); + +std::vector allAnnotatedDeclarations(frontend::Expression const* _expression); +std::optional declarationPosition(frontend::Declaration const* _declaration); + +}