mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #11016 from ethereum/customNatspec
Allow custom natspec tags.
This commit is contained in:
commit
cc88fd25e9
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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."
|
||||
);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
16
test/libsolidity/syntaxTests/natspec/invalid/invalid_tag.sol
Normal file
16
test/libsolidity/syntaxTests/natspec/invalid/invalid_tag.sol
Normal 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.
|
Loading…
Reference in New Issue
Block a user