LSP: Introduce HandlerBase for future LSP-feature implementations.

This commit is contained in:
Christian Parpart 2022-01-05 12:15:10 +01:00
parent 60463cfd11
commit 1035eacb53
7 changed files with 249 additions and 75 deletions

View File

@ -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

View File

@ -0,0 +1,63 @@
#include <libsolidity/lsp/HandlerBase.h>
#include <libsolidity/lsp/LanguageServer.h>
#include <libsolidity/lsp/Utils.h>
#include <libsolidity/ast/AST.h>
#include <liblangutil/Exceptions.h>
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<SourceLocation> HandlerBase::parsePosition(string const& _sourceUnitName, Json::Value const& _position) const
{
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> 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;
}
}

View File

@ -0,0 +1,46 @@
#pragma once
#include <libsolidity/lsp/FileRepository.h>
#include <libsolidity/lsp/LanguageServer.h>
#include <liblangutil/SourceLocation.h>
#include <liblangutil/CharStreamProvider.h>
#include <optional>
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<langutil::SourceLocation> 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<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(); };
LanguageServer& m_server;
};
}

View File

@ -21,6 +21,9 @@
#include <libsolidity/interface/ReadFile.h>
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/lsp/LanguageServer.h>
#include <libsolidity/lsp/HandlerBase.h>
#include <libsolidity/lsp/Utils.h>
#include <liblangutil/SourceReferenceExtractor.h>
#include <liblangutil/CharStream.h>
@ -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<LineColumn> 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<SourceLocation> LanguageServer::parsePosition(
string const& _sourceUnitName,
Json::Value const& _position
) const
optional<SourceLocation> LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range)
{
if (!m_fileRepository.sourceUnits().count(_sourceUnitName))
return nullopt;
if (optional<LineColumn> lineColumn = parseLineColumn(_position))
if (optional<int> const offset = CharStream::translateLineColumnToPosition(
m_fileRepository.sourceUnits().at(_sourceUnitName),
*lineColumn
))
return SourceLocation{*offset, *offset, make_shared<string>(_sourceUnitName)};
return nullopt;
return HandlerBase{*this}.parseRange(_sourceUnitName, _range);
}
optional<SourceLocation> LanguageServer::parseRange(string const& _sourceUnitName, Json::Value const& _range) const
Json::Value LanguageServer::toRange(SourceLocation const& _location)
{
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 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<int> sourcePos = m_compilerStack.charStream(_sourceUnitName)
.translateLineColumnToPosition(_filePos);
if (!sourcePos.has_value())
return nullptr;
return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName));
}

View File

@ -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<void(MessageID, Json::Value const&)>;
std::optional<langutil::SourceLocation> 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<langutil::SourceLocation> 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<void(MessageID, Json::Value const&)>;
enum class State { Started, Initialized, ShutdownRequested, ExitRequested, ExitWithoutShutdown };
State m_state = State::Started;

70
libsolidity/lsp/Utils.cpp Normal file
View File

@ -0,0 +1,70 @@
#include <liblangutil/CharStreamProvider.h>
#include <liblangutil/Exceptions.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/lsp/FileRepository.h>
#include <libsolidity/lsp/Utils.h>
namespace solidity::lsp
{
using namespace frontend;
using namespace langutil;
using namespace std;
optional<LineColumn> 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<Declaration const*> allAnnotatedDeclarations(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);
}
return output;
}
optional<SourceLocation> declarationPosition(Declaration const* _declaration)
{
if (!_declaration)
return nullopt;
if (_declaration->nameLocation().isValid())
return _declaration->nameLocation();
if (_declaration->location().isValid())
return _declaration->location();
return nullopt;
}
}

29
libsolidity/lsp/Utils.h Normal file
View File

@ -0,0 +1,29 @@
#pragma once
#include <liblangutil/SourceLocation.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolutil/JSON.h>
#include <optional>
#include <vector>
namespace solidity::langutil
{
class CharStreamProvider;
}
namespace solidity::lsp
{
class FileRepository;
std::optional<langutil::LineColumn> parseLineColumn(Json::Value const& _lineColumn);
Json::Value toJson(langutil::LineColumn _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);
}