From 7d37ed45316ebb3b201b376d98e89e96558a1684 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Thu, 5 Mar 2020 21:59:26 +0100 Subject: [PATCH] Adds structured docs for variable declarations. - adds natspec generation for state variables. - exports structured docs for state variables to JSON. --- libsolidity/analysis/DocStringAnalyser.cpp | 17 ++++++++ libsolidity/analysis/DocStringAnalyser.h | 7 ++++ libsolidity/ast/AST.h | 8 ++-- libsolidity/ast/ASTAnnotations.h | 2 +- libsolidity/ast/ASTJsonConverter.cpp | 3 ++ libsolidity/ast/ASTJsonImporter.cpp | 1 + libsolidity/interface/Natspec.cpp | 26 +++++++++++++ libsolidity/parsing/Parser.cpp | 8 +++- test/libsolidity/SolidityNatspecJSON.cpp | 39 +++++++++++++++++++ .../natspec/docstring_state_variable.sol | 9 +++++ .../natspec/docstring_variable.sol | 13 +++++++ 11 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 test/libsolidity/syntaxTests/natspec/docstring_state_variable.sol create mode 100644 test/libsolidity/syntaxTests/natspec/docstring_variable.sol diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp index 53edc8cdf..9385838a9 100644 --- a/libsolidity/analysis/DocStringAnalyser.cpp +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -57,6 +57,13 @@ bool DocStringAnalyser::visit(FunctionDefinition const& _function) return true; } +bool DocStringAnalyser::visit(VariableDeclaration const& _variable) +{ + if (_variable.isStateVariable() && _variable.isPartOfExternalInterface()) + handleDeclaration(_variable, _variable, _variable.annotation()); + return true; +} + bool DocStringAnalyser::visit(ModifierDefinition const& _modifier) { handleCallable(_modifier, _modifier, _modifier.annotation()); @@ -117,6 +124,16 @@ void DocStringAnalyser::handleCallable( checkParameters(_callable, _node, _annotation); } +void DocStringAnalyser::handleDeclaration( + Declaration const&, + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation +) +{ + static set const validTags = set{"author", "dev", "notice", "return", "param"}; + parseDocStrings(_node, _annotation, validTags, "state variables"); +} + void DocStringAnalyser::parseDocStrings( StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation, diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h index b9f816854..09fa07b40 100644 --- a/libsolidity/analysis/DocStringAnalyser.h +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -46,6 +46,7 @@ public: private: bool visit(ContractDefinition const& _contract) override; bool visit(FunctionDefinition const& _function) override; + bool visit(VariableDeclaration const& _variable) override; bool visit(ModifierDefinition const& _modifier) override; bool visit(EventDefinition const& _event) override; @@ -67,6 +68,12 @@ private: StructurallyDocumentedAnnotation& _annotation ); + void handleDeclaration( + Declaration const& _declaration, + StructurallyDocumented const& _node, + StructurallyDocumentedAnnotation& _annotation + ); + void parseDocStrings( StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation, diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 055c0298b..aacd88507 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -859,7 +859,7 @@ private: * Declaration of a variable. This can be used in various places, e.g. in function parameter * lists, struct definitions and even function bodies. */ -class VariableDeclaration: public Declaration +class VariableDeclaration: public Declaration, public StructurallyDocumented { public: enum Location { Unspecified, Storage, Memory, CallData }; @@ -882,6 +882,7 @@ public: ASTPointer const& _name, ASTPointer _value, Visibility _visibility, + ASTPointer const& _documentation, bool _isStateVar = false, bool _isIndexed = false, Mutability _mutability = Mutability::Mutable, @@ -889,8 +890,9 @@ public: Location _referenceLocation = Location::Unspecified ): Declaration(_id, _location, _name, _visibility), - m_typeName(std::move(_type)), - m_value(std::move(_value)), + StructurallyDocumented(_documentation), + m_typeName(_type), + m_value(_value), m_isStateVariable(_isStateVar), m_isIndexed(_isIndexed), m_mutability(_mutability), diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index 0ed54897a..eb8f7a263 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -171,7 +171,7 @@ struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, Structurally { }; -struct VariableDeclarationAnnotation: DeclarationAnnotation +struct VariableDeclarationAnnotation: DeclarationAnnotation, StructurallyDocumentedAnnotation { /// Type of variable (type of identifier referencing this variable). TypePointer type = nullptr; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 603781b26..bfdb92e11 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -390,7 +390,10 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node) make_pair("typeDescriptions", typePointerToJson(_node.annotation().type, true)) }; if (_node.isStateVariable() && _node.isPublic()) + { attributes.emplace_back("functionSelector", _node.externalIdentifierHex()); + attributes.emplace_back("documentation", _node.documentation() ? toJson(*_node.documentation()) : Json::nullValue); + } if (m_inEvent) attributes.emplace_back("indexed", _node.isIndexed()); if (!_node.annotation().baseFunctions.empty()) diff --git a/libsolidity/ast/ASTJsonImporter.cpp b/libsolidity/ast/ASTJsonImporter.cpp index c0bee4c60..f96e247cc 100644 --- a/libsolidity/ast/ASTJsonImporter.cpp +++ b/libsolidity/ast/ASTJsonImporter.cpp @@ -441,6 +441,7 @@ ASTPointer ASTJsonImporter::createVariableDeclaration(Json: make_shared(member(_node, "name").asString()), nullOrCast(member(_node, "value")), visibility(_node), + _node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")), memberAsBool(_node, "stateVariable"), _node.isMember("indexed") ? memberAsBool(_node, "indexed") : false, mutability, diff --git a/libsolidity/interface/Natspec.cpp b/libsolidity/interface/Natspec.cpp index 5e51696ec..ef842c8ef 100644 --- a/libsolidity/interface/Natspec.cpp +++ b/libsolidity/interface/Natspec.cpp @@ -52,6 +52,7 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) for (auto const& it: _contractDef.interfaceFunctions()) if (it.second->hasDeclaration()) + { if (auto const* f = dynamic_cast(&it.second->declaration())) { string value = extractDoc(f->annotation().docTags, "notice"); @@ -63,6 +64,18 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef) methods[it.second->externalSignature()] = user; } } + if (auto var = dynamic_cast(&it.second->declaration())) + { + string value = extractDoc(var->annotation().docTags, "notice"); + if (!value.empty()) + { + Json::Value user; + // since @notice is the only user tag if missing function should not appear + user["notice"] = Json::Value(value); + methods[it.second->externalSignature()] = user; + } + } + } doc["methods"] = methods; return doc; @@ -108,6 +121,19 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef) if (!method.empty()) methods[it.second->externalSignature()] = method; } + if (auto var = dynamic_cast(&it.second->declaration())) + { + Json::Value method(devDocumentation(var->annotation().docTags)); + if (!method.empty()) + { +// Json::Value jsonReturn = extractReturnParameterDocs(var->annotation().docTags, *var); + +// if (!jsonReturn.empty()) +// method["returns"] = jsonReturn; + + methods[it.second->externalSignature()] = method; + } + } } doc["methods"] = methods; diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 7ae22dc70..884a0765a 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -686,6 +686,7 @@ ASTPointer Parser::parseVariableDeclaration( ASTNodeFactory nodeFactory = _lookAheadArrayType ? ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this); ASTPointer type; + ASTPointer documentation = parseStructuredDocumentation(); if (_lookAheadArrayType) type = _lookAheadArrayType; else @@ -695,6 +696,9 @@ ASTPointer Parser::parseVariableDeclaration( nodeFactory.setEndPositionFromNode(type); } +// if (!_options.isStateVariable && documentation != nullptr) +// fatalParserError("Only state variables can retrieve a docstring."); + if (dynamic_cast(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace) fatalParserError( 2915_error, @@ -809,6 +813,7 @@ ASTPointer Parser::parseVariableDeclaration( identifier, value, visibility, + documentation, _options.isStateVariable, isIndexed, mutability, @@ -1574,7 +1579,8 @@ ASTPointer Parser::parseVariableDeclarationStateme ASTPointer(), name, ASTPointer(), - Visibility::Default + Visibility::Default, + nullptr ); } variables.push_back(var); diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index 69ae832ad..c3f96d874 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -204,6 +204,45 @@ BOOST_AUTO_TEST_CASE(dev_and_user_no_doc) checkNatspec(sourceCode, "test", userNatspec, true); } +BOOST_AUTO_TEST_CASE(dev_state_variable) +{ + char const* sourceCode = R"( + contract test { + /// @dev Public accessor for the internal state + /// @notice Keeps track of the internal state + uint public state; + } + )"; + + char const* natspec = "{" + "\"methods\":{" + " \"state()\":{ \n" + " \"details\": \"Public accessor for the internal state\",\n" + " }\n" + "}}"; + + checkNatspec(sourceCode, "test", natspec, false); +} + +BOOST_AUTO_TEST_CASE(user_state_variable) +{ + char const* sourceCode = R"( + contract test { + /// @notice Keeps track of the internal state + uint public state; + } + )"; + + char const* natspec = "{" + "\"methods\":{" + " \"state()\":{ \n" + " \"notice\": \"Keeps track of the internal state\",n" + " }\n" + "}}"; + + checkNatspec(sourceCode, "test", natspec, true); +} + BOOST_AUTO_TEST_CASE(dev_desc_after_nl) { char const* sourceCode = R"( diff --git a/test/libsolidity/syntaxTests/natspec/docstring_state_variable.sol b/test/libsolidity/syntaxTests/natspec/docstring_state_variable.sol new file mode 100644 index 000000000..1ee27b62b --- /dev/null +++ b/test/libsolidity/syntaxTests/natspec/docstring_state_variable.sol @@ -0,0 +1,9 @@ +contract C { + /// @title example of title + /// @author example of author + /// @notice example of notice + /// @dev example of dev + /// @param example of param + /// @return example of return + uint public state; +} \ No newline at end of file diff --git a/test/libsolidity/syntaxTests/natspec/docstring_variable.sol b/test/libsolidity/syntaxTests/natspec/docstring_variable.sol new file mode 100644 index 000000000..ccbd4c8a5 --- /dev/null +++ b/test/libsolidity/syntaxTests/natspec/docstring_variable.sol @@ -0,0 +1,13 @@ +contract C { + function f() public pure returns (uint) { + /// @title example of title + /// @author example of author + /// @notice example of notice + /// @dev example of dev + /// @param example of param + /// @return example of return + uint state = 42; + return state; + } +} +// ----