Merge pull request #11016 from ethereum/customNatspec

Allow custom natspec tags.
This commit is contained in:
chriseth 2021-03-02 16:53:44 +01:00 committed by GitHub
commit cc88fd25e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 134 additions and 13 deletions

View File

@ -8,6 +8,7 @@ Compiler Features:
* Inline Assembly: Do not warn anymore about variables or functions being shadowed by EVM opcodes.
* NatSpec: Provide source locations for parsing errors.
* Optimizer: Simple inlining when jumping to small blocks that jump again after a few side-effect free opcodes.
* NatSpec: Allow and export all tags that start with ``@custom:``.
Bugfixes:

View File

@ -109,12 +109,16 @@ Tag
``@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
``@custom:...`` Custom tag, semantics is application-defined everywhere
=============== ====================================================================================== =============================
If your function returns multiple values, like ``(int quotient, int remainder)``
then use multiple ``@return`` statements in the same format as the
``@param`` statements.
Custom tags start with ``@custom:`` and can contain lowercase characters and hyphens following that.
They can be used everywhere and are part of the developer documentation.
.. _header-dynamic:
Dynamic expressions

View File

@ -27,6 +27,8 @@
#include <libsolidity/ast/AST.h>
#include <liblangutil/ErrorReporter.h>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::langutil;
@ -54,8 +56,8 @@ void copyMissingTags(set<CallableDeclaration const*> const& _baseFunctions, Stru
for (auto it = sourceDoc.docTags.begin(); it != sourceDoc.docTags.end();)
{
string const& tag = it->first;
// Don't copy tag "inheritdoc" or already existing tags
if (tag == "inheritdoc" || _target.docTags.count(tag))
// Don't copy tag "inheritdoc", custom tags or already existing tags
if (tag == "inheritdoc" || _target.docTags.count(tag) || boost::starts_with(tag, "custom"))
{
it++;
continue;

View File

@ -27,6 +27,13 @@
#include <libsolidity/parsing/DocStringParser.h>
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/Common.h>
#include <range/v3/algorithm/any_of.hpp>
#include <boost/algorithm/string.hpp>
#include <regex>
using namespace std;
using namespace solidity;
@ -153,15 +160,27 @@ void DocStringTagParser::parseDocStrings(
_annotation.docTags = DocStringParser{*_node.documentation(), m_errorReporter}.parse();
size_t returnTagsVisited = 0;
for (auto const& docTag: _annotation.docTags)
for (auto const& [tagName, tagValue]: _annotation.docTags)
{
if (!_validTags.count(docTag.first))
string static const customPrefix("custom:");
if (boost::starts_with(tagName, customPrefix) && tagName.size() > customPrefix.size())
{
regex static const customRegex("^custom:[a-z][a-z-]*$");
if (!regex_match(tagName, customRegex))
m_errorReporter.docstringParsingError(
2968_error,
_node.documentation()->location(),
"Invalid character in custom tag @" + tagName + ". Only lowercase letters and \"-\" are permitted."
);
continue;
}
else if (!_validTags.count(tagName))
m_errorReporter.docstringParsingError(
6546_error,
_node.documentation()->location(),
"Documentation tag @" + docTag.first + " not valid for " + _nodeName + "."
"Documentation tag @" + tagName + " not valid for " + _nodeName + "."
);
else if (docTag.first == "return")
else if (tagName == "return")
{
returnTagsVisited++;
if (auto const* varDecl = dynamic_cast<VariableDeclaration const*>(&_node))
@ -171,19 +190,19 @@ void DocStringTagParser::parseDocStrings(
m_errorReporter.docstringParsingError(
5256_error,
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + "\" is only allowed once on state-variables."
"Documentation tag \"@" + tagName + "\" is only allowed once on state-variables."
);
}
else if (auto const* function = dynamic_cast<FunctionDefinition const*>(&_node))
{
string content = docTag.second.content;
string content = tagValue.content;
string firstWord = content.substr(0, content.find_first_of(" \t"));
if (returnTagsVisited > function->returnParameters().size())
m_errorReporter.docstringParsingError(
2604_error,
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
"Documentation tag \"@" + tagName + " " + tagValue.content + "\"" +
" exceeds the number of return parameters."
);
else
@ -193,7 +212,7 @@ void DocStringTagParser::parseDocStrings(
m_errorReporter.docstringParsingError(
5856_error,
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + " " + docTag.second.content + "\"" +
"Documentation tag \"@" + tagName + " " + tagValue.content + "\"" +
" does not contain the name of its return parameter."
);
}

View File

@ -27,7 +27,9 @@
#include <libsolidity/interface/Natspec.h>
#include <libsolidity/ast/AST.h>
#include <boost/range/irange.hpp>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace solidity;
@ -97,7 +99,7 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
{
Json::Value doc;
Json::Value doc = extractCustomDoc(_contractDef.annotation().docTags);
Json::Value methods(Json::objectValue);
doc["version"] = Json::Value(c_natspecVersion);
@ -205,9 +207,21 @@ string Natspec::extractDoc(multimap<string, DocTag> const& _tags, string const&
return value;
}
Json::Value Natspec::extractCustomDoc(multimap<string, DocTag> const& _tags)
{
std::map<string, string> concatenated;
for (auto const& [tag, value]: _tags)
if (boost::starts_with(tag, "custom"))
concatenated[tag] += value.content;
Json::Value result;
for (auto& [tag, value]: concatenated)
result[tag] = move(value);
return result;
}
Json::Value Natspec::devDocumentation(std::multimap<std::string, DocTag> const& _tags)
{
Json::Value json(Json::objectValue);
Json::Value json = extractCustomDoc(_tags);
auto dev = extractDoc(_tags, "dev");
if (!dev.empty())
json["details"] = Json::Value(dev);

View File

@ -57,6 +57,9 @@ private:
/// @returns concatenation of all content under the given tag name.
static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name);
/// Extract all custom tags from @a _tags.
static Json::Value extractCustomDoc(std::multimap<std::string, DocTag> const& _tags);
/// Helper-function that will create a json object with dev specific annotations, if present.
/// @param _tags docTags that are used.
/// @return A JSON representation

View File

@ -2247,6 +2247,68 @@ BOOST_AUTO_TEST_CASE(dev_return_name_no_description)
checkNatspec(sourceCode, "B", natspec2, false);
}
BOOST_AUTO_TEST_CASE(custom)
{
char const* sourceCode = R"(
/// @custom:x one two three
/// @custom:y line
/// break
/// @custom:t one
/// @custom:t two
contract A {
/// @custom:note statevar
uint x;
/// @custom:since 2014
function g(int x) public pure virtual returns (int, int) { return (1, 2); }
}
)";
char const* natspec = R"ABCDEF({
"custom:t": "onetwo",
"custom:x": "one two three",
"custom:y": "line break",
"methods":
{
"g(int256)":
{
"custom:since": "2014"
}
},
"stateVariables": { "x": { "custom:note": "statevar" } }
})ABCDEF";
checkNatspec(sourceCode, "A", natspec, false);
}
BOOST_AUTO_TEST_CASE(custom_inheritance)
{
char const *sourceCode = R"(
contract A {
/// @custom:since 2014
function g(uint x) public pure virtual {}
}
contract B is A {
function g(uint x) public pure override {}
}
)";
char const* natspecA = R"ABCDEF({
"methods":
{
"g(uint256)":
{
"custom:since": "2014"
}
}
)ABCDEF";
char const* natspecB = R"ABCDEF({
"methods": {}
})ABCDEF";
checkNatspec(sourceCode, "A", natspecA, false);
checkNatspec(sourceCode, "B", natspecB, false);
}
}
BOOST_AUTO_TEST_SUITE_END()

View File

@ -0,0 +1,16 @@
/// @a&b test
contract C {
/// @custom:x^y test2
function f() public pure {}
/// @custom:
function g() public pure {}
/// @custom:abcDEF
function h() public pure {}
/// @custom:abc-def
function i() public pure {}
}
// ----
// DocstringParsingError 6546: (0-14): Documentation tag @a&b not valid for contracts.
// DocstringParsingError 2968: (28-49): Invalid character in custom tag @custom:x^y. Only lowercase letters and "-" are permitted.
// DocstringParsingError 6546: (80-92): Documentation tag @custom: not valid for functions.
// DocstringParsingError 2968: (123-141): Invalid character in custom tag @custom:abcDEF. Only lowercase letters and "-" are permitted.