/* 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 . */ /** * @author Christian * @date 2015 * Parses and analyses the doc strings. * Stores the parsing results in the AST annotations and reports errors. */ #include #include #include #include #include #include #include #include #include using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; bool DocStringTagParser::parseDocStrings(SourceUnit const& _sourceUnit) { auto errorWatcher = m_errorReporter.errorWatcher(); _sourceUnit.accept(*this); return errorWatcher.ok(); } bool DocStringTagParser::visit(ContractDefinition const& _contract) { static set const validTags = set{"author", "title", "dev", "notice"}; parseDocStrings(_contract, _contract.annotation(), validTags, "contracts"); return true; } bool DocStringTagParser::visit(FunctionDefinition const& _function) { if (_function.isConstructor()) handleConstructor(_function, _function, _function.annotation()); else handleCallable(_function, _function, _function.annotation()); return true; } bool DocStringTagParser::visit(VariableDeclaration const& _variable) { if (_variable.isStateVariable()) { if (_variable.isPublic()) parseDocStrings(_variable, _variable.annotation(), {"dev", "notice", "return", "inheritdoc"}, "public state variables"); else parseDocStrings(_variable, _variable.annotation(), {"dev", "inheritdoc"}, "non-public state variables"); } else if (_variable.isFileLevelVariable()) parseDocStrings(_variable, _variable.annotation(), {"dev"}, "file-level variables"); return false; } bool DocStringTagParser::visit(ModifierDefinition const& _modifier) { handleCallable(_modifier, _modifier, _modifier.annotation()); return true; } bool DocStringTagParser::visit(EventDefinition const& _event) { handleCallable(_event, _event, _event.annotation()); return true; } void DocStringTagParser::checkParameters( CallableDeclaration const& _callable, StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation ) { 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)) m_errorReporter.docstringParsingError( 3881_error, _node.documentation()->location(), "Documented parameter \"" + i->second.paramName + "\" not found in the parameter list of the function." ); } void DocStringTagParser::handleConstructor( CallableDeclaration const& _callable, StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation ) { static set const validTags = set{"author", "dev", "notice", "param"}; parseDocStrings(_node, _annotation, validTags, "constructor"); checkParameters(_callable, _node, _annotation); } void DocStringTagParser::handleCallable( CallableDeclaration const& _callable, StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation ) { static set const validEventTags = set{"dev", "notice", "return", "param"}; static set const validModifierTags = set{"dev", "notice", "param", "inheritdoc"}; static set const validTags = set{"dev", "notice", "return", "param", "inheritdoc"}; if (dynamic_cast(&_callable)) parseDocStrings(_node, _annotation, validEventTags, "events"); else if (dynamic_cast(&_callable)) parseDocStrings(_node, _annotation, validModifierTags, "modifiers"); else parseDocStrings(_node, _annotation, validTags, "functions"); checkParameters(_callable, _node, _annotation); } void DocStringTagParser::parseDocStrings( StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation, set const& _validTags, string const& _nodeName ) { if (!_node.documentation()) return; _annotation.docTags = DocStringParser{*_node.documentation(), m_errorReporter}.parse(); size_t returnTagsVisited = 0; for (auto const& [tagName, tagValue]: _annotation.docTags) { string static const customPrefix("custom:"); if (tagName == "custom" || tagName == "custom:") m_errorReporter.docstringParsingError( 6564_error, _node.documentation()->location(), "Custom documentation tag must contain a chosen name, i.e. @custom:mytag." ); else if (boost::starts_with(tagName, customPrefix) && tagName.size() > customPrefix.size()) { regex static const customRegex("^custom:[a-z][a-z-]*$"); if (!regex_match(tagName, customRegex)) m_errorReporter.docstringParsingError( 2968_error, _node.documentation()->location(), "Invalid character in custom tag @" + tagName + ". Only lowercase letters and \"-\" are permitted." ); continue; } else if (!_validTags.count(tagName)) m_errorReporter.docstringParsingError( 6546_error, _node.documentation()->location(), "Documentation tag @" + tagName + " not valid for " + _nodeName + "." ); else if (tagName == "return") { returnTagsVisited++; if (auto const* varDecl = dynamic_cast(&_node)) { solAssert(varDecl->isPublic(), "@return is only allowed on public state-variables."); if (returnTagsVisited > 1) m_errorReporter.docstringParsingError( 5256_error, _node.documentation()->location(), "Documentation tag \"@" + tagName + "\" is only allowed once on state-variables." ); } else if (auto const* function = dynamic_cast(&_node)) { string content = tagValue.content; string firstWord = content.substr(0, content.find_first_of(" \t")); if (returnTagsVisited > function->returnParameters().size()) m_errorReporter.docstringParsingError( 2604_error, _node.documentation()->location(), "Documentation tag \"@" + tagName + " " + tagValue.content + "\"" + " exceeds the number of return parameters." ); else { auto parameter = function->returnParameters().at(returnTagsVisited - 1); if (!parameter->name().empty() && parameter->name() != firstWord) m_errorReporter.docstringParsingError( 5856_error, _node.documentation()->location(), "Documentation tag \"@" + tagName + " " + tagValue.content + "\"" + " does not contain the name of its return parameter." ); } } } } }