Merge pull request #8532 from aarlt/structured-docs-variables-aarlt

Allow NatSpec comments for state variables
This commit is contained in:
chriseth 2020-05-19 19:26:35 +02:00 committed by GitHub
commit 22d5caa979
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 384 additions and 99 deletions

View File

@ -7,7 +7,7 @@ Compiler Features:
* Build system: Update the soljson.js build to emscripten 1.39.15 and boost 1.73.0 and include Z3 for integrated SMTChecker support without the callback mechanism. * Build system: Update the soljson.js build to emscripten 1.39.15 and boost 1.73.0 and include Z3 for integrated SMTChecker support without the callback mechanism.
* SMTChecker: Support array ``length``. * SMTChecker: Support array ``length``.
* SMTChecker: Support array ``push`` and ``pop``. * SMTChecker: Support array ``push`` and ``pop``.
* Add support for natspec comments on state variables.
Bugfixes: Bugfixes:
* Optimizer: Fixed a bug in BlockDeDuplicator. * Optimizer: Fixed a bug in BlockDeDuplicator.

View File

@ -255,9 +255,9 @@ which only need to be created if there is a dispute.
contract C { contract C {
function createDSalted(bytes32 salt, uint arg) public { function createDSalted(bytes32 salt, uint arg) public {
/// This complicated expression just tells you how the address // This complicated expression just tells you how the address
/// can be pre-computed. It is just there for illustration. // can be pre-computed. It is just there for illustration.
/// You actually only need ``new D{salt: salt}(arg)``. // You actually only need ``new D{salt: salt}(arg)``.
address predictedAddress = address(uint(keccak256(abi.encodePacked( address predictedAddress = address(uint(keccak256(abi.encodePacked(
byte(0xff), byte(0xff),
address(this), address(this),

View File

@ -28,6 +28,9 @@ Documentation Example
Documentation is inserted above each ``class``, ``interface`` and Documentation is inserted above each ``class``, ``interface`` and
``function`` using the doxygen notation format. ``function`` using the doxygen notation format.
Note: a ``public`` state variable is equivalent to a ``function``
for the purposes of NatSpec.
- For Solidity you may choose ``///`` for single or multi-line - For Solidity you may choose ``///`` for single or multi-line
comments, or ``/**`` and ending with ``*/``. comments, or ``/**`` and ending with ``*/``.
@ -82,10 +85,10 @@ Tag
=========== =============================================================================== ============================= =========== =============================================================================== =============================
``@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, function ``@author`` The name of the author contract, interface, function
``@notice`` Explain to an end user what this does contract, interface, function ``@notice`` Explain to an end user what this does contract, interface, function, public state variable
``@dev`` Explain to a developer any extra details contract, interface, function ``@dev`` Explain to a developer any extra details contract, interface, function, state variable
``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function ``@param`` Documents a parameter just like in doxygen (must be followed by parameter name) function
``@return`` Documents the return variables of a contract's function function ``@return`` Documents the return variables of a contract's function 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)``

View File

@ -63,7 +63,7 @@ complete contract):
// THIS CONTRACT CONTAINS A BUG - DO NOT USE // THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund { contract Fund {
/// Mapping of ether shares of the contract. /// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares; mapping(address => uint) shares;
/// Withdraw your share. /// Withdraw your share.
function withdraw() public { function withdraw() public {
@ -87,7 +87,7 @@ as it uses ``call`` which forwards all remaining gas by default:
// THIS CONTRACT CONTAINS A BUG - DO NOT USE // THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund { contract Fund {
/// Mapping of ether shares of the contract. /// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares; mapping(address => uint) shares;
/// Withdraw your share. /// Withdraw your share.
function withdraw() public { function withdraw() public {
@ -106,7 +106,7 @@ outlined further below:
pragma solidity >=0.4.11 <0.7.0; pragma solidity >=0.4.11 <0.7.0;
contract Fund { contract Fund {
/// Mapping of ether shares of the contract. /// @dev Mapping of ether shares of the contract.
mapping(address => uint) shares; mapping(address => uint) shares;
/// Withdraw your share. /// Withdraw your share.
function withdraw() public { function withdraw() public {

View File

@ -415,7 +415,7 @@ Array slices are useful to ABI-decode secondary data passed in function paramete
pragma solidity >=0.6.0 <0.7.0; pragma solidity >=0.6.0 <0.7.0;
contract Proxy { contract Proxy {
/// Address of the client contract managed by proxy i.e., this contract /// @dev Address of the client contract managed by proxy i.e., this contract
address client; address client;
constructor(address _client) public { constructor(address _client) public {

View File

@ -57,6 +57,31 @@ bool DocStringAnalyser::visit(FunctionDefinition const& _function)
return true; return true;
} }
bool DocStringAnalyser::visit(VariableDeclaration const& _variable)
{
if (_variable.isStateVariable())
{
static set<string> const validPublicTags = set<string>{"dev", "notice", "return", "title", "author"};
if (_variable.isPublic())
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "public state variables");
else
{
parseDocStrings(_variable, _variable.annotation(), validPublicTags, "non-public state variables");
if (_variable.annotation().docTags.count("notice") > 0)
m_errorReporter.warning(
9098_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(
4822_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 DocStringAnalyser::visit(ModifierDefinition const& _modifier) bool DocStringAnalyser::visit(ModifierDefinition const& _modifier)
{ {
handleCallable(_modifier, _modifier, _modifier.annotation()); handleCallable(_modifier, _modifier, _modifier.annotation());
@ -144,7 +169,20 @@ void DocStringAnalyser::parseDocStrings(
if (docTag.first == "return") if (docTag.first == "return")
{ {
returnTagsVisited++; returnTagsVisited++;
if (auto* function = dynamic_cast<FunctionDefinition const*>(&_node)) if (auto* varDecl = dynamic_cast<VariableDeclaration const*>(&_node))
{
if (!varDecl->isPublic())
appendError(
_node.documentation()->location(),
"Documentation tag \"@" + docTag.first + "\" is only allowed on public state-variables."
);
if (returnTagsVisited > 1)
appendError(
_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 content = docTag.second.content;
string firstWord = content.substr(0, content.find_first_of(" \t")); string firstWord = content.substr(0, content.find_first_of(" \t"));

View File

@ -46,6 +46,7 @@ public:
private: private:
bool visit(ContractDefinition const& _contract) override; 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(ModifierDefinition const& _modifier) override; bool visit(ModifierDefinition const& _modifier) override;
bool visit(EventDefinition const& _event) override; bool visit(EventDefinition const& _event) override;
@ -67,6 +68,12 @@ private:
StructurallyDocumentedAnnotation& _annotation StructurallyDocumentedAnnotation& _annotation
); );
void handleDeclaration(
Declaration const& _declaration,
StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation
);
void parseDocStrings( void parseDocStrings(
StructurallyDocumented const& _node, StructurallyDocumented const& _node,
StructurallyDocumentedAnnotation& _annotation, StructurallyDocumentedAnnotation& _annotation,

View File

@ -859,7 +859,7 @@ private:
* Declaration of a variable. This can be used in various places, e.g. in function parameter * Declaration of a variable. This can be used in various places, e.g. in function parameter
* lists, struct definitions and even function bodies. * lists, struct definitions and even function bodies.
*/ */
class VariableDeclaration: public Declaration class VariableDeclaration: public Declaration, public StructurallyDocumented
{ {
public: public:
enum Location { Unspecified, Storage, Memory, CallData }; enum Location { Unspecified, Storage, Memory, CallData };
@ -882,6 +882,7 @@ public:
ASTPointer<ASTString> const& _name, ASTPointer<ASTString> const& _name,
ASTPointer<Expression> _value, ASTPointer<Expression> _value,
Visibility _visibility, Visibility _visibility,
ASTPointer<StructuredDocumentation> const _documentation = nullptr,
bool _isStateVar = false, bool _isStateVar = false,
bool _isIndexed = false, bool _isIndexed = false,
Mutability _mutability = Mutability::Mutable, Mutability _mutability = Mutability::Mutable,
@ -889,6 +890,7 @@ public:
Location _referenceLocation = Location::Unspecified Location _referenceLocation = Location::Unspecified
): ):
Declaration(_id, _location, _name, _visibility), Declaration(_id, _location, _name, _visibility),
StructurallyDocumented(std::move(_documentation)),
m_typeName(std::move(_type)), m_typeName(std::move(_type)),
m_value(std::move(_value)), m_value(std::move(_value)),
m_isStateVariable(_isStateVar), m_isStateVariable(_isStateVar),

View File

@ -171,7 +171,7 @@ struct ModifierDefinitionAnnotation: CallableDeclarationAnnotation, Structurally
{ {
}; };
struct VariableDeclarationAnnotation: DeclarationAnnotation struct VariableDeclarationAnnotation: DeclarationAnnotation, StructurallyDocumentedAnnotation
{ {
/// Type of variable (type of identifier referencing this variable). /// Type of variable (type of identifier referencing this variable).
TypePointer type = nullptr; TypePointer type = nullptr;

View File

@ -391,6 +391,8 @@ bool ASTJsonConverter::visit(VariableDeclaration const& _node)
}; };
if (_node.isStateVariable() && _node.isPublic()) if (_node.isStateVariable() && _node.isPublic())
attributes.emplace_back("functionSelector", _node.externalIdentifierHex()); attributes.emplace_back("functionSelector", _node.externalIdentifierHex());
if (_node.isStateVariable() && _node.documentation())
attributes.emplace_back("documentation", toJson(*_node.documentation()));
if (m_inEvent) if (m_inEvent)
attributes.emplace_back("indexed", _node.isIndexed()); attributes.emplace_back("indexed", _node.isIndexed());
if (!_node.annotation().baseFunctions.empty()) if (!_node.annotation().baseFunctions.empty())

View File

@ -441,6 +441,7 @@ ASTPointer<VariableDeclaration> ASTJsonImporter::createVariableDeclaration(Json:
make_shared<ASTString>(member(_node, "name").asString()), make_shared<ASTString>(member(_node, "name").asString()),
nullOrCast<Expression>(member(_node, "value")), nullOrCast<Expression>(member(_node, "value")),
visibility(_node), visibility(_node),
_node["documentation"].isNull() ? nullptr : createDocumentation(member(_node, "documentation")),
memberAsBool(_node, "stateVariable"), memberAsBool(_node, "stateVariable"),
_node.isMember("indexed") ? memberAsBool(_node, "indexed") : false, _node.isMember("indexed") ? memberAsBool(_node, "indexed") : false,
mutability, mutability,

View File

@ -40,7 +40,7 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
auto constructorDefinition(_contractDef.constructor()); auto constructorDefinition(_contractDef.constructor());
if (constructorDefinition) if (constructorDefinition)
{ {
string value = extractDoc(constructorDefinition->annotation().docTags, "notice"); string const value = extractDoc(constructorDefinition->annotation().docTags, "notice");
if (!value.empty()) if (!value.empty())
// add the constructor, only if we have any documentation to add // add the constructor, only if we have any documentation to add
methods["constructor"] = Json::Value(value); methods["constructor"] = Json::Value(value);
@ -52,6 +52,7 @@ 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())
{
if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration())) if (auto const* f = dynamic_cast<FunctionDefinition const*>(&it.second->declaration()))
{ {
string value = extractDoc(f->annotation().docTags, "notice"); string value = extractDoc(f->annotation().docTags, "notice");
@ -63,6 +64,19 @@ Json::Value Natspec::userDocumentation(ContractDefinition const& _contractDef)
methods[it.second->externalSignature()] = user; methods[it.second->externalSignature()] = user;
} }
} }
else if (auto var = dynamic_cast<VariableDeclaration const*>(&it.second->declaration()))
{
solAssert(var->isStateVariable() && var->isPublic(), "");
string value = extractDoc(var->annotation().docTags, "notice");
if (!value.empty())
{
Json::Value user;
// since @notice is the only user tag if missing function should not appear
user["notice"] = Json::Value(value);
methods[it.second->externalSignature()] = user;
}
}
}
doc["methods"] = methods; doc["methods"] = methods;
return doc; return doc;
@ -110,7 +124,20 @@ Json::Value Natspec::devDocumentation(ContractDefinition const& _contractDef)
} }
} }
Json::Value stateVariables(Json::objectValue);
for (VariableDeclaration const* varDecl: _contractDef.stateVariables())
{
if (auto devDoc = devDocumentation(varDecl->annotation().docTags); !devDoc.empty())
stateVariables[varDecl->name()] = devDoc;
solAssert(varDecl->annotation().docTags.count("return") <= 1, "");
if (varDecl->annotation().docTags.count("return") == 1)
stateVariables[varDecl->name()]["return"] = extractDoc(varDecl->annotation().docTags, "return");
}
doc["methods"] = methods; doc["methods"] = methods;
if (!stateVariables.empty())
doc["stateVariables"] = stateVariables;
return doc; return doc;
} }

View File

@ -686,6 +686,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
ASTNodeFactory nodeFactory = _lookAheadArrayType ? ASTNodeFactory nodeFactory = _lookAheadArrayType ?
ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this); ASTNodeFactory(*this, _lookAheadArrayType) : ASTNodeFactory(*this);
ASTPointer<TypeName> type; ASTPointer<TypeName> type;
ASTPointer<StructuredDocumentation> const documentation = parseStructuredDocumentation();
if (_lookAheadArrayType) if (_lookAheadArrayType)
type = _lookAheadArrayType; type = _lookAheadArrayType;
else else
@ -695,6 +696,9 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
nodeFactory.setEndPositionFromNode(type); nodeFactory.setEndPositionFromNode(type);
} }
if (!_options.isStateVariable && documentation != nullptr)
parserWarning(2837_error, "Only state variables can have a docstring. This will be disallowed in 0.7.0.");
if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace) if (dynamic_cast<FunctionTypeName*>(type.get()) && _options.isStateVariable && m_scanner->currentToken() == Token::LBrace)
fatalParserError( fatalParserError(
2915_error, 2915_error,
@ -809,6 +813,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
identifier, identifier,
value, value,
visibility, visibility,
documentation,
_options.isStateVariable, _options.isStateVariable,
isIndexed, isIndexed,
mutability, mutability,

View File

@ -87,10 +87,10 @@
{ {
"C": "C":
[ [
20 23
] ]
}, },
"id": 21, "id": 24,
"license": null, "license": null,
"nodeType": "SourceUnit", "nodeType": "SourceUnit",
"nodes": "nodes":
@ -102,90 +102,129 @@
"contractKind": "contract", "contractKind": "contract",
"documentation": null, "documentation": null,
"fullyImplemented": true, "fullyImplemented": true,
"id": 20, "id": 23,
"linearizedBaseContracts": "linearizedBaseContracts":
[ [
20 23
], ],
"name": "C", "name": "C",
"nodeType": "ContractDefinition", "nodeType": "ContractDefinition",
"nodes": "nodes":
[ [
{ {
"anonymous": false, "constant": false,
"documentation": "documentation":
{ {
"id": 7, "id": 7,
"nodeType": "StructuredDocumentation", "nodeType": "StructuredDocumentation",
"src": "15:26:3", "src": "15:32:3",
"text": "Some comment on Evt." "text": "Some comment on state var."
}, },
"functionSelector": "c19d93fb",
"id": 9, "id": 9,
"name": "Evt", "mutability": "mutable",
"nodeType": "EventDefinition", "name": "state",
"parameters": "nodeType": "VariableDeclaration",
"overrides": null,
"scope": 23,
"src": "48:17:3",
"stateVariable": true,
"storageLocation": "default",
"typeDescriptions":
{
"typeIdentifier": "t_uint256",
"typeString": "uint256"
},
"typeName":
{ {
"id": 8, "id": 8,
"nodeType": "ParameterList", "name": "uint",
"parameters": [], "nodeType": "ElementaryTypeName",
"src": "51:2:3" "src": "48:4:3",
"typeDescriptions":
{
"typeIdentifier": "t_uint256",
"typeString": "uint256"
}
}, },
"src": "42:12:3" "value": null,
"visibility": "public"
}, },
{ {
"body": "anonymous": false,
{
"id": 13,
"nodeType": "Block",
"src": "99:6:3",
"statements":
[
{
"id": 12,
"nodeType": "PlaceholderStatement",
"src": "101:1:3"
}
]
},
"documentation": "documentation":
{ {
"id": 10, "id": 10,
"nodeType": "StructuredDocumentation", "nodeType": "StructuredDocumentation",
"src": "57:26:3", "src": "69:26:3",
"text": "Some comment on mod." "text": "Some comment on Evt."
}, },
"id": 14, "id": 12,
"name": "mod", "name": "Evt",
"nodeType": "ModifierDefinition", "nodeType": "EventDefinition",
"overrides": null,
"parameters": "parameters":
{ {
"id": 11, "id": 11,
"nodeType": "ParameterList", "nodeType": "ParameterList",
"parameters": [], "parameters": [],
"src": "96:2:3" "src": "105:2:3"
}, },
"src": "84:21:3", "src": "96:12:3"
},
{
"body":
{
"id": 16,
"nodeType": "Block",
"src": "153:6:3",
"statements":
[
{
"id": 15,
"nodeType": "PlaceholderStatement",
"src": "155:1:3"
}
]
},
"documentation":
{
"id": 13,
"nodeType": "StructuredDocumentation",
"src": "111:26:3",
"text": "Some comment on mod."
},
"id": 17,
"name": "mod",
"nodeType": "ModifierDefinition",
"overrides": null,
"parameters":
{
"id": 14,
"nodeType": "ParameterList",
"parameters": [],
"src": "150:2:3"
},
"src": "138:21:3",
"virtual": false, "virtual": false,
"visibility": "internal" "visibility": "internal"
}, },
{ {
"body": "body":
{ {
"id": 18, "id": 21,
"nodeType": "Block", "nodeType": "Block",
"src": "155:2:3", "src": "209:2:3",
"statements": [] "statements": []
}, },
"documentation": "documentation":
{ {
"id": 15, "id": 18,
"nodeType": "StructuredDocumentation", "nodeType": "StructuredDocumentation",
"src": "108:25:3", "src": "162:25:3",
"text": "Some comment on fn." "text": "Some comment on fn."
}, },
"functionSelector": "a4a2c40b", "functionSelector": "a4a2c40b",
"id": 19, "id": 22,
"implemented": true, "implemented": true,
"kind": "function", "kind": "function",
"modifiers": [], "modifiers": [],
@ -194,29 +233,29 @@
"overrides": null, "overrides": null,
"parameters": "parameters":
{ {
"id": 16, "id": 19,
"nodeType": "ParameterList", "nodeType": "ParameterList",
"parameters": [], "parameters": [],
"src": "145:2:3" "src": "199:2:3"
}, },
"returnParameters": "returnParameters":
{ {
"id": 17, "id": 20,
"nodeType": "ParameterList", "nodeType": "ParameterList",
"parameters": [], "parameters": [],
"src": "155:0:3" "src": "209:0:3"
}, },
"scope": 20, "scope": 23,
"src": "134:23:3", "src": "188:23:3",
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"virtual": false, "virtual": false,
"visibility": "public" "visibility": "public"
} }
], ],
"scope": 21, "scope": 24,
"src": "0:159:3" "src": "0:213:3"
} }
], ],
"src": "0:160:3" "src": "0:214:3"
} }
] ]

View File

@ -11,6 +11,7 @@ contract C {}
// ---- SOURCE: c // ---- SOURCE: c
contract C { contract C {
/** Some comment on state var.*/ uint public state;
/** Some comment on Evt.*/ event Evt(); /** Some comment on Evt.*/ event Evt();
/** Some comment on mod.*/ modifier mod() { _; } /** Some comment on mod.*/ modifier mod() { _; }
/** Some comment on fn.*/ function fn() public {} /** Some comment on fn.*/ function fn() public {}

View File

@ -6,7 +6,7 @@
{ {
"C": "C":
[ [
20 23
] ]
}, },
"license": null "license": null
@ -30,13 +30,54 @@
"fullyImplemented": true, "fullyImplemented": true,
"linearizedBaseContracts": "linearizedBaseContracts":
[ [
20 23
], ],
"name": "C", "name": "C",
"scope": 21 "scope": 24
}, },
"children": "children":
[ [
{
"attributes":
{
"constant": false,
"functionSelector": "c19d93fb",
"mutability": "mutable",
"name": "state",
"overrides": null,
"scope": 23,
"stateVariable": true,
"storageLocation": "default",
"type": "uint256",
"value": null,
"visibility": "public"
},
"children":
[
{
"attributes":
{
"name": "uint",
"type": "uint256"
},
"id": 8,
"name": "ElementaryTypeName",
"src": "48:4:3"
},
{
"attributes":
{
"text": "Some comment on state var."
},
"id": 7,
"name": "StructuredDocumentation",
"src": "15:32:3"
}
],
"id": 9,
"name": "VariableDeclaration",
"src": "48:17:3"
},
{ {
"attributes": "attributes":
{ {
@ -50,9 +91,9 @@
{ {
"text": "Some comment on Evt." "text": "Some comment on Evt."
}, },
"id": 7, "id": 10,
"name": "StructuredDocumentation", "name": "StructuredDocumentation",
"src": "15:26:3" "src": "69:26:3"
}, },
{ {
"attributes": "attributes":
@ -63,14 +104,14 @@
] ]
}, },
"children": [], "children": [],
"id": 8, "id": 11,
"name": "ParameterList", "name": "ParameterList",
"src": "51:2:3" "src": "105:2:3"
} }
], ],
"id": 9, "id": 12,
"name": "EventDefinition", "name": "EventDefinition",
"src": "42:12:3" "src": "96:12:3"
}, },
{ {
"attributes": "attributes":
@ -87,9 +128,9 @@
{ {
"text": "Some comment on mod." "text": "Some comment on mod."
}, },
"id": 10, "id": 13,
"name": "StructuredDocumentation", "name": "StructuredDocumentation",
"src": "57:26:3" "src": "111:26:3"
}, },
{ {
"attributes": "attributes":
@ -100,27 +141,27 @@
] ]
}, },
"children": [], "children": [],
"id": 11, "id": 14,
"name": "ParameterList", "name": "ParameterList",
"src": "96:2:3" "src": "150:2:3"
}, },
{ {
"children": "children":
[ [
{ {
"id": 12, "id": 15,
"name": "PlaceholderStatement", "name": "PlaceholderStatement",
"src": "101:1:3" "src": "155:1:3"
} }
], ],
"id": 13, "id": 16,
"name": "Block", "name": "Block",
"src": "99:6:3" "src": "153:6:3"
} }
], ],
"id": 14, "id": 17,
"name": "ModifierDefinition", "name": "ModifierDefinition",
"src": "84:21:3" "src": "138:21:3"
}, },
{ {
"attributes": "attributes":
@ -135,7 +176,7 @@
], ],
"name": "fn", "name": "fn",
"overrides": null, "overrides": null,
"scope": 20, "scope": 23,
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"virtual": false, "virtual": false,
"visibility": "public" "visibility": "public"
@ -147,9 +188,9 @@
{ {
"text": "Some comment on fn." "text": "Some comment on fn."
}, },
"id": 15, "id": 18,
"name": "StructuredDocumentation", "name": "StructuredDocumentation",
"src": "108:25:3" "src": "162:25:3"
}, },
{ {
"attributes": "attributes":
@ -160,9 +201,9 @@
] ]
}, },
"children": [], "children": [],
"id": 16, "id": 19,
"name": "ParameterList", "name": "ParameterList",
"src": "145:2:3" "src": "199:2:3"
}, },
{ {
"attributes": "attributes":
@ -173,9 +214,9 @@
] ]
}, },
"children": [], "children": [],
"id": 17, "id": 20,
"name": "ParameterList", "name": "ParameterList",
"src": "155:0:3" "src": "209:0:3"
}, },
{ {
"attributes": "attributes":
@ -186,22 +227,22 @@
] ]
}, },
"children": [], "children": [],
"id": 18, "id": 21,
"name": "Block", "name": "Block",
"src": "155:2:3" "src": "209:2:3"
} }
], ],
"id": 19, "id": 22,
"name": "FunctionDefinition", "name": "FunctionDefinition",
"src": "134:23:3" "src": "188:23:3"
} }
], ],
"id": 20, "id": 23,
"name": "ContractDefinition", "name": "ContractDefinition",
"src": "0:159:3" "src": "0:213:3"
} }
], ],
"id": 21, "id": 24,
"name": "SourceUnit", "name": "SourceUnit",
"src": "0:160:3" "src": "0:214:3"
} }

View File

@ -204,6 +204,77 @@ BOOST_AUTO_TEST_CASE(dev_and_user_no_doc)
checkNatspec(sourceCode, "test", userNatspec, true); checkNatspec(sourceCode, "test", userNatspec, true);
} }
BOOST_AUTO_TEST_CASE(public_state_variable)
{
char const* sourceCode = R"(
contract test {
/// @notice example of notice
/// @dev example of dev
/// @return returns state
uint public state;
}
)";
char const* devDoc = R"R(
{
"methods" : {},
"stateVariables" :
{
"state" :
{
"details" : "example of dev",
"return" : "returns state"
}
}
}
)R";
checkNatspec(sourceCode, "test", devDoc, false);
char const* userDoc = R"R(
{
"methods" :
{
"state()" :
{
"notice": "example of notice"
}
}
}
)R";
checkNatspec(sourceCode, "test", userDoc, true);
}
BOOST_AUTO_TEST_CASE(private_state_variable)
{
char const* sourceCode = R"(
contract test {
/// @dev example of dev
uint private state;
}
)";
char const* devDoc = R"(
{
"methods" : {},
"stateVariables" :
{
"state" :
{
"details" : "example of dev"
}
}
}
)";
checkNatspec(sourceCode, "test", devDoc, false);
char const* userDoc = R"(
{
"methods":{}
}
)";
checkNatspec(sourceCode, "test", userDoc, true);
}
BOOST_AUTO_TEST_CASE(dev_desc_after_nl) BOOST_AUTO_TEST_CASE(dev_desc_after_nl)
{ {
char const* sourceCode = R"( char const* sourceCode = R"(

View File

@ -0,0 +1,7 @@
contract C {
/// @title title
/// @author author
uint private state;
}
// ----
// Warning: (17-56): Documentation tag @title and @author is only allowed on contract definitions. It will be disallowed in 0.7.0.

View File

@ -0,0 +1,6 @@
contract test {
/// @return returns something
uint private state;
}
// ----
// DocstringParsingError: (18-47): Documentation tag "@return" is only allowed on public state-variables.

View File

@ -0,0 +1,7 @@
contract C {
/// @notice example of notice
/// @dev example of dev
uint private state;
}
// ----
// Warning: (17-74): Documentation tag on non-public state variables will be disallowed in 0.7.0. You will need to use the @dev tag explicitly.

View File

@ -0,0 +1,5 @@
contract C {
/// @notice example of notice
/// @dev example of dev
uint public state;
}

View File

@ -0,0 +1,9 @@
contract test {
/// @notice example of notice
/// @dev example of dev
/// @return returns something
/// @return returns something
uint public state;
}
// ----
// DocstringParsingError: (18-137): Documentation tag "@return" is only allowed once on state-variables.

View File

@ -0,0 +1,14 @@
contract C {
function f() public pure returns (uint) {
/// @title example of title
/// @author example of author
/// @notice example of notice
/// @dev example of dev
/// @param example of param
/// @return example of return
uint state = 42;
return state;
}
}
// ----
// Warning: (290-295): Only state variables can have a docstring. This will be disallowed in 0.7.0.