/* 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 . */ // SPDX-License-Identifier: GPL-3.0 #include #include #include #include #include using namespace solidity::langutil; using namespace solidity::frontend; namespace solidity::lsp { namespace { std::optional semanticTokenTypeForType(frontend::Type const* _type) { if (!_type) return std::nullopt; switch (_type->category()) { case frontend::Type::Category::Address: return SemanticTokenType::Class; case frontend::Type::Category::Bool: return SemanticTokenType::Number; case frontend::Type::Category::Enum: return SemanticTokenType::Enum; case frontend::Type::Category::Function: return SemanticTokenType::Function; case frontend::Type::Category::Integer: return SemanticTokenType::Number; case frontend::Type::Category::RationalNumber: return SemanticTokenType::Number; case frontend::Type::Category::StringLiteral: return SemanticTokenType::String; case frontend::Type::Category::Struct: return SemanticTokenType::Struct; case frontend::Type::Category::Contract: return SemanticTokenType::Class; default: lspDebug(fmt::format("semanticTokenTypeForType: unknown category: {}", static_cast(_type->category()))); return SemanticTokenType::Type; } } SemanticTokenType semanticTokenTypeForExpression(frontend::Type const* _type) { if (!_type) return SemanticTokenType::Variable; switch (_type->category()) { case frontend::Type::Category::Enum: return SemanticTokenType::Enum; default: return SemanticTokenType::Variable; } } } // end namespace Json::Value SemanticTokensBuilder::build(SourceUnit const& _sourceUnit, CharStream const& _charStream) { reset(&_charStream); _sourceUnit.accept(*this); return m_encodedTokens; } void SemanticTokensBuilder::reset(CharStream const* _charStream) { m_encodedTokens = Json::arrayValue; m_charStream = _charStream; m_lastLine = 0; m_lastStartChar = 0; } void SemanticTokensBuilder::encode( SourceLocation const& _sourceLocation, SemanticTokenType _tokenType, SemanticTokenModifiers _modifiers ) { /* https://microsoft.github.io/language-server-protocol/specifications/specification-3-17/#textDocument_semanticTokens // Step-1: Absolute positions { line: 2, startChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, { line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 }, { line: 5, startChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } // Step-2: Relative positions as intermediate step { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 }, { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 }, { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 } // Step-3: final array result // 1st token, 2nd token, 3rd token [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] So traverse through the AST and assign each leaf a token 5-tuple. */ // solAssert(_sourceLocation.isValid()); if (!_sourceLocation.isValid()) return; auto const [line, startChar] = m_charStream->translatePositionToLineColumn(_sourceLocation.start); auto const length = _sourceLocation.end - _sourceLocation.start; lspDebug(fmt::format("encode [{}:{}..{}] {}", line, startChar, length, static_cast(_tokenType))); m_encodedTokens.append(line - m_lastLine); if (line == m_lastLine) m_encodedTokens.append(startChar - m_lastStartChar); else m_encodedTokens.append(startChar); m_encodedTokens.append(length); m_encodedTokens.append(static_cast(_tokenType)); m_encodedTokens.append(static_cast(_modifiers)); m_lastLine = line; m_lastStartChar = startChar; } bool SemanticTokensBuilder::visit(frontend::ContractDefinition const& _node) { encode(_node.nameLocation(), SemanticTokenType::Class); return true; } bool SemanticTokensBuilder::visit(frontend::ElementaryTypeName const& _node) { encode(_node.location(), SemanticTokenType::Type); return true; } bool SemanticTokensBuilder::visit(frontend::ElementaryTypeNameExpression const& _node) { if (auto const tokenType = semanticTokenTypeForType(_node.annotation().type); tokenType.has_value()) encode(_node.location(), tokenType.value()); return true; } bool SemanticTokensBuilder::visit(frontend::EnumDefinition const& _node) { encode(_node.nameLocation(), SemanticTokenType::Enum); return true; } bool SemanticTokensBuilder::visit(frontend::EnumValue const& _node) { encode(_node.nameLocation(), SemanticTokenType::EnumMember); return true; } bool SemanticTokensBuilder::visit(frontend::ErrorDefinition const& _node) { encode(_node.nameLocation(), SemanticTokenType::Event); return true; } bool SemanticTokensBuilder::visit(frontend::FunctionDefinition const& _node) { encode(_node.nameLocation(), SemanticTokenType::Function); return true; } bool SemanticTokensBuilder::visit(frontend::ModifierDefinition const& _node) { encode(_node.nameLocation(), SemanticTokenType::Modifier); return true; } void SemanticTokensBuilder::endVisit(frontend::Literal const& _literal) { encode(_literal.location(), SemanticTokenType::Number); } void SemanticTokensBuilder::endVisit(frontend::StructuredDocumentation const& _documentation) { encode(_documentation.location(), SemanticTokenType::Comment); } void SemanticTokensBuilder::endVisit(frontend::Identifier const& _identifier) { //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) { lspDebug("OMG We've found a const!"); modifiers = modifiers | SemanticTokenModifiers::Readonly; } encode(_identifier.location(), semanticTokenTypeForExpression(_identifier.annotation().type), modifiers); } void SemanticTokensBuilder::endVisit(frontend::IdentifierPath const& _node) { lspDebug(fmt::format("IdentifierPath: identifier path [{}..{}]", _node.location().start, _node.location().end)); for (size_t i = 0; i < _node.path().size(); ++i) lspDebug(fmt::format(" [{}]: {}", i, _node.path().at(i))); if (dynamic_cast(_node.annotation().referencedDeclaration)) encode(_node.location(), SemanticTokenType::EnumMember); else encode(_node.location(), SemanticTokenType::Variable); } bool SemanticTokensBuilder::visit(frontend::MemberAccess const& _node) { lspDebug(fmt::format("[{}..{}] MemberAccess({}): {}", _node.location().start, _node.location().end, _node.annotation().referencedDeclaration ? _node.annotation().referencedDeclaration->name() : "?", _node.memberName())); auto const memberNameLength = static_cast(_node.memberName().size()); auto const memberTokenType = semanticTokenTypeForExpression(_node.annotation().type); auto lhsLocation = _node.location(); lhsLocation.end -= (memberNameLength + 1 /*exclude the dot*/); auto rhsLocation = _node.location(); rhsLocation.start = rhsLocation.end - static_cast(memberNameLength); if (memberTokenType == SemanticTokenType::Enum) { // Special handling for enumeration symbols. encode(lhsLocation, SemanticTokenType::Enum); encode(rhsLocation, SemanticTokenType::EnumMember); } else if (memberTokenType == SemanticTokenType::Function) { // Special handling for function symbols. encode(lhsLocation, SemanticTokenType::Variable); encode(rhsLocation, memberTokenType); } else { encode(rhsLocation, memberTokenType); } return false; // we handle LHS and RHS explicitly above. } void SemanticTokensBuilder::endVisit(PragmaDirective const& _pragma) { encode(_pragma.location(), SemanticTokenType::Macro); // NOTE: It would be nice if we could highlight based on the symbols, // such as (version) numerics be different than identifiers. } bool SemanticTokensBuilder::visit(frontend::UserDefinedTypeName const& _node) { if (auto const token = semanticTokenTypeForType(_node.annotation().type); token.has_value()) encode(_node.location(), *token); return false; } bool SemanticTokensBuilder::visit(frontend::VariableDeclaration const& _node) { lspDebug(fmt::format("VariableDeclaration: {}", _node.name())); if (auto const token = semanticTokenTypeForType(_node.typeName().annotation().type); token.has_value()) encode(_node.typeName().location(), *token); encode(_node.nameLocation(), SemanticTokenType::Variable); if (_node.overrides()) _node.overrides()->accept(*this); if (_node.value()) _node.value()->accept(*this); return false; } } // end namespace