Allow custom natspec tags.

This commit is contained in:
chriseth 2021-02-25 13:57:34 +01:00
parent 2a25d04896
commit 5690020d88
6 changed files with 99 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

@ -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

@ -28,6 +28,8 @@
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <liblangutil/ErrorReporter.h>
#include <boost/algorithm/string.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::langutil;
@ -153,15 +155,17 @@ 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))
if (boost::starts_with(tagName, "custom:") && tagName.size() > string("custom:").size())
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 +175,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 +197,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()