mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #9218 from ethereum/issue-8911
Natspec: Implement inheritance and @inheritdoc
This commit is contained in:
commit
e7f26c2320
@ -7,6 +7,7 @@ Compiler Features:
|
|||||||
* Code Generator: Evaluate ``keccak256`` of string literals at compile-time.
|
* Code Generator: Evaluate ``keccak256`` of string literals at compile-time.
|
||||||
* Peephole Optimizer: Remove unnecessary masking of tags.
|
* 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.
|
* 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:
|
Bugfixes:
|
||||||
* SMTChecker: Fix internal error when using bitwise operators on fixed bytes type.
|
* 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
|
.. code:: solidity
|
||||||
|
|
||||||
// SPDX-License-Identifier: GPL-3.0
|
// 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
|
/// @title A simulator for trees
|
||||||
/// @author Larry A. Gardner
|
/// @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
|
/// @dev The Alexandr N. Tetearing algorithm could increase precision
|
||||||
/// @param rings The number of rings from dendrochronological sample
|
/// @param rings The number of rings from dendrochronological sample
|
||||||
/// @return age in years, rounded up for partial years
|
/// @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;
|
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:
|
.. _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
|
used then the Solidity compiler will interpret a ``///`` or ``/**`` comment
|
||||||
in the same way as if it were tagged with ``@notice``.
|
in the same way as if it were tagged with ``@notice``.
|
||||||
|
|
||||||
=========== =============================================================================== =============================
|
=============== ====================================================================================== =============================
|
||||||
Tag Context
|
Tag Context
|
||||||
=========== =============================================================================== =============================
|
=============== ====================================================================================== =============================
|
||||||
``@title`` A title that should describe the contract/interface contract, interface
|
``@title`` A title that should describe the contract/interface contract, interface
|
||||||
``@author`` The name of the author 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
|
``@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
|
``@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
|
``@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
|
``@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)``
|
If your function returns multiple values, like ``(int quotient, int remainder)``
|
||||||
then use multiple ``@return`` statements in the same format as the
|
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 the parameter names are different.
|
||||||
* When there is more than one base function.
|
* 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:
|
.. _header-output:
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@ set(sources
|
|||||||
analysis/DeclarationTypeChecker.h
|
analysis/DeclarationTypeChecker.h
|
||||||
analysis/DocStringAnalyser.cpp
|
analysis/DocStringAnalyser.cpp
|
||||||
analysis/DocStringAnalyser.h
|
analysis/DocStringAnalyser.h
|
||||||
|
analysis/DocStringTagParser.cpp
|
||||||
|
analysis/DocStringTagParser.h
|
||||||
analysis/ImmutableValidator.cpp
|
analysis/ImmutableValidator.cpp
|
||||||
analysis/ImmutableValidator.h
|
analysis/ImmutableValidator.h
|
||||||
analysis/GlobalContext.cpp
|
analysis/GlobalContext.cpp
|
||||||
|
@ -53,6 +53,17 @@ void copyMissingTags(StructurallyDocumentedAnnotation& _target, set<CallableDecl
|
|||||||
_target.docTags.emplace(tag, content);
|
_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)
|
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(); });
|
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();
|
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)
|
bool DocStringAnalyser::visit(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
if (_function.isConstructor())
|
if (!_function.isConstructor())
|
||||||
handleConstructor(_function, _function, _function.annotation());
|
|
||||||
else
|
|
||||||
handleCallable(_function, _function, _function.annotation());
|
handleCallable(_function, _function, _function.annotation());
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
|
bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
|
||||||
{
|
{
|
||||||
if (_variable.isStateVariable())
|
if (!_variable.isStateVariable())
|
||||||
{
|
return false;
|
||||||
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "title", "author"};
|
|
||||||
if (_variable.isPublic())
|
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_variable.annotation().baseFunctions, _variable, _variable.annotation()))
|
||||||
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables");
|
copyMissingTags(_variable.annotation(), {baseFunction});
|
||||||
else
|
else if (_variable.annotation().docTags.empty())
|
||||||
{
|
copyMissingTags(_variable.annotation(), _variable.annotation().baseFunctions);
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,133 +112,41 @@ bool DocStringAnalyser::visit(EventDefinition const& _event)
|
|||||||
return true;
|
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(
|
void DocStringAnalyser::handleCallable(
|
||||||
CallableDeclaration const& _callable,
|
CallableDeclaration const& _callable,
|
||||||
StructurallyDocumented const& _node,
|
StructurallyDocumented const& _node,
|
||||||
StructurallyDocumentedAnnotation& _annotation
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
static set<string> const validTags = set<string>{"author", "dev", "notice", "return", "param"};
|
if (CallableDeclaration const* baseFunction = resolveInheritDoc(_callable.annotation().baseFunctions, _node, _annotation))
|
||||||
parseDocStrings(_node, _annotation, validTags, "functions");
|
copyMissingTags(_annotation, {baseFunction});
|
||||||
checkParameters(_callable, _node, _annotation);
|
else if (
|
||||||
|
|
||||||
if (
|
|
||||||
_annotation.docTags.empty() &&
|
_annotation.docTags.empty() &&
|
||||||
_callable.annotation().baseFunctions.size() == 1 &&
|
_callable.annotation().baseFunctions.size() == 1 &&
|
||||||
parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin())
|
parameterNamesEqual(_callable, **_callable.annotation().baseFunctions.begin())
|
||||||
)
|
)
|
||||||
copyMissingTags(_annotation, _callable.annotation().baseFunctions);
|
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,
|
StructurallyDocumented const& _node,
|
||||||
StructurallyDocumentedAnnotation& _annotation,
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
set<string> const& _validTags,
|
|
||||||
string const& _nodeName
|
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
DocStringParser parser;
|
if (_annotation.inheritdocReference == nullptr)
|
||||||
if (_node.documentation() && !_node.documentation()->text()->empty())
|
return nullptr;
|
||||||
{
|
|
||||||
parser.parse(*_node.documentation()->text(), m_errorReporter);
|
|
||||||
_annotation.docTags = parser.tags();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t returnTagsVisited = 0;
|
if (auto const callable = findBaseCallable(_baseFuncs, _annotation.inheritdocReference->id()))
|
||||||
for (auto const& docTag: _annotation.docTags)
|
return callable;
|
||||||
{
|
|
||||||
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 (returnTagsVisited > function->returnParameters().size())
|
m_errorReporter.docstringParsingError(
|
||||||
m_errorReporter.docstringParsingError(
|
4682_error,
|
||||||
2604_error,
|
_node.documentation()->location(),
|
||||||
_node.documentation()->location(),
|
"Documentation tag @inheritdoc references contract \"" +
|
||||||
"Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
|
_annotation.inheritdocReference->name() +
|
||||||
" exceeds the number of return parameters."
|
"\", but the contract does not contain a function that is overridden by this function."
|
||||||
);
|
);
|
||||||
else
|
|
||||||
{
|
return nullptr;
|
||||||
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."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,6 @@
|
|||||||
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
// SPDX-License-Identifier: GPL-3.0
|
// 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
|
#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.
|
* Stores the parsing results in the AST annotations and reports errors.
|
||||||
*/
|
*/
|
||||||
class DocStringAnalyser: private ASTConstVisitor
|
class DocStringAnalyser: private ASTConstVisitor
|
||||||
@ -45,20 +39,13 @@ public:
|
|||||||
bool analyseDocStrings(SourceUnit const& _sourceUnit);
|
bool analyseDocStrings(SourceUnit const& _sourceUnit);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool visit(ContractDefinition const& _contract) override;
|
|
||||||
bool visit(FunctionDefinition const& _function) override;
|
bool visit(FunctionDefinition const& _function) override;
|
||||||
bool visit(VariableDeclaration const& _variable) override;
|
bool visit(VariableDeclaration const& _variable) override;
|
||||||
bool visit(ModifierDefinition const& _modifier) override;
|
bool visit(ModifierDefinition const& _modifier) override;
|
||||||
bool visit(EventDefinition const& _event) override;
|
bool visit(EventDefinition const& _event) override;
|
||||||
|
|
||||||
void checkParameters(
|
CallableDeclaration const* resolveInheritDoc(
|
||||||
CallableDeclaration const& _callable,
|
std::set<CallableDeclaration const*> const& _baseFunctions,
|
||||||
StructurallyDocumented const& _node,
|
|
||||||
StructurallyDocumentedAnnotation& _annotation
|
|
||||||
);
|
|
||||||
|
|
||||||
void handleConstructor(
|
|
||||||
CallableDeclaration const& _callable,
|
|
||||||
StructurallyDocumented const& _node,
|
StructurallyDocumented const& _node,
|
||||||
StructurallyDocumentedAnnotation& _annotation
|
StructurallyDocumentedAnnotation& _annotation
|
||||||
);
|
);
|
||||||
@ -69,19 +56,6 @@ private:
|
|||||||
StructurallyDocumentedAnnotation& _annotation
|
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;
|
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 <libsolutil/StringUtils.h>
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
|
#include <boost/algorithm/string/split.hpp>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
@ -106,6 +107,14 @@ void ReferencesResolver::endVisit(VariableDeclarationStatement const& _varDeclSt
|
|||||||
m_resolver.activateVariable(var->name());
|
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)
|
bool ReferencesResolver::visit(Identifier const& _identifier)
|
||||||
{
|
{
|
||||||
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name());
|
auto declarations = m_resolver.nameFromCurrentScope(_identifier.name());
|
||||||
@ -132,6 +141,10 @@ bool ReferencesResolver::visit(Identifier const& _identifier)
|
|||||||
bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
|
bool ReferencesResolver::visit(FunctionDefinition const& _functionDefinition)
|
||||||
{
|
{
|
||||||
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
|
m_returnParameters.push_back(_functionDefinition.returnParameterList().get());
|
||||||
|
|
||||||
|
if (_functionDefinition.documentation())
|
||||||
|
resolveInheritDoc(*_functionDefinition.documentation(), _functionDefinition.annotation());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,9 +154,13 @@ void ReferencesResolver::endVisit(FunctionDefinition const&)
|
|||||||
m_returnParameters.pop_back();
|
m_returnParameters.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ReferencesResolver::visit(ModifierDefinition const&)
|
bool ReferencesResolver::visit(ModifierDefinition const& _modifierDefinition)
|
||||||
{
|
{
|
||||||
m_returnParameters.push_back(nullptr);
|
m_returnParameters.push_back(nullptr);
|
||||||
|
|
||||||
|
if (_modifierDefinition.documentation())
|
||||||
|
resolveInheritDoc(*_modifierDefinition.documentation(), _modifierDefinition.annotation());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,4 +301,53 @@ void ReferencesResolver::operator()(yul::VariableDeclaration const& _varDecl)
|
|||||||
visit(*_varDecl.value);
|
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;
|
bool visit(ForStatement const& _for) override;
|
||||||
void endVisit(ForStatement const& _for) override;
|
void endVisit(ForStatement const& _for) override;
|
||||||
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
void endVisit(VariableDeclarationStatement const& _varDeclStatement) override;
|
||||||
|
bool visit(VariableDeclaration const& _varDecl) override;
|
||||||
bool visit(Identifier const& _identifier) override;
|
bool visit(Identifier const& _identifier) override;
|
||||||
bool visit(FunctionDefinition const& _functionDefinition) override;
|
bool visit(FunctionDefinition const& _functionDefinition) override;
|
||||||
void endVisit(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::Identifier const& _identifier) override;
|
||||||
void operator()(yul::VariableDeclaration const& _varDecl) override;
|
void operator()(yul::VariableDeclaration const& _varDecl) override;
|
||||||
|
|
||||||
|
void resolveInheritDoc(StructuredDocumentation const& _documentation, StructurallyDocumentedAnnotation& _annotation);
|
||||||
|
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
langutil::ErrorReporter& m_errorReporter;
|
||||||
NameAndTypeResolver& m_resolver;
|
NameAndTypeResolver& m_resolver;
|
||||||
langutil::EVMVersion m_evmVersion;
|
langutil::EVMVersion m_evmVersion;
|
||||||
|
@ -79,6 +79,8 @@ struct StructurallyDocumentedAnnotation
|
|||||||
|
|
||||||
/// Mapping docstring tag name -> content.
|
/// Mapping docstring tag name -> content.
|
||||||
std::multimap<std::string, DocTag> docTags;
|
std::multimap<std::string, DocTag> docTags;
|
||||||
|
/// contract that @inheritdoc references if it exists
|
||||||
|
ContractDefinition const* inheritdocReference = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SourceUnitAnnotation: ASTAnnotation
|
struct SourceUnitAnnotation: ASTAnnotation
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
#include <libsolidity/analysis/ContractLevelChecker.h>
|
#include <libsolidity/analysis/ContractLevelChecker.h>
|
||||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||||
#include <libsolidity/analysis/DocStringAnalyser.h>
|
#include <libsolidity/analysis/DocStringAnalyser.h>
|
||||||
|
#include <libsolidity/analysis/DocStringTagParser.h>
|
||||||
#include <libsolidity/analysis/GlobalContext.h>
|
#include <libsolidity/analysis/GlobalContext.h>
|
||||||
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
#include <libsolidity/analysis/NameAndTypeResolver.h>
|
||||||
#include <libsolidity/analysis/PostTypeChecker.h>
|
#include <libsolidity/analysis/PostTypeChecker.h>
|
||||||
@ -308,6 +309,11 @@ bool CompilerStack::analyze()
|
|||||||
if (source->ast && !syntaxChecker.checkSyntax(*source->ast))
|
if (source->ast && !syntaxChecker.checkSyntax(*source->ast))
|
||||||
noErrors = false;
|
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>();
|
m_globalContext = make_shared<GlobalContext>();
|
||||||
// We need to keep the same resolver during the whole process.
|
// We need to keep the same resolver during the whole process.
|
||||||
NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_errorReporter);
|
NameAndTypeResolver resolver(*m_globalContext, m_evmVersion, m_errorReporter);
|
||||||
@ -363,6 +369,7 @@ bool CompilerStack::analyze()
|
|||||||
if (!contractLevelChecker.check(*contract))
|
if (!contractLevelChecker.check(*contract))
|
||||||
noErrors = false;
|
noErrors = false;
|
||||||
|
|
||||||
|
// Requires ContractLevelChecker
|
||||||
DocStringAnalyser docStringAnalyser(m_errorReporter);
|
DocStringAnalyser docStringAnalyser(m_errorReporter);
|
||||||
for (Source const* source: m_sourceOrder)
|
for (Source const* source: m_sourceOrder)
|
||||||
if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast))
|
if (source->ast && !docStringAnalyser.analyseDocStrings(*source->ast))
|
||||||
|
@ -58,28 +58,22 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
|
|||||||
for (auto const& it: _contractDef.interfaceFunctions())
|
for (auto const& it: _contractDef.interfaceFunctions())
|
||||||
if (it.second->hasDeclaration())
|
if (it.second->hasDeclaration())
|
||||||
{
|
{
|
||||||
|
string value;
|
||||||
|
|
||||||
if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
|
if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
|
||||||
{
|
value = extractDoc(f->annotation().docTags, "notice");
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (auto var = dynamic_cast<VariableDeclaration const*>(&it.second->declaration()))
|
else if (auto var = dynamic_cast<VariableDeclaration const*>(&it.second->declaration()))
|
||||||
{
|
{
|
||||||
solAssert(var->isStateVariable() && var->isPublic(), "");
|
solAssert(var->isStateVariable() && var->isPublic(), "");
|
||||||
string value = extractDoc(var->annotation().docTags, "notice");
|
value = extractDoc(var->annotation().docTags, "notice");
|
||||||
if (!value.empty())
|
}
|
||||||
{
|
|
||||||
Json::Value user;
|
if (!value.empty())
|
||||||
// since @notice is the only user tag if missing function should not appear
|
{
|
||||||
user["notice"] = Json::Value(value);
|
Json::Value user;
|
||||||
methods[it.second->externalSignature()] = 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));
|
BOOST_REQUIRE(Error::containsErrorOfType(m_compilerStack.errors(), Error::Type::DocstringParsingError));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
CompilerStack m_compilerStack;
|
CompilerStack m_compilerStack;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1277,13 +1277,7 @@ BOOST_AUTO_TEST_CASE(dev_default_inherit_variable)
|
|||||||
)";
|
)";
|
||||||
|
|
||||||
char const *natspec = R"ABCDEF({
|
char const *natspec = R"ABCDEF({
|
||||||
"methods":
|
"methods": { "x()": { "details": "test" } }
|
||||||
{
|
|
||||||
"x()":
|
|
||||||
{
|
|
||||||
"details": "test"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})ABCDEF";
|
})ABCDEF";
|
||||||
|
|
||||||
char const *natspec1 = R"ABCDEF({
|
char const *natspec1 = R"ABCDEF({
|
||||||
@ -1318,13 +1312,80 @@ BOOST_AUTO_TEST_CASE(user_default_inherit_variable)
|
|||||||
)";
|
)";
|
||||||
|
|
||||||
char const *natspec = R"ABCDEF({
|
char const *natspec = R"ABCDEF({
|
||||||
"methods":
|
"methods": { "x()": { "notice": "Hello world" } }
|
||||||
{
|
})ABCDEF";
|
||||||
"x()":
|
|
||||||
{
|
checkNatspec(sourceCode, "C", natspec, true);
|
||||||
"notice": "Hello world"
|
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";
|
})ABCDEF";
|
||||||
|
|
||||||
checkNatspec(sourceCode, "C", natspec, true);
|
checkNatspec(sourceCode, "C", natspec, true);
|
||||||
@ -1423,6 +1484,411 @@ BOOST_AUTO_TEST_CASE(user_default_inherit)
|
|||||||
checkNatspec(sourceCode, "Token", natspec, true);
|
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)
|
BOOST_AUTO_TEST_CASE(dev_inherit_parameter_mismatch)
|
||||||
{
|
{
|
||||||
char const *sourceCode = R"(
|
char const *sourceCode = R"(
|
||||||
@ -1517,6 +1983,80 @@ BOOST_AUTO_TEST_CASE(user_inherit_parameter_mismatch)
|
|||||||
checkNatspec(sourceCode, "Token", natspec2, true);
|
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()
|
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