From b4f561680a2a5169d1245271245e2b71822cb73a Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 26 Oct 2015 15:13:36 +0100 Subject: Store docstrings in AST annotations. --- libsolidity/analysis/DocStringAnalyser.cpp | 117 ++++++++++ libsolidity/analysis/DocStringAnalyser.h | 64 ++++++ libsolidity/ast/AST.cpp | 21 ++ libsolidity/ast/AST.h | 6 + libsolidity/ast/ASTAnnotations.h | 27 ++- libsolidity/interface/CompilerStack.cpp | 22 +- libsolidity/interface/CompilerStack.h | 3 - libsolidity/interface/InterfaceHandler.cpp | 318 ++++---------------------- libsolidity/interface/InterfaceHandler.h | 55 +---- libsolidity/parsing/DocStringParser.cpp | 141 ++++++++++++ libsolidity/parsing/DocStringParser.h | 70 ++++++ test/libsolidity/SolidityEndToEndTest.cpp | 26 --- test/libsolidity/SolidityNatspecJSON.cpp | 31 +++ test/libsolidity/solidityExecutionFramework.h | 22 -- 14 files changed, 540 insertions(+), 383 deletions(-) create mode 100644 libsolidity/analysis/DocStringAnalyser.cpp create mode 100644 libsolidity/analysis/DocStringAnalyser.h create mode 100644 libsolidity/parsing/DocStringParser.cpp create mode 100644 libsolidity/parsing/DocStringParser.h diff --git a/libsolidity/analysis/DocStringAnalyser.cpp b/libsolidity/analysis/DocStringAnalyser.cpp new file mode 100644 index 00000000..96cb5692 --- /dev/null +++ b/libsolidity/analysis/DocStringAnalyser.cpp @@ -0,0 +1,117 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2015 + * Parses and analyses the doc strings. + * Stores the parsing results in the AST annotations and reports errors. + */ + +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit) +{ + m_errorOccured = false; + _sourceUnit.accept(*this); + + return !m_errorOccured; +} + +bool DocStringAnalyser::visit(ContractDefinition const& _node) +{ + parseDocStrings(_node, _node.annotation()); + + static const set validTags = set{"author", "title", "dev", "notice"}; + for (auto const& docTag: _node.annotation().docTags) + if (!validTags.count(docTag.first)) + appendError("Doc tag @" + docTag.first + " not valid for contracts."); + + return true; +} + +bool DocStringAnalyser::visit(FunctionDefinition const& _node) +{ + handleCallable(_node, _node, _node.annotation()); + return true; +} + +bool DocStringAnalyser::visit(ModifierDefinition const& _node) +{ + handleCallable(_node, _node, _node.annotation()); + + return true; +} + +bool DocStringAnalyser::visit(EventDefinition const& _node) +{ + handleCallable(_node, _node, _node.annotation()); + + return true; +} + +void DocStringAnalyser::handleCallable( + CallableDeclaration const& _callable, + Documented const& _node, + DocumentedAnnotation& _annotation +) +{ + parseDocStrings(_node, _annotation); + static const set validTags = set{"author", "dev", "notice", "return", "param", "why3"}; + for (auto const& docTag: _annotation.docTags) + if (!validTags.count(docTag.first)) + appendError("Doc tag @" + docTag.first + " not valid for functions."); + + set validParams; + for (auto const& p: _callable.parameters()) + validParams.insert(p->name()); + if (_callable.returnParameterList()) + for (auto const& p: _callable.returnParameterList()->parameters()) + validParams.insert(p->name()); + auto paramRange = _annotation.docTags.equal_range("param"); + for (auto i = paramRange.first; i != paramRange.second; ++i) + if (!validParams.count(i->second.paramName)) + appendError( + "Documented parameter \"" + + i->second.paramName + + "\" not found in the parameter list of the function." + ); +} + +void DocStringAnalyser::parseDocStrings(Documented const& _node, DocumentedAnnotation& _annotation) +{ + DocStringParser parser; + if (_node.documentation() && !_node.documentation()->empty()) + { + if (!parser.parse(*_node.documentation(), m_errors)) + m_errorOccured = true; + _annotation.docTags = parser.tags(); + } +} + +void DocStringAnalyser::appendError(string const& _description) +{ + auto err = make_shared(Error::Type::DocstringParsingError); + *err << errinfo_comment(_description); + m_errors.push_back(err); + m_errorOccured = true; +} diff --git a/libsolidity/analysis/DocStringAnalyser.h b/libsolidity/analysis/DocStringAnalyser.h new file mode 100644 index 00000000..06384c8d --- /dev/null +++ b/libsolidity/analysis/DocStringAnalyser.h @@ -0,0 +1,64 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2015 + * Parses and analyses the doc strings. + * Stores the parsing results in the AST annotations and reports errors. + */ + +#pragma once + +#include + +namespace dev +{ +namespace solidity +{ + +/** + * Parses and analyses the doc strings. + * Stores the parsing results in the AST annotations and reports errors. + */ +class DocStringAnalyser: private ASTConstVisitor +{ +public: + DocStringAnalyser(ErrorList& _errors): m_errors(_errors) {} + bool analyseDocStrings(SourceUnit const& _sourceUnit); + +private: + virtual bool visit(ContractDefinition const& _contract) override; + virtual bool visit(FunctionDefinition const& _function) override; + virtual bool visit(ModifierDefinition const& _modifier) override; + virtual bool visit(EventDefinition const& _event) override; + + void handleCallable( + CallableDeclaration const& _callable, + Documented const& _node, + DocumentedAnnotation& _annotation + ); + + void parseDocStrings(Documented const& _node, DocumentedAnnotation& _annotation); + + void appendError(std::string const& _description); + + bool m_errorOccured = false; + ErrorList& m_errors; +}; + +} +} diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 71d80a36..9d1fb811 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -248,16 +248,37 @@ string FunctionDefinition::externalSignature() const return FunctionType(*this).externalSignature(); } +FunctionDefinitionAnnotation& FunctionDefinition::annotation() const +{ + if (!m_annotation) + m_annotation = new FunctionDefinitionAnnotation(); + return static_cast(*m_annotation); +} + TypePointer ModifierDefinition::type(ContractDefinition const*) const { return make_shared(*this); } +ModifierDefinitionAnnotation& ModifierDefinition::annotation() const +{ + if (!m_annotation) + m_annotation = new ModifierDefinitionAnnotation(); + return static_cast(*m_annotation); +} + TypePointer EventDefinition::type(ContractDefinition const*) const { return make_shared(*this); } +EventDefinitionAnnotation& EventDefinition::annotation() const +{ + if (!m_annotation) + m_annotation = new EventDefinitionAnnotation(); + return static_cast(*m_annotation); +} + UserDefinedTypeNameAnnotation& UserDefinedTypeName::annotation() const { if (!m_annotation) diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 3fe447eb..6a593d3e 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -492,6 +492,8 @@ public: virtual TypePointer type(ContractDefinition const* m_currentContract) const override; + virtual FunctionDefinitionAnnotation& annotation() const override; + private: bool m_isConstructor; bool m_isDeclaredConst; @@ -593,6 +595,8 @@ public: virtual TypePointer type(ContractDefinition const* m_currentContract) const override; + virtual ModifierDefinitionAnnotation& annotation() const override; + private: ASTPointer m_body; }; @@ -647,6 +651,8 @@ public: virtual TypePointer type(ContractDefinition const* m_currentContract) const override; + virtual EventDefinitionAnnotation& annotation() const override; + private: bool m_anonymous = false; }; diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h index d112b1ef..094a178e 100644 --- a/libsolidity/ast/ASTAnnotations.h +++ b/libsolidity/ast/ASTAnnotations.h @@ -41,13 +41,26 @@ struct ASTAnnotation virtual ~ASTAnnotation() {} }; +struct DocTag +{ + std::string content; ///< The text content of the tag. + std::string paramName; ///< Only used for @param, stores the parameter name. +}; + +struct DocumentedAnnotation +{ + virtual ~DocumentedAnnotation() {} + /// Mapping docstring tag name -> content. + std::multimap docTags; +}; + struct TypeDeclarationAnnotation: ASTAnnotation { /// The name of this type, prefixed by proper namespaces if globally accessible. std::string canonicalName; }; -struct ContractDefinitionAnnotation: TypeDeclarationAnnotation +struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnotation { /// Whether all functions are implemented. bool isFullyImplemented = true; @@ -59,6 +72,18 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation std::set contractDependencies; }; +struct FunctionDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +{ +}; + +struct EventDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +{ +}; + +struct ModifierDefinitionAnnotation: ASTAnnotation, DocumentedAnnotation +{ +}; + struct VariableDeclarationAnnotation: ASTAnnotation { /// Type of variable (type of identifier referencing this variable). diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 775c7eb6..6b55b408 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -114,6 +115,12 @@ bool CompilerStack::parse() resolveImports(); + bool noErrors = true; + DocStringAnalyser docStringAnalyser(m_errors); + for (Source const* source: m_sourceOrder) + if (!docStringAnalyser.analyseDocStrings(*source->ast)) + noErrors = false; + m_globalContext = make_shared(); NameAndTypeResolver resolver(m_globalContext->declarations(), m_errors); for (Source const* source: m_sourceOrder) @@ -131,8 +138,6 @@ bool CompilerStack::parse() m_contracts[contract->name()].contract = contract; } - InterfaceHandler interfaceHandler; - bool typesFine = true; for (Source const* source: m_sourceOrder) for (ASTPointer const& node: source->ast->nodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) @@ -142,15 +147,15 @@ bool CompilerStack::parse() TypeChecker typeChecker(m_errors); if (typeChecker.checkTypeRequirements(*contract)) { - contract->setDevDocumentation(interfaceHandler.devDocumentation(*contract)); - contract->setUserDocumentation(interfaceHandler.userDocumentation(*contract)); + contract->setDevDocumentation(InterfaceHandler::devDocumentation(*contract)); + contract->setUserDocumentation(InterfaceHandler::userDocumentation(*contract)); } else - typesFine = false; + noErrors = false; m_contracts[contract->name()].contract = contract; } - m_parseSuccessful = typesFine; + m_parseSuccessful = noErrors; return m_parseSuccessful; } @@ -287,7 +292,7 @@ string const& CompilerStack::metadata(string const& _contractName, Documentation // caches the result if (!*doc) - doc->reset(new string(currentContract.interfaceHandler->documentation(*currentContract.contract, _type))); + doc->reset(new string(InterfaceHandler::documentation(*currentContract.contract, _type))); return *(*doc); } @@ -428,8 +433,5 @@ CompilerStack::Source const& CompilerStack::source(string const& _sourceName) co return it->second; } -CompilerStack::Contract::Contract(): interfaceHandler(make_shared()) {} - - } } diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index 1f1b74f5..ac71da2e 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -188,13 +188,10 @@ private: eth::LinkerObject object; eth::LinkerObject runtimeObject; eth::LinkerObject cloneObject; - std::shared_ptr interfaceHandler; mutable std::unique_ptr interface; mutable std::unique_ptr solidityInterface; mutable std::unique_ptr userDocumentation; mutable std::unique_ptr devDocumentation; - - Contract(); }; void resolveImports(); diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 136136e6..30cd9724 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -3,19 +3,10 @@ #include #include #include -using namespace std; - -namespace dev -{ -namespace solidity -{ - -/* -- public -- */ -InterfaceHandler::InterfaceHandler() -{ - m_lastTag = DocTagType::None; -} +using namespace std; +using namespace dev; +using namespace dev::solidity; string InterfaceHandler::documentation( ContractDefinition const& _contractDef, @@ -181,20 +172,18 @@ string InterfaceHandler::userDocumentation(ContractDefinition const& _contractDe Json::Value methods(Json::objectValue); for (auto const& it: _contractDef.interfaceFunctions()) - { - Json::Value user; - auto strPtr = it.second->documentation(); - if (strPtr) - { - resetUser(); - parseDocString(*strPtr, CommentOwner::Function); - if (!m_notice.empty()) - {// since @notice is the only user tag if missing function should not appear - user["notice"] = Json::Value(m_notice); - methods[it.second->externalSignature()] = user; + if (it.second->hasDeclaration()) + if (auto const* f = dynamic_cast(&it.second->declaration())) + { + string value = extractDoc(f->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 Json::StyledWriter().write(doc); @@ -202,60 +191,45 @@ string InterfaceHandler::userDocumentation(ContractDefinition const& _contractDe string InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef) { - // LTODO: Somewhere in this function warnings for mismatch of param names - // should be thrown Json::Value doc; Json::Value methods(Json::objectValue); - auto contractDoc = _contractDef.documentation(); - if (contractDoc) - { - m_contractAuthor.clear(); - m_title.clear(); - parseDocString(*contractDoc, CommentOwner::Contract); - - if (!m_contractAuthor.empty()) - doc["author"] = m_contractAuthor; - - if (!m_title.empty()) - doc["title"] = m_title; - } + auto author = extractDoc(_contractDef.annotation().docTags, "author"); + if (!author.empty()) + doc["author"] = author; + auto title = extractDoc(_contractDef.annotation().docTags, "title"); + if (!title.empty()) + doc["title"] = title; for (auto const& it: _contractDef.interfaceFunctions()) { + if (!it.second->hasDeclaration()) + continue; Json::Value method; - auto strPtr = it.second->documentation(); - if (strPtr) + if (auto fun = dynamic_cast(&it.second->declaration())) { - resetDev(); - parseDocString(*strPtr, CommentOwner::Function); + auto dev = extractDoc(fun->annotation().docTags, "dev"); + if (!dev.empty()) + method["details"] = Json::Value(dev); - if (!m_dev.empty()) - method["details"] = Json::Value(m_dev); + auto author = extractDoc(fun->annotation().docTags, "author"); + if (!author.empty()) + method["author"] = author; - if (!m_author.empty()) - method["author"] = m_author; + auto ret = extractDoc(fun->annotation().docTags, "return"); + if (!ret.empty()) + method["return"] = ret; Json::Value params(Json::objectValue); - vector paramNames = it.second->parameterNames(); - for (auto const& pair: m_params) - { - if (find(paramNames.begin(), paramNames.end(), pair.first) == paramNames.end()) - // LTODO: mismatching parameter name, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION( - Error(Error::Type::DocstringParsingError) << - errinfo_comment("documented parameter \"" + pair.first + "\" not found in the parameter list of the function.") - ); - params[pair.first] = pair.second; - } + auto paramRange = fun->annotation().docTags.equal_range("param"); + for (auto i = paramRange.first; i != paramRange.second; ++i) + params[i->second.paramName] = Json::Value(i->second.content); - if (!m_params.empty()) + if (!params.empty()) method["params"] = params; - if (!m_return.empty()) - method["return"] = m_return; - - if (!method.empty()) // add the function, only if we have any documentation to add + if (!method.empty()) + // add the function, only if we have any documentation to add methods[it.second->externalSignature()] = method; } } @@ -264,215 +238,11 @@ string InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef return Json::StyledWriter().write(doc); } -/* -- private -- */ -void InterfaceHandler::resetUser() +string InterfaceHandler::extractDoc(multimap const& _tags, string const& _name) { - m_notice.clear(); + string value; + auto range = _tags.equal_range(_name); + for (auto i = range.first; i != range.second; i++) + value += i->second.content; + return value; } - -void InterfaceHandler::resetDev() -{ - m_dev.clear(); - m_author.clear(); - m_return.clear(); - m_params.clear(); -} - -static inline string::const_iterator skipLineOrEOS( - string::const_iterator _nlPos, - string::const_iterator _end -) -{ - return (_nlPos == _end) ? _end : ++_nlPos; -} - -string::const_iterator InterfaceHandler::parseDocTagLine( - string::const_iterator _pos, - string::const_iterator _end, - string& _tagString, - DocTagType _tagType, - bool _appending -) -{ - auto nlPos = find(_pos, _end, '\n'); - if (_appending && _pos < _end && *_pos != ' ') - _tagString += " "; - copy(_pos, nlPos, back_inserter(_tagString)); - m_lastTag = _tagType; - return skipLineOrEOS(nlPos, _end); -} - -string::const_iterator InterfaceHandler::parseDocTagParam( - string::const_iterator _pos, - string::const_iterator _end -) -{ - // find param name - auto currPos = find(_pos, _end, ' '); - if (currPos == _end) - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("End of param name not found" + string(_pos, _end))); - - - auto paramName = string(_pos, currPos); - - currPos += 1; - auto nlPos = find(currPos, _end, '\n'); - auto paramDesc = string(currPos, nlPos); - m_params.push_back(make_pair(paramName, paramDesc)); - - m_lastTag = DocTagType::Param; - return skipLineOrEOS(nlPos, _end); -} - -string::const_iterator InterfaceHandler::appendDocTagParam( - string::const_iterator _pos, - string::const_iterator _end -) -{ - // Should never be called with an empty vector - solAssert(!m_params.empty(), "Internal: Tried to append to empty parameter"); - - auto pair = m_params.back(); - if (_pos < _end && *_pos != ' ') - pair.second += " "; - auto nlPos = find(_pos, _end, '\n'); - copy(_pos, nlPos, back_inserter(pair.second)); - - m_params.at(m_params.size() - 1) = pair; - - return skipLineOrEOS(nlPos, _end); -} - -string::const_iterator InterfaceHandler::parseDocTag( - string::const_iterator _pos, - string::const_iterator _end, - string const& _tag, - CommentOwner _owner -) -{ - // LTODO: need to check for @(start of a tag) between here and the end of line - // for all cases. Also somehow automate list of acceptable tags for each - // language construct since current way does not scale well. - if (m_lastTag == DocTagType::None || _tag != "") - { - if (_tag == "dev") - return parseDocTagLine(_pos, _end, m_dev, DocTagType::Dev, false); - else if (_tag == "notice") - return parseDocTagLine(_pos, _end, m_notice, DocTagType::Notice, false); - else if (_tag == "return") - return parseDocTagLine(_pos, _end, m_return, DocTagType::Return, false); - else if (_tag == "author") - { - if (_owner == CommentOwner::Contract) - return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::Author, false); - else if (_owner == CommentOwner::Function) - return parseDocTagLine(_pos, _end, m_author, DocTagType::Author, false); - else - // LTODO: for now this else makes no sense but later comments will go to more language constructs - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("@author tag is legal only for contracts")); - } - else if (_tag == "title") - { - if (_owner == CommentOwner::Contract) - return parseDocTagLine(_pos, _end, m_title, DocTagType::Title, false); - else - // LTODO: Unknown tag, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("@title tag is legal only for contracts")); - } - else if (_tag == "param") - return parseDocTagParam(_pos, _end); - else - // LTODO: Unknown tag, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("Unknown tag " + _tag + " encountered")); - } - else - return appendDocTag(_pos, _end, _owner); -} - -string::const_iterator InterfaceHandler::appendDocTag( - string::const_iterator _pos, - string::const_iterator _end, - CommentOwner _owner -) -{ - switch (m_lastTag) - { - case DocTagType::Dev: - return parseDocTagLine(_pos, _end, m_dev, DocTagType::Dev, true); - case DocTagType::Notice: - return parseDocTagLine(_pos, _end, m_notice, DocTagType::Notice, true); - case DocTagType::Return: - return parseDocTagLine(_pos, _end, m_return, DocTagType::Return, true); - case DocTagType::Author: - if (_owner == CommentOwner::Contract) - return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::Author, true); - else if (_owner == CommentOwner::Function) - return parseDocTagLine(_pos, _end, m_author, DocTagType::Author, true); - else - // LTODO: Unknown tag, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("@author tag in illegal comment")); - case DocTagType::Title: - if (_owner == CommentOwner::Contract) - return parseDocTagLine(_pos, _end, m_title, DocTagType::Title, true); - else - // LTODO: Unknown tag, throw some form of warning and not just an exception - BOOST_THROW_EXCEPTION(Error(Error::Type::DocstringParsingError) << errinfo_comment("@title tag in illegal comment")); - case DocTagType::Param: - return appendDocTagParam(_pos, _end); - default: - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Illegal documentation tag type")); - break; - } -} - -static inline string::const_iterator firstSpaceOrNl( - string::const_iterator _pos, - string::const_iterator _end -) -{ - auto spacePos = find(_pos, _end, ' '); - auto nlPos = find(_pos, _end, '\n'); - return (spacePos < nlPos) ? spacePos : nlPos; -} - -void InterfaceHandler::parseDocString(string const& _string, CommentOwner _owner) -{ - auto currPos = _string.begin(); - auto end = _string.end(); - - while (currPos != end) - { - auto tagPos = find(currPos, end, '@'); - auto nlPos = find(currPos, end, '\n'); - - if (tagPos != end && tagPos < nlPos) - { - // we found a tag - auto tagNameEndPos = firstSpaceOrNl(tagPos, end); - if (tagNameEndPos == end) - BOOST_THROW_EXCEPTION( - Error(Error::Type::DocstringParsingError) << - errinfo_comment("End of tag " + string(tagPos, tagNameEndPos) + "not found")); - - currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos), _owner); - } - else if (m_lastTag != DocTagType::None) // continuation of the previous tag - currPos = appendDocTag(currPos, end, _owner); - else if (currPos != end) - { - // if it begins without a tag then consider it as @notice - if (currPos == _string.begin()) - { - currPos = parseDocTag(currPos, end, "notice", CommentOwner::Function); - continue; - } - else if (nlPos == end) //end of text - return; - // else skip the line if a newline was found and we get here - currPos = nlPos + 1; - } - } -} - -} //solidity NS -} // dev NS diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/InterfaceHandler.h index 62164517..30b8f520 100644 --- a/libsolidity/interface/InterfaceHandler.h +++ b/libsolidity/interface/InterfaceHandler.h @@ -37,6 +37,7 @@ namespace solidity // Forward declarations class ContractDefinition; +struct DocTag; enum class DocumentationType: uint8_t; enum class DocTagType: uint8_t @@ -59,73 +60,33 @@ enum class CommentOwner class InterfaceHandler { public: - InterfaceHandler(); - /// Get the given type of documentation /// @param _contractDef The contract definition /// @param _type The type of the documentation. Can be one of the /// types provided by @c DocumentationType /// @return A string with the json representation of provided type - std::string documentation( + static std::string documentation( ContractDefinition const& _contractDef, DocumentationType _type ); /// Get the ABI Interface of the contract /// @param _contractDef The contract definition /// @return A string with the json representation of the contract's ABI Interface - std::string abiInterface(ContractDefinition const& _contractDef); - std::string ABISolidityInterface(ContractDefinition const& _contractDef); + static std::string abiInterface(ContractDefinition const& _contractDef); + static std::string ABISolidityInterface(ContractDefinition const& _contractDef); /// Get the User documentation of the contract /// @param _contractDef The contract definition /// @return A string with the json representation of the contract's user documentation - std::string userDocumentation(ContractDefinition const& _contractDef); + static std::string userDocumentation(ContractDefinition const& _contractDef); /// Genereates the Developer's documentation of the contract /// @param _contractDef The contract definition /// @return A string with the json representation /// of the contract's developer documentation - std::string devDocumentation(ContractDefinition const& _contractDef); + static std::string devDocumentation(ContractDefinition const& _contractDef); private: - void resetUser(); - void resetDev(); - - std::string::const_iterator parseDocTagLine( - std::string::const_iterator _pos, - std::string::const_iterator _end, - std::string& _tagString, - DocTagType _tagType, - bool _appending - ); - std::string::const_iterator parseDocTagParam( - std::string::const_iterator _pos, - std::string::const_iterator _end - ); - std::string::const_iterator appendDocTagParam( - std::string::const_iterator _pos, - std::string::const_iterator _end - ); - void parseDocString(std::string const& _string, CommentOwner _owner); - std::string::const_iterator appendDocTag( - std::string::const_iterator _pos, - std::string::const_iterator _end, - CommentOwner _owner - ); - std::string::const_iterator parseDocTag( - std::string::const_iterator _pos, - std::string::const_iterator _end, - std::string const& _tag, - CommentOwner _owner - ); - - // internal state - DocTagType m_lastTag; - std::string m_notice; - std::string m_dev; - std::string m_return; - std::string m_contractAuthor; - std::string m_author; - std::string m_title; - std::vector> m_params; + /// Returns concatenation of all content under the given tag name. + static std::string extractDoc(std::multimap const& _tags, std::string const& _name); }; } //solidity NS diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp new file mode 100644 index 00000000..bbee35f5 --- /dev/null +++ b/libsolidity/parsing/DocStringParser.cpp @@ -0,0 +1,141 @@ + +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::solidity; + + +static inline string::const_iterator skipLineOrEOS( + string::const_iterator _nlPos, + string::const_iterator _end +) +{ + return (_nlPos == _end) ? _end : ++_nlPos; +} + +static inline string::const_iterator firstSpaceOrNl( + string::const_iterator _pos, + string::const_iterator _end +) +{ + auto spacePos = find(_pos, _end, ' '); + auto nlPos = find(_pos, _end, '\n'); + return (spacePos < nlPos) ? spacePos : nlPos; +} + +bool DocStringParser::parse(string const& _docString, ErrorList& _errors) +{ + m_errors = &_errors; + m_errorsOccurred = false; + m_lastTag = nullptr; + + auto currPos = _docString.begin(); + auto end = _docString.end(); + + while (currPos != end) + { + auto tagPos = find(currPos, end, '@'); + auto nlPos = find(currPos, end, '\n'); + + if (tagPos != end && tagPos < nlPos) + { + // we found a tag + auto tagNameEndPos = firstSpaceOrNl(tagPos, end); + if (tagNameEndPos == end) + { + appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found"); + break; + } + + currPos = parseDocTag(tagNameEndPos + 1, end, string(tagPos + 1, tagNameEndPos)); + } + else if (!!m_lastTag) // continuation of the previous tag + currPos = appendDocTag(currPos, end); + else if (currPos != end) + { + // if it begins without a tag then consider it as @notice + if (currPos == _docString.begin()) + { + currPos = parseDocTag(currPos, end, "notice"); + continue; + } + else if (nlPos == end) //end of text + break; + // else skip the line if a newline was found and we get here + currPos = nlPos + 1; + } + } + return !m_errorsOccurred; +} + +DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, bool _appending) +{ + solAssert(!!m_lastTag, ""); + auto nlPos = find(_pos, _end, '\n'); + if (_appending && _pos < _end && *_pos != ' ') + m_lastTag->content += " "; + copy(_pos, nlPos, back_inserter(m_lastTag->content)); + return skipLineOrEOS(nlPos, _end); +} + +DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end) +{ + // find param name + auto currPos = find(_pos, _end, ' '); + if (currPos == _end) + { + appendError("End of param name not found" + string(_pos, _end)); + return _end; + } + + auto paramName = string(_pos, currPos); + + currPos += 1; + auto nlPos = find(currPos, _end, '\n'); + auto paramDesc = string(currPos, nlPos); + newTag("param"); + m_lastTag->paramName = paramName; + m_lastTag->content = paramDesc; + + return skipLineOrEOS(nlPos, _end); +} + +DocStringParser::iter DocStringParser::parseDocTag(iter _pos, iter _end, string const& _tag) +{ + // LTODO: need to check for @(start of a tag) between here and the end of line + // for all cases. + if (!m_lastTag || _tag != "") + { + if (_tag == "param") + return parseDocTagParam(_pos, _end); + else + { + newTag(_tag); + return parseDocTagLine(_pos, _end, false); + } + } + else + return appendDocTag(_pos, _end); +} + +DocStringParser::iter DocStringParser::appendDocTag(iter _pos, iter _end) +{ + solAssert(!!m_lastTag, ""); + return parseDocTagLine(_pos, _end, true); +} + +void DocStringParser::newTag(string const& _tagName) +{ + m_lastTag = &m_docTags.insert(make_pair(_tagName, DocTag()))->second; +} + +void DocStringParser::appendError(string const& _description) +{ + auto err = make_shared(Error::Type::DocstringParsingError); + *err << errinfo_comment(_description); + m_errors->push_back(err); + m_errorsOccurred = true; +} diff --git a/libsolidity/parsing/DocStringParser.h b/libsolidity/parsing/DocStringParser.h new file mode 100644 index 00000000..f67b8bbd --- /dev/null +++ b/libsolidity/parsing/DocStringParser.h @@ -0,0 +1,70 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Lefteris + * @date 2014, 2015 + * Parses a given docstring into pieces introduced by tags. + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace solidity +{ + +class DocStringParser +{ +public: + /// Parse the given @a _docString and stores the parsed components internally. + /// @returns false on error and appends the error to @a _errors. + bool parse(std::string const& _docString, ErrorList& _errors); + + std::multimap const& tags() const { return m_docTags; } + +private: + using iter = std::string::const_iterator; + void resetUser(); + void resetDev(); + + iter parseDocTagLine(iter _pos, iter _end, bool _appending); + iter parseDocTagParam(iter _pos, iter _end); + iter appendDocTagParam(iter _pos, iter _end); + void parseDocString(std::string const& _string); + iter appendDocTag(iter _pos, iter _end); + /// Parses the doc tag named @a _tag, adds it to m_docTags and returns the position + /// after the tag. + iter parseDocTag(iter _pos, iter _end, std::string const& _tag); + + /// Creates and inserts a new tag and adjusts m_lastTag. + void newTag(std::string const& _tagName); + + void appendError(std::string const& _description); + + /// Mapping tag name -> content. + std::multimap m_docTags; + DocTag* m_lastTag = nullptr; + ErrorList* m_errors = nullptr; + bool m_errorsOccurred = false; +}; + +} //solidity NS +} // dev NS diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 5f7c6684..fb7c4013 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4735,32 +4735,6 @@ BOOST_AUTO_TEST_CASE(bytes_memory_index_access) ) == encodeArgs(u256(data.size()), string("d"))); } -BOOST_AUTO_TEST_CASE(dev_title_at_function_error) -{ - char const* sourceCode = " /// @author Lefteris\n" - " /// @title Just a test contract\n" - "contract test {\n" - " /// @dev Mul function\n" - " /// @title I really should not be here\n" - " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" - "}\n"; - - compileRequireError(sourceCode, Error::Type::DocstringParsingError); -} - -BOOST_AUTO_TEST_CASE(dev_documenting_nonexistant_param) -{ - char const* sourceCode = "contract test {\n" - " /// @dev Multiplies a number by 7 and adds second parameter\n" - " /// @param a Documentation for the first parameter\n" - " /// @param not_existing Documentation for the second parameter\n" - " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" - "}\n"; - - compileRequireError(sourceCode, Error::Type::DocstringParsingError); -} - - BOOST_AUTO_TEST_CASE(storage_array_ref) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index ee67dd66..8c0c2098 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -63,6 +63,12 @@ public: ); } + void expectNatspecError(std::string const& _code) + { + BOOST_CHECK(!m_compilerStack.parse(_code)); + BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError)); + } + private: CompilerStack m_compilerStack; Json::Reader m_reader; @@ -543,6 +549,31 @@ BOOST_AUTO_TEST_CASE(empty_comment) checkNatspec(sourceCode, natspec, true); } +BOOST_AUTO_TEST_CASE(dev_title_at_function_error) +{ + char const* sourceCode = " /// @author Lefteris\n" + " /// @title Just a test contract\n" + "contract test {\n" + " /// @dev Mul function\n" + " /// @title I really should not be here\n" + " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" + "}\n"; + + expectNatspecError(sourceCode); +} + +BOOST_AUTO_TEST_CASE(dev_documenting_nonexistant_param) +{ + char const* sourceCode = "contract test {\n" + " /// @dev Multiplies a number by 7 and adds second parameter\n" + " /// @param a Documentation for the first parameter\n" + " /// @param not_existing Documentation for the second parameter\n" + " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n" + "}\n"; + + expectNatspecError(sourceCode); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/solidityExecutionFramework.h b/test/libsolidity/solidityExecutionFramework.h index ed317d2f..4da02eb2 100644 --- a/test/libsolidity/solidityExecutionFramework.h +++ b/test/libsolidity/solidityExecutionFramework.h @@ -67,28 +67,6 @@ public: return m_output; } - void compileRequireError(std::string const& _sourceCode, Error::Type _type) - { - m_compiler.reset(false, m_addStandardSources); - m_compiler.addSource("", _sourceCode); - bool foundError = false; - try - { - m_compiler.compile(m_optimize, m_optimizeRuns); - BOOST_REQUIRE(Error::containsErrorOfType(m_compiler.errors(), _type)); - } - catch (Error const& _e) - { - BOOST_REQUIRE(_e.type() == _type); - foundError = true; - } - catch (Exception const& _exception) - { - BOOST_REQUIRE(false); - } - BOOST_REQUIRE(foundError); - } - bytes const& compileAndRun( std::string const& _sourceCode, u256 const& _value = 0, -- cgit