/* 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 using namespace std; using namespace solidity; using namespace solidity::langutil; using namespace solidity::frontend; namespace { void copyMissingTags(StructurallyDocumentedAnnotation& _target, set const& _baseFunctions) { if (_baseFunctions.size() != 1) return; auto& sourceDoc = dynamic_cast((*_baseFunctions.begin())->annotation()); set existingTags; for (auto const& iterator: _target.docTags) existingTags.insert(iterator.first); for (auto const& [tag, content]: sourceDoc.docTags) if (tag != "inheritdoc" && !existingTags.count(tag)) _target.docTags.emplace(tag, content); } bool parameterNamesEqual(CallableDeclaration const& _a, CallableDeclaration const& _b) { return boost::range::equal(_a.parameters(), _b.parameters(), [](auto const& pa, auto const& pb) { return pa->name() == pb->name(); }); } } bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit) { auto errorWatcher = m_errorReporter.errorWatcher(); _sourceUnit.accept(*this); return errorWatcher.ok(); } bool DocStringAnalyser::visit(ContractDefinition const& _contract) { static set const validTags = set{"author", "title", "dev", "notice"}; parseDocStrings(_contract, _contract.annotation(), validTags, "contracts"); return true; } bool DocStringAnalyser::visit(FunctionDefinition const& _function) { if (_function.isConstructor()) handleConstructor(_function, _function, _function.annotation()); else handleCallable(_function, _function, _function.annotation()); return true; } bool DocStringAnalyser::visit(VariableDeclaration const& _variable) { if (_variable.isStateVariable()) { static set const validPublicTags = set{"dev", "notice", "return", "title", "author"}; if (_variable.isPublic()) parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables"); else { parseDocStrings(_variable, _variable.annotation(), validPublicTags, "non-public state variables"); if (_variable.annotation().docTags.count("notice") > 0) m_errorReporter.warning( 7816_error, _variable.documentation()->location(), "Documentation tag on non-public state variables will be disallowed in 0.7.0. " "You will need to use the @dev tag explicitly." ); } if (_variable.annotation().docTags.count("title") > 0 || _variable.annotation().docTags.count("author") > 0) m_errorReporter.warning( 8532_error, _variable.documentation()->location(), "Documentation tag @title and @author is only allowed on contract definitions. " "It will be disallowed in 0.7.0." ); if (_variable.annotation().docTags.empty()) copyMissingTags(_variable.annotation(), _variable.annotation().baseFunctions); } return false; } bool DocStringAnalyser::visit(ModifierDefinition const& _modifier) { handleCallable(_modifier, _modifier, _modifier.annotation()); return true; } bool DocStringAnalyser::visit(EventDefinition const& _event) { handleCallable(_event, _event, _event.annotation()); return true; } void DocStringAnalyser::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 DocStringAnalyser::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 DocStringAnalyser::handleCallable( CallableDeclaration const& _callable, StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation ) { static set const validTags = set{"author", "dev", "notice", "return", "param"}; parseDocStrings(_node, _annotation, validTags, "functions"); checkParameters(_callable, _node, _annotation); if ( _annotation.docTags.empty() && _callable.annotation().baseFunctions.size() == 1 && parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin()) ) copyMissingTags(_annotation, _callable.annotation().baseFunctions); if (_node.documentation() && _annotation.docTags.count("author") > 0) m_errorReporter.warning( 9843_error, _node.documentation()->location(), "Documentation tag @author is only allowed on contract definitions. " "It will be disallowed in 0.7.0." ); } void DocStringAnalyser::parseDocStrings( StructurallyDocumented const& _node, StructurallyDocumentedAnnotation& _annotation, set const& _validTags, string const& _nodeName ) { DocStringParser parser; if (_node.documentation() && !_node.documentation()->text()->empty()) { parser.parse(*_node.documentation()->text(), m_errorReporter); _annotation.docTags = parser.tags(); } size_t returnTagsVisited = 0; for (auto const& docTag: _annotation.docTags) { if (!_validTags.count(docTag.first)) m_errorReporter.docstringParsingError( 6546_error, _node.documentation()->location(), "Documentation tag @" + docTag.first + " not valid for " + _nodeName + "." ); else if (docTag.first == "return") { returnTagsVisited++; if (auto* varDecl = dynamic_cast(&_node)) { if (!varDecl->isPublic()) m_errorReporter.docstringParsingError( 9440_error, _node.documentation()->location(), "Documentation tag \"@" + docTag.first + "\" is only allowed on public state-variables." ); if (returnTagsVisited > 1) m_errorReporter.docstringParsingError( 5256_error, _node.documentation()->location(), "Documentation tag \"@" + docTag.first + "\" is only allowed once on state-variables." ); } else if (auto* function = dynamic_cast(&_node)) { string content = docTag.second.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 \"@" + docTag.first + " " + docTag.second.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 \"@" + docTag.first + " " + docTag.second.content + "\"" + " does not contain the name of its return parameter." ); } } } } }