def test_no_tags_implies_notice(): code = """ ''' Because there is no tag, this docstring is handled as a notice. ''' @public def foo(): ''' This one too! ''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) userdoc, devdoc = parse_natspec(srilang_ast, global_ctx) assert userdoc == { "methods": { "foo()": { "notice": "This one too!" } }, "notice": "Because there is no tag, this docstring is handled as a notice.", } assert not devdoc
def test_whitespace(): code = """ ''' @dev Whitespace gets cleaned up, people can use awful formatting. We don't mind! @author Mr No-linter ''' """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) _, devdoc = parse_natspec(srilang_ast, global_ctx) assert devdoc == { "author": "Mr No-linter", "details": "Whitespace gets cleaned up, people can use awful formatting. We don't mind!", }
def test_returns(): code = """ @public def foo(bar: int128, baz: uint256) -> (int128, uint256): ''' @return value of bar @return value of baz ''' return bar, baz """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) _, devdoc = parse_natspec(srilang_ast, global_ctx) assert devdoc == { "methods": { "foo(int128,uint256)": { "returns": { "_0": "value of bar", "_1": "value of baz" } } } }
def test_params(): code = """ @public def foo(bar: int128, baz: uint256, potato: bytes32): ''' @param bar a number @param baz also a number @dev we didn't document potato, but that's ok ''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) _, devdoc = parse_natspec(srilang_ast, global_ctx) assert devdoc == { "methods": { "foo(int128,uint256,bytes32)": { "details": "we didn't document potato, but that's ok", "params": { "bar": "a number", "baz": "also a number" }, } } }
def test_documentation_example_output(): srilang_ast = parse_to_ast(test_code) global_ctx = GlobalContext.get_global_context(srilang_ast) userdoc, devdoc = parse_natspec(srilang_ast, global_ctx) assert userdoc == expected_userdoc assert devdoc == expected_devdoc
def generate_inline_function(code, variables, memory_allocator): ast_code = parse_to_ast(code) new_context = Context(vars=variables, global_ctx=GlobalContext(), memory_allocator=memory_allocator, origcode=code) generated_lll = parse_body(ast_code.body, new_context) return new_context, generated_lll
def test_empty_param(bad_docstring): code = f""" @public def foo(a: int128): '''{bad_docstring}''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises(NatSpecSyntaxException, match="No description given for parameter 'a'"): parse_natspec(srilang_ast, global_ctx)
def test_too_many_returns_no_return_type(): code = """ @public def foo(): '''@return should fail, the function does not include a return value''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises(NatSpecSyntaxException, match="Method does not return any values"): parse_natspec(srilang_ast, global_ctx)
def test_unknown_param(): code = """ @public def foo(bar: int128, baz: uint256): '''@param hotdog not a number''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises(NatSpecSyntaxException, match="Method has no parameter 'hotdog'"): parse_natspec(srilang_ast, global_ctx)
def test_invalid_field(): code = """ @public def foo(): '''@title function level docstrings cannot have titles''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises(NatSpecSyntaxException, match="'@title' is not a valid field"): parse_natspec(srilang_ast, global_ctx)
def parse_to_lll( source_code: str, runtime_only: bool = False, interface_codes: Optional[InterfaceImports] = None) -> LLLnode: srilang_module = sri_ast.parse_to_ast(source_code) global_ctx = GlobalContext.get_global_context( srilang_module, interface_codes=interface_codes) lll_nodes, lll_runtime = parse_tree_to_lll(source_code, global_ctx) if runtime_only: return lll_runtime else: return lll_nodes
def test_duplicate_param(): code = """ @public def foo(bar: int128, baz: uint256): ''' @param bar a number @param bar also a number ''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises(NatSpecSyntaxException, match="Parameter 'bar' documented more than once"): parse_natspec(srilang_ast, global_ctx)
def test_unknown_field(): code = """ @public def foo(): ''' @notice this is ok @thing this is bad ''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises(NatSpecSyntaxException, match="Unknown NatSpec field '@thing'"): parse_natspec(srilang_ast, global_ctx)
def test_partial_natspec(): code = """ @public def foo(): ''' Regular comments preceeding natspec is not allowed @notice this is natspec ''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises(NatSpecSyntaxException, match="NatSpec docstring opens with untagged comment"): parse_natspec(srilang_ast, global_ctx)
def extract_sigs(sig_code): if sig_code['type'] == 'srilang': interface_ast = [ i for i in sri_ast.parse_to_ast(sig_code['code']) if isinstance(i, sri_ast.FunctionDef) or (isinstance(i, sri_ast.AnnAssign) and i.target.id != "implements") ] global_ctx = GlobalContext.get_global_context(interface_ast) return sig_utils.mk_full_signature(global_ctx, sig_formatter=lambda x: x) elif sig_code['type'] == 'json': return mk_full_signature_from_json(sig_code['code']) else: raise Exception( (f"Unknown interface signature type '{sig_code['type']}' supplied. " "'srilang' & 'json' are supported") )
def test_duplicate_fields(): code = """ @public def foo(): ''' @notice It's fine to have one notice, but.... @notice a second one, not so much ''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises(NatSpecSyntaxException, match="Duplicate NatSpec field '@notice'"): parse_natspec(srilang_ast, global_ctx)
def test_too_many_returns_single_return_type(): code = """ @public def foo() -> int128: ''' @return int128 @return this should fail ''' return 1 """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) with pytest.raises( NatSpecSyntaxException, match="Number of documented return values exceeds actual number", ): parse_natspec(srilang_ast, global_ctx)
def generate_global_context( srilang_module: sri_ast.Module, interface_codes: Optional[InterfaceImports], ) -> GlobalContext: """ Generate a contextualized AST from the srilang AST. Arguments --------- srilang_module : sri_ast.Module Top-level srilang AST node interface_codes: Dict, optional Interfaces that may be imported by the contracts. Returns ------- GlobalContext Sorted, contextualized representation of the srilang AST """ return GlobalContext.get_global_context( srilang_module, interface_codes=interface_codes )
def test_ignore_private_methods(): code = """ @public def foo(bar: int128, baz: uint256): '''@dev I will be parsed.''' pass @private def notfoo(bar: int128, baz: uint256): '''@dev I will not be parsed.''' pass """ srilang_ast = parse_to_ast(code) global_ctx = GlobalContext.get_global_context(srilang_ast) _, devdoc = parse_natspec(srilang_ast, global_ctx) assert devdoc["methods"] == { "foo(int128,uint256)": { "details": "I will be parsed." } }