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 | ||||
| 		)) | ||||
| 			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; | ||||
| } | ||||
| 	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 {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()) | ||||
| 	if (optional<int> sourcePos = | ||||
| 		m_compilerStack.charStream(_sourceUnitName).translateLineColumnToPosition(_filePos)) | ||||
| 		return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName)); | ||||
| 	else | ||||
| 		return nullptr; | ||||
| 
 | ||||
| 	return locateInnermostASTNode(*sourcePos, m_compilerStack.ast(_sourceUnitName)); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
| 	return output; | ||||
| 	if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(_expression)) | ||||
| 		if (memberAccess->annotation().referencedDeclaration) | ||||
| 			return memberAccess->annotation().referencedDeclaration; | ||||
| 
 | ||||
| 	return nullptr; | ||||
| } | ||||
| 
 | ||||
| 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