mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
NatSpec: Implement `@inheritdoc
`
This commit is contained in:
parent
6a1b1283fd
commit
ba0a4de50d
@ -7,6 +7,7 @@ Compiler Features:
|
||||
* Code Generator: Evaluate ``keccak256`` of string literals at compile-time.
|
||||
* Peephole Optimizer: Remove unnecessary masking of tags.
|
||||
* Yul EVM Code Transform: Free stack slots directly after visiting the right-hand-side of variable declarations instead of at the end of the statement only.
|
||||
* NatSpec: Implement tag ``@inheritdoc`` to copy documentation from a specific contract.
|
||||
|
||||
Bugfixes:
|
||||
* SMTChecker: Fix internal error when using bitwise operators on fixed bytes type.
|
||||
|
@ -49,7 +49,7 @@ The following example shows a contract and a function using all available tags.
|
||||
.. code:: solidity
|
||||
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
pragma solidity >=0.5.0 <0.7.0;
|
||||
pragma solidity >0.6.10 <0.7.0;
|
||||
|
||||
/// @title A simulator for trees
|
||||
/// @author Larry A. Gardner
|
||||
@ -60,9 +60,33 @@ The following example shows a contract and a function using all available tags.
|
||||
/// @dev The Alexandr N. Tetearing algorithm could increase precision
|
||||
/// @param rings The number of rings from dendrochronological sample
|
||||
/// @return age in years, rounded up for partial years
|
||||
function age(uint256 rings) external pure returns (uint256) {
|
||||
function age(uint256 rings) external virtual pure returns (uint256) {
|
||||
return rings + 1;
|
||||
}
|
||||
|
||||
/// @notice Returns the amount of leaves the tree has.
|
||||
/// @dev Returns only a fixed number.
|
||||
function leaves() external virtual pure returns(uint256) {
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
contract Plant {
|
||||
function leaves() external virtual pure returns(uint256) {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
contract KumquatTree is Tree, Plant {
|
||||
function age(uint256 rings) external override pure returns (uint256) {
|
||||
return rings + 2;
|
||||
}
|
||||
|
||||
/// Return the amount of leaves that this specific kind of tree has
|
||||
/// @inheritdoc Tree
|
||||
function leaves() external override(Tree, Plant) pure returns(uint256) {
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
.. _header-tags:
|
||||
@ -75,16 +99,17 @@ NatSpec tag and where it may be used. As a special case, if no tags are
|
||||
used then the Solidity compiler will interpret a ``///`` or ``/**`` comment
|
||||
in the same way as if it were tagged with ``@notice``.
|
||||
|
||||
=========== =============================================================================== =============================
|
||||
Tag Context
|
||||
=========== =============================================================================== =============================
|
||||
``@title`` A title that should describe the contract/interface contract, interface
|
||||
``@author`` The name of the author contract, interface
|
||||
``@notice`` Explain to an end user what this does contract, interface, function, public state variable, event
|
||||
``@dev`` Explain to a developer any extra details contract, interface, function, state variable, event
|
||||
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function, event
|
||||
``@return`` Documents the return variables of a contract's function function, public state variable
|
||||
=========== =============================================================================== =============================
|
||||
=============== ====================================================================================== =============================
|
||||
Tag Context
|
||||
=============== ====================================================================================== =============================
|
||||
``@title`` A title that should describe the contract/interface contract, interface
|
||||
``@author`` The name of the author contract, interface
|
||||
``@notice`` Explain to an end user what this does contract, interface, function, public state variable, event
|
||||
``@dev`` Explain to a developer any extra details contract, interface, function, state variable, event
|
||||
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function, event
|
||||
``@return`` Documents the return variables of a contract's function function, public state variable
|
||||
``@inheritdoc`` Copies all missing tags from the base function (must be followed by the contract name) function, public state variable
|
||||
=============== ====================================================================================== =============================
|
||||
|
||||
If your function returns multiple values, like ``(int quotient, int remainder)``
|
||||
then use multiple ``@return`` statements in the same format as the
|
||||
@ -127,6 +152,7 @@ base function. Exceptions to this are:
|
||||
|
||||
* When the parameter names are different.
|
||||
* When there is more than one base function.
|
||||
* When there is an explicit ``@inheritdoc`` tag which specifies which contract should be used to inherit.
|
||||
|
||||
.. _header-output:
|
||||
|
||||
|
@ -16,6 +16,8 @@ set(sources
|
||||
analysis/DeclarationTypeChecker.h
|
||||
analysis/DocStringAnalyser.cpp
|
||||
analysis/DocStringAnalyser.h
|
||||
analysis/DocStringTagParser.cpp
|
||||
analysis/DocStringTagParser.h
|
||||
analysis/ImmutableValidator.cpp
|
||||
analysis/ImmutableValidator.h
|
||||
analysis/GlobalContext.cpp
|
||||
|
@ -53,6 +53,17 @@ void copyMissingTags(StructurallyDocumentedAnnotation& _target, set<CallableDecl
|
||||
_target.docTags.emplace(tag, content);
|
||||
}
|
||||
|
||||
CallableDeclaration const* findBaseCallable(set<CallableDeclaration const*> const& _baseFunctions, int64_t _contractId)
|
||||
{
|
||||
for (CallableDeclaration const* baseFuncCandidate: _baseFunctions)
|
||||
if (baseFuncCandidate->annotation().contract->id() == _contractId)
|
||||
return baseFuncCandidate;
|
||||
else if (auto callable = findBaseCallable(baseFuncCandidate->annotation().baseFunctions, _contractId))
|
||||
return callable;
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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(); });
|
||||
@ -67,50 +78,23 @@ bool DocStringAnalyser::analyseDocStrings(SourceUnit const& _sourceUnit)
|
||||
return errorWatcher.ok();
|
||||
}
|
||||
|
||||
bool DocStringAnalyser::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
static set<string> const validTags = set<string>{"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
|
||||
if (!_function.isConstructor())
|
||||
handleCallable(_function, _function, _function.annotation());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
|
||||
{
|
||||
if (_variable.isStateVariable())
|
||||
{
|
||||
static set<string> const validPublicTags = set<string>{"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.isStateVariable())
|
||||
return false;
|
||||
|
||||
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))
|
||||
copyMissingTags(_variable.annotation(), {baseFunction});
|
||||
else if (_variable.annotation().docTags.empty())
|
||||
copyMissingTags(_variable.annotation(), _variable.annotation().baseFunctions);
|
||||
|
||||
if (_variable.annotation().docTags.empty())
|
||||
copyMissingTags(_variable.annotation(), _variable.annotation().baseFunctions);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -128,133 +112,41 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
|
||||
return true;
|
||||
}
|
||||
|
||||
void DocStringAnalyser::checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
)
|
||||
{
|
||||
set<string> 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<string> const validTags = set<string>{"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<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
||||
parseDocStrings(_node, _annotation, validTags, "functions");
|
||||
checkParameters(_callable, _node, _annotation);
|
||||
|
||||
if (
|
||||
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_callable.annotation().baseFunctions, _node, _annotation))
|
||||
copyMissingTags(_annotation, {baseFunction});
|
||||
else 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(
|
||||
CallableDeclaration const* DocStringAnalyser::resolveInheritDoc(
|
||||
set<CallableDeclaration const*> const& _baseFuncs,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation,
|
||||
set<string> const& _validTags,
|
||||
string const& _nodeName
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
)
|
||||
{
|
||||
DocStringParser parser;
|
||||
if (_node.documentation() && !_node.documentation()->text()->empty())
|
||||
{
|
||||
parser.parse(*_node.documentation()->text(), m_errorReporter);
|
||||
_annotation.docTags = parser.tags();
|
||||
}
|
||||
if (_annotation.inheritdocReference == nullptr)
|
||||
return nullptr;
|
||||
|
||||
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<VariableDeclaration const*>(&_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<FunctionDefinition const*>(&_node))
|
||||
{
|
||||
string content = docTag.second.content;
|
||||
string firstWord = content.substr(0, content.find_first_of(" \t"));
|
||||
if (auto const callable = findBaseCallable(_baseFuncs, _annotation.inheritdocReference->id()))
|
||||
return callable;
|
||||
|
||||
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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
m_errorReporter.docstringParsingError(
|
||||
4682_error,
|
||||
_node.documentation()->location(),
|
||||
"Documentation tag @inheritdoc references contract \"" +
|
||||
_annotation.inheritdocReference->name() +
|
||||
"\", but the contract does not contain a function that is overridden by this function."
|
||||
);
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -15,12 +15,6 @@
|
||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
/**
|
||||
* @author Christian <c@ethdev.com>
|
||||
* @date 2015
|
||||
* Parses and analyses the doc strings.
|
||||
* Stores the parsing results in the AST annotations and reports errors.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
@ -35,7 +29,7 @@ namespace solidity::frontend
|
||||
{
|
||||
|
||||
/**
|
||||
* Parses and analyses the doc strings.
|
||||
* Analyses and validates the doc strings.
|
||||
* Stores the parsing results in the AST annotations and reports errors.
|
||||
*/
|
||||
class DocStringAnalyser: private ASTConstVisitor
|
||||
@ -45,20 +39,13 @@ public:
|
||||
bool analyseDocStrings(SourceUnit const& _sourceUnit);
|
||||
|
||||
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;
|
||||
|
||||
void checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void handleConstructor(
|
||||
CallableDeclaration const& _callable,
|
||||
CallableDeclaration const* resolveInheritDoc(
|
||||
std::set<CallableDeclaration const*> const& _baseFunctions,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
@ -69,19 +56,6 @@ private:
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void handleDeclaration(
|
||||
Declaration const& _declaration,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void parseDocStrings(
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation,
|
||||
std::set<std::string> const& _validTags,
|
||||
std::string const& _nodeName
|
||||
);
|
||||
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
};
|
||||
|
||||
|
227
libsolidity/analysis/DocStringTagParser.cpp
Normal file
227
libsolidity/analysis/DocStringTagParser.cpp
Normal file
@ -0,0 +1,227 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* @author Christian <c@ethdev.com>
|
||||
* @date 2015
|
||||
* Parses and analyses the doc strings.
|
||||
* Stores the parsing results in the AST annotations and reports errors.
|
||||
*/
|
||||
|
||||
#include <libsolidity/analysis/DocStringTagParser.h>
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/parsing/DocStringParser.h>
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
|
||||
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<string> const validTags = set<string>{"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())
|
||||
{
|
||||
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "title", "author", "inheritdoc"};
|
||||
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."
|
||||
);
|
||||
}
|
||||
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<string> 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<string> const validTags = set<string>{"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<string> const validEventTags = set<string>{"author", "dev", "notice", "return", "param"};
|
||||
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param", "inheritdoc"};
|
||||
|
||||
if (dynamic_cast<EventDefinition const*>(&_callable))
|
||||
parseDocStrings(_node, _annotation, validEventTags, "events");
|
||||
else
|
||||
parseDocStrings(_node, _annotation, validTags, "functions");
|
||||
|
||||
checkParameters(_callable, _node, _annotation);
|
||||
|
||||
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 DocStringTagParser::parseDocStrings(
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation,
|
||||
set<string> 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 const* varDecl = dynamic_cast<VariableDeclaration const*>(&_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 const* function = dynamic_cast<FunctionDefinition const*>(&_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."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
76
libsolidity/analysis/DocStringTagParser.h
Normal file
76
libsolidity/analysis/DocStringTagParser.h
Normal file
@ -0,0 +1,76 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/ASTVisitor.h>
|
||||
|
||||
namespace solidity::langutil
|
||||
{
|
||||
class ErrorReporter;
|
||||
}
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
/**
|
||||
* Parses the doc tags and does basic validity checks.
|
||||
* Stores the parsing results in the AST annotations and reports errors.
|
||||
*/
|
||||
class DocStringTagParser: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
explicit DocStringTagParser(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
|
||||
bool parseDocStrings(SourceUnit const& _sourceUnit);
|
||||
|
||||
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;
|
||||
|
||||
void checkParameters(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void handleConstructor(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void handleCallable(
|
||||
CallableDeclaration const& _callable,
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation
|
||||
);
|
||||
|
||||
void parseDocStrings(
|
||||
StructurallyDocumented const& _node,
|
||||
StructurallyDocumentedAnnotation& _annotation,
|
||||
std::set<std::string> const& _validTags,
|
||||
std::string const& _nodeName
|
||||
);
|
||||
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
};
|
||||
|
||||
}
|
@ -36,6 +36,7 @@
|
||||
#include <libsolutil/StringUtils.h>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace solidity::langutil;
|
||||
@ -106,6 +107,14 @@ void ReferencesResolver::endVisit(VariableDeclarationStatement const& _varDeclSt
|
||||
m_resolver.activateVariable(var->name());
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(VariableDeclaration const& _varDecl)
|
||||
{
|
||||
if (_varDecl.documentation())
|
||||
resolveInheritDoc(*_varDecl.documentation(), _varDecl.annotation());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(Identifier const& _identifier)
|
||||
{
|
||||
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name());
|
||||
@ -132,6 +141,10 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
|
||||
bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
|
||||
{
|
||||
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
|
||||
|
||||
if (_functionDefinition.documentation())
|
||||
resolveInheritDoc(*_functionDefinition.documentation(), _functionDefinition.annotation());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -141,9 +154,13 @@ void ReferencesResolver::endVisit(FunctionDefinition const&)
|
||||
m_returnParameters.pop_back();
|
||||
}
|
||||
|
||||
bool ReferencesResolver::visit(ModifierDefinition const&)
|
||||
bool ReferencesResolver::visit(ModifierDefinition const& _modifierDefinition)
|
||||
{
|
||||
m_returnParameters.push_back(nullptr);
|
||||
|
||||
if (_modifierDefinition.documentation())
|
||||
resolveInheritDoc(*_modifierDefinition.documentation(), _modifierDefinition.annotation());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -284,4 +301,53 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
|
||||
visit(*_varDecl.value);
|
||||
}
|
||||
|
||||
void ReferencesResolver::resolveInheritDoc(StructuredDocumentation const& _documentation, StructurallyDocumentedAnnotation& _annotation)
|
||||
{
|
||||
switch (_annotation.docTags.count("inheritdoc"))
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case 1:
|
||||
{
|
||||
string const& name = _annotation.docTags.find("inheritdoc")->second.content;
|
||||
vector<string> path;
|
||||
boost::split(path, name, boost::is_any_of("."));
|
||||
Declaration const* result = m_resolver.pathFromCurrentScope(path);
|
||||
|
||||
if (result == nullptr)
|
||||
{
|
||||
m_errorReporter.docstringParsingError(
|
||||
9397_error,
|
||||
_documentation.location(),
|
||||
"Documentation tag @inheritdoc references inexistent contract \"" +
|
||||
name +
|
||||
"\"."
|
||||
);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_annotation.inheritdocReference = dynamic_cast<ContractDefinition const*>(result);
|
||||
|
||||
if (!_annotation.inheritdocReference)
|
||||
m_errorReporter.docstringParsingError(
|
||||
1430_error,
|
||||
_documentation.location(),
|
||||
"Documentation tag @inheritdoc reference \"" +
|
||||
name +
|
||||
"\" is not a contract."
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
m_errorReporter.docstringParsingError(
|
||||
5142_error,
|
||||
_documentation.location(),
|
||||
"Documentation tag @inheritdoc can only be given once."
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -76,6 +76,7 @@ private:
|
||||
bool visit(ForStatement const& _for) override;
|
||||
void endVisit(ForStatement const& _for) override;
|
||||
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
||||
bool visit(VariableDeclaration const& _varDecl) override;
|
||||
bool visit(Identifier const& _identifier) override;
|
||||
bool visit(FunctionDefinition const& _functionDefinition) override;
|
||||
void endVisit(FunctionDefinition const& _functionDefinition) override;
|
||||
@ -89,6 +90,8 @@ private:
|
||||
void operator()(yul::Identifier const& _identifier) override;
|
||||
void operator()(yul::VariableDeclaration const& _varDecl) override;
|
||||
|
||||
void resolveInheritDoc(StructuredDocumentation const& _documentation, StructurallyDocumentedAnnotation& _annotation);
|
||||
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
NameAndTypeResolver& m_resolver;
|
||||
langutil::EVMVersion m_evmVersion;
|
||||
|
@ -79,6 +79,8 @@ struct StructurallyDocumentedAnnotation
|
||||
|
||||
/// Mapping docstring tag name -> content.
|
||||
std::multimap<std::string, DocTag> docTags;
|
||||
/// contract that @inheritdoc references if it exists
|
||||
ContractDefinition const* inheritdocReference = nullptr;
|
||||
};
|
||||
|
||||
struct SourceUnitAnnotation: ASTAnnotation
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include <libsolidity/analysis/ContractLevelChecker.h>
|
||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||
#include <libsolidity/analysis/DocStringAnalyser.h>
|
||||
#include <libsolidity/analysis/DocStringTagParser.h>
|
||||
#include <libsolidity/analysis/GlobalContext.h>
|
||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||
#include <libsolidity/analysis/PostTypeChecker.h>
|
||||
@ -308,6 +309,11 @@ bool CompilerStack::analyze()
|
||||
if (source->ast && !syntaxChecker.checkSyntax(*source->ast))
|
||||
noErrors = false;
|
||||
|
||||
DocStringTagParser DocStringTagParser(m_errorReporter);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast && !DocStringTagParser.parseDocStrings(*source->ast))
|
||||
noErrors = false;
|
||||
|
||||
m_globalContext = make_shared<GlobalContext>();
|
||||
// We need to keep the same resolver during the whole process.
|
||||
NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_errorReporter);
|
||||
@ -363,6 +369,7 @@ bool CompilerStack::analyze()
|
||||
if (!contractLevelChecker.check(*contract))
|
||||
noErrors = false;
|
||||
|
||||
// Requires ContractLevelChecker
|
||||
DocStringAnalyser docStringAnalyser(m_errorReporter);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast))
|
||||
|
@ -58,28 +58,22 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
|
||||
for (auto const& it: _contractDef.interfaceFunctions())
|
||||
if (it.second->hasDeclaration())
|
||||
{
|
||||
string value;
|
||||
|
||||
if (auto const* f = dynamic_cast<FunctionDefinition const*>(&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;
|
||||
}
|
||||
}
|
||||
value = extractDoc(f->annotation().docTags, "notice");
|
||||
else if (auto var = dynamic_cast<VariableDeclaration const*>(&it.second->declaration()))
|
||||
{
|
||||
solAssert(var->isStateVariable() && var->isPublic(), "");
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +77,7 @@ public:
|
||||
BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError));
|
||||
}
|
||||
|
||||
private:
|
||||
protected:
|
||||
CompilerStack m_compilerStack;
|
||||
};
|
||||
|
||||
@ -1277,13 +1277,7 @@ BOOST_AUTO_TEST_CASE(dev_default_inherit_variable)
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"x()":
|
||||
{
|
||||
"details": "test"
|
||||
}
|
||||
}
|
||||
"methods": { "x()": { "details": "test" } }
|
||||
})ABCDEF";
|
||||
|
||||
char const *natspec1 = R"ABCDEF({
|
||||
@ -1318,13 +1312,80 @@ BOOST_AUTO_TEST_CASE(user_default_inherit_variable)
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"x()":
|
||||
{
|
||||
"notice": "Hello world"
|
||||
"methods": { "x()": { "notice": "Hello world" } }
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "C", natspec, true);
|
||||
checkNatspec(sourceCode, "D", natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_explicit_inherit_variable)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
contract B {
|
||||
function x() virtual external returns (uint) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
contract C {
|
||||
/// @notice Hello world
|
||||
/// @dev test
|
||||
function x() virtual external returns (uint) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
contract D is C, B {
|
||||
/// @inheritdoc C
|
||||
uint public override(C, B) x;
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods": { "x()": { "details": "test" } }
|
||||
})ABCDEF";
|
||||
|
||||
char const *natspec1 = R"ABCDEF({
|
||||
"methods" : {},
|
||||
"stateVariables" :
|
||||
{
|
||||
"x" :
|
||||
{
|
||||
"details" : "test"
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "C", natspec, false);
|
||||
checkNatspec(sourceCode, "D", natspec1, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_explicit_inherit_variable)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
contract B {
|
||||
function x() virtual external returns (uint) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
contract C {
|
||||
/// @notice Hello world
|
||||
/// @dev test
|
||||
function x() virtual external returns (uint) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
contract D is C, B {
|
||||
/// @inheritdoc C
|
||||
uint public override(C, B) x;
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods": { "x()": { "notice": "Hello world" } }
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "C", natspec, true);
|
||||
@ -1423,6 +1484,411 @@ BOOST_AUTO_TEST_CASE(user_default_inherit)
|
||||
checkNatspec(sourceCode, "Token", natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_explicit_inherit)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
contract ERC21 {
|
||||
function transfer(address to, uint amount) virtual external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
contract Token is ERC21, ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
function transfer(address to, uint amount) override(ERC21, ERC20) external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"author": "Programmer",
|
||||
"details": "test",
|
||||
"params":
|
||||
{
|
||||
"amount": "amount to transfer",
|
||||
"to": "address to transfer to"
|
||||
}
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "ERC20", natspec, false);
|
||||
checkNatspec(sourceCode, "Token", natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_explicit_inherit)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
contract ERC21 {
|
||||
function transfer(address to, uint amount) virtual external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
contract Token is ERC21, ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
function transfer(address to, uint amount) override(ERC21, ERC20) external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"notice": "Transfer ``amount`` from ``msg.sender`` to ``to``."
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "ERC20", natspec, true);
|
||||
checkNatspec(sourceCode, "Token", natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_explicit_inherit2)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
contract ERC21 is ERC20 {
|
||||
function transfer(address to, uint amount) virtual override external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
contract Token is ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
function transfer(address to, uint amount) override external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"author": "Programmer",
|
||||
"details": "test",
|
||||
"params":
|
||||
{
|
||||
"amount": "amount to transfer",
|
||||
"to": "address to transfer to"
|
||||
}
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "ERC20", natspec, false);
|
||||
checkNatspec(sourceCode, "ERC21", natspec, false);
|
||||
checkNatspec(sourceCode, "Token", natspec, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_explicit_inherit2)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
contract ERC21 is ERC20 {
|
||||
function transfer(address to, uint amount) virtual override external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
contract Token is ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
function transfer(address to, uint amount) override external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"notice": "Transfer ``amount`` from ``msg.sender`` to ``to``."
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "ERC20", natspec, true);
|
||||
checkNatspec(sourceCode, "ERC21", natspec, true);
|
||||
checkNatspec(sourceCode, "Token", natspec, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_explicit_inherit_partial2)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
contract ERC21 is ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
/// @dev override dev comment
|
||||
/// @notice override notice
|
||||
function transfer(address to, uint amount) virtual override external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
contract Token is ERC21 {
|
||||
function transfer(address to, uint amount) override external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"author": "Programmer",
|
||||
"details": "test",
|
||||
"params":
|
||||
{
|
||||
"amount": "amount to transfer",
|
||||
"to": "address to transfer to"
|
||||
}
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
char const *natspec2 = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"author": "Programmer",
|
||||
"details": "override dev comment",
|
||||
"params":
|
||||
{
|
||||
"amount": "amount to transfer",
|
||||
"to": "address to transfer to"
|
||||
}
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "ERC20", natspec, false);
|
||||
checkNatspec(sourceCode, "Token", natspec2, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_explicit_inherit_partial2)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
contract ERC21 is ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
/// @dev override dev comment
|
||||
/// @notice override notice
|
||||
function transfer(address to, uint amount) virtual override external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
contract Token is ERC21 {
|
||||
function transfer(address to, uint amount) override external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"notice": "Transfer ``amount`` from ``msg.sender`` to ``to``."
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
char const *natspec2 = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"notice": "override notice"
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "ERC20", natspec, true);
|
||||
checkNatspec(sourceCode, "Token", natspec2, true);
|
||||
}
|
||||
BOOST_AUTO_TEST_CASE(dev_explicit_inherit_partial)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
contract ERC21 {
|
||||
function transfer(address to, uint amount) virtual external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
contract Token is ERC21, ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
/// @dev override dev comment
|
||||
/// @notice override notice
|
||||
function transfer(address to, uint amount) override(ERC21, ERC20) external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"author": "Programmer",
|
||||
"details": "test",
|
||||
"params":
|
||||
{
|
||||
"amount": "amount to transfer",
|
||||
"to": "address to transfer to"
|
||||
}
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
char const *natspec2 = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"author": "Programmer",
|
||||
"details": "override dev comment",
|
||||
"params":
|
||||
{
|
||||
"amount": "amount to transfer",
|
||||
"to": "address to transfer to"
|
||||
}
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "ERC20", natspec, false);
|
||||
checkNatspec(sourceCode, "Token", natspec2, false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(user_explicit_inherit_partial)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
contract ERC21 {
|
||||
function transfer(address to, uint amount) virtual external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
contract Token is ERC21, ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
/// @dev override dev comment
|
||||
/// @notice override notice
|
||||
function transfer(address to, uint amount) override(ERC21, ERC20) external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"notice": "Transfer ``amount`` from ``msg.sender`` to ``to``."
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
char const *natspec2 = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"notice": "override notice"
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
checkNatspec(sourceCode, "ERC20", natspec, true);
|
||||
checkNatspec(sourceCode, "Token", natspec2, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_inherit_parameter_mismatch)
|
||||
{
|
||||
char const *sourceCode = R"(
|
||||
@ -1517,6 +1983,80 @@ BOOST_AUTO_TEST_CASE(user_inherit_parameter_mismatch)
|
||||
checkNatspec(sourceCode, "Token", natspec2, true);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(dev_explicit_inehrit_complex)
|
||||
{
|
||||
char const *sourceCode1 = R"(
|
||||
interface ERC20 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer
|
||||
/// @dev test
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
|
||||
interface ERC21 {
|
||||
/// Transfer ``amount`` from ``msg.sender`` to ``to``.
|
||||
/// @author Programmer2
|
||||
/// @dev test2
|
||||
/// @param to address to transfer to
|
||||
/// @param amount amount to transfer
|
||||
function transfer(address to, uint amount) external returns (bool);
|
||||
}
|
||||
)";
|
||||
|
||||
char const *sourceCode2 = R"(
|
||||
import "Interfaces.sol" as myInterfaces;
|
||||
|
||||
contract Token is myInterfaces.ERC20, myInterfaces.ERC21 {
|
||||
/// @inheritdoc myInterfaces.ERC20
|
||||
function transfer(address too, uint amount)
|
||||
override(myInterfaces.ERC20, myInterfaces.ERC21) external returns (bool) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
char const *natspec = R"ABCDEF({
|
||||
"methods":
|
||||
{
|
||||
"transfer(address,uint256)":
|
||||
{
|
||||
"author": "Programmer",
|
||||
"details": "test",
|
||||
"params":
|
||||
{
|
||||
"amount": "amount to transfer",
|
||||
"to": "address to transfer to"
|
||||
}
|
||||
}
|
||||
}
|
||||
})ABCDEF";
|
||||
|
||||
m_compilerStack.reset();
|
||||
m_compilerStack.setSources({
|
||||
{"Interfaces.sol", "pragma solidity >=0.0;\n" + std::string(sourceCode1)},
|
||||
{"Testfile.sol", "pragma solidity >=0.0;\n" + std::string(sourceCode2)}
|
||||
});
|
||||
|
||||
m_compilerStack.setEVMVersion(solidity::test::CommonOptions::get().evmVersion());
|
||||
|
||||
BOOST_REQUIRE_MESSAGE(m_compilerStack.parseAndAnalyze(), "Parsing contract failed");
|
||||
|
||||
Json::Value generatedDocumentation = m_compilerStack.natspecDev("Token");
|
||||
Json::Value expectedDocumentation;
|
||||
util::jsonParseStrict(natspec, expectedDocumentation);
|
||||
|
||||
expectedDocumentation["version"] = Json::Value(Natspec::c_natspecVersion);
|
||||
expectedDocumentation["kind"] = Json::Value("dev");
|
||||
|
||||
BOOST_CHECK_MESSAGE(
|
||||
expectedDocumentation == generatedDocumentation,
|
||||
"Expected:\n" << expectedDocumentation.toStyledString() <<
|
||||
"\n but got:\n" << generatedDocumentation.toStyledString()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
@ -0,0 +1,7 @@
|
||||
contract C {
|
||||
/// @inheritdoc X
|
||||
function f() internal {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// DocstringParsingError 9397: (17-34): Documentation tag @inheritdoc references inexistent contract "X".
|
@ -0,0 +1,10 @@
|
||||
contract D {
|
||||
}
|
||||
|
||||
contract C is D {
|
||||
/// @inheritdoc D
|
||||
function f() internal {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// DocstringParsingError 4682: (38-55): Documentation tag @inheritdoc references contract "D", but the contract does not contain a function that is overridden by this function.
|
@ -0,0 +1,11 @@
|
||||
contract D {
|
||||
struct S { uint a; }
|
||||
}
|
||||
|
||||
contract C is D {
|
||||
/// @inheritdoc D.S
|
||||
function f() internal {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// DocstringParsingError 1430: (63-82): Documentation tag @inheritdoc reference "D.S" is not a contract.
|
@ -0,0 +1,8 @@
|
||||
contract C {
|
||||
struct S { uint a; }
|
||||
/// @inheritdoc S
|
||||
function f() internal {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// DocstringParsingError 1430: (42-59): Documentation tag @inheritdoc reference "S" is not a contract.
|
@ -0,0 +1,17 @@
|
||||
contract ERC20 {
|
||||
/// @notice This event is emitted when a transfer occurs.
|
||||
/// @param from The source account.
|
||||
/// @param to The destination account.
|
||||
/// @param amount The amount.
|
||||
/// @dev A test case!
|
||||
event Transfer(address indexed from, address indexed to, uint amount);
|
||||
}
|
||||
|
||||
contract A is ERC20 {
|
||||
/// @inheritdoc ERC20
|
||||
event Transfer();
|
||||
}
|
||||
|
||||
|
||||
// ----
|
||||
// DocstringParsingError 6546: (305-326): Documentation tag @inheritdoc not valid for events.
|
Loading…
Reference in New Issue
Block a user