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