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